Move some system services to separate directories

Refactored the directory structure so that services can be optionally
excluded. This is step 1. Will be followed by another change that makes
it possible to remove services from the build.

Change-Id: Ideacedfd34b5e213217ad3ff4ebb21c4a8e73f85
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
new file mode 100644
index 0000000..3cdf170
--- /dev/null
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -0,0 +1,1515 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TimeZone;
+
+import static android.app.AlarmManager.RTC_WAKEUP;
+import static android.app.AlarmManager.RTC;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
+import static android.app.AlarmManager.ELAPSED_REALTIME;
+
+import com.android.internal.util.LocalLog;
+
+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_MASK = 1 << ELAPSED_REALTIME;
+    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
+    static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup
+
+    static final String TAG = "AlarmManager";
+    static final String ClockReceiver_TAG = "ClockReceiver";
+    static final boolean localLOGV = false;
+    static final boolean DEBUG_BATCH = localLOGV || false;
+    static final boolean DEBUG_VALIDATE = localLOGV || false;
+    static final int ALARM_EVENT = 1;
+    static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
+    
+    static final Intent mBackgroundIntent
+            = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND);
+    static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder();
+    
+    static final boolean WAKEUP_STATS = false;
+
+    final LocalLog mLog = new LocalLog(TAG);
+
+    final Object mLock = new Object();
+
+    long mNativeData;
+    private long mNextWakeup;
+    private long mNextNonWakeup;
+    int mBroadcastRefCount = 0;
+    PowerManager.WakeLock mWakeLock;
+    ArrayList<InFlight> mInFlight = new ArrayList<InFlight>();
+    final AlarmHandler mHandler = new AlarmHandler();
+    ClockReceiver mClockReceiver;
+    private UninstallReceiver mUninstallReceiver;
+    final ResultReceiver mResultReceiver = new ResultReceiver();
+    PendingIntent mTimeTickSender;
+    PendingIntent mDateChangeSender;
+
+    class WakeupEvent {
+        public long when;
+        public int uid;
+        public String action;
+
+        public WakeupEvent(long theTime, int theUid, String theAction) {
+            when = theTime;
+            uid = theUid;
+            action = theAction;
+        }
+    }
+
+    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
+        long end;
+        boolean standalone; // certain "batches" don't participate in coalescing
+
+        final ArrayList<Alarm> alarms = new ArrayList<Alarm>();
+
+        Batch() {
+            start = 0;
+            end = Long.MAX_VALUE;
+        }
+
+        Batch(Alarm seed) {
+            start = seed.whenElapsed;
+            end = seed.maxWhen;
+            alarms.add(seed);
+        }
+
+        int size() {
+            return alarms.size();
+        }
+
+        Alarm get(int index) {
+            return alarms.get(index);
+        }
+
+        boolean canHold(long whenElapsed, long maxWhen) {
+            return (end >= whenElapsed) && (start <= maxWhen);
+        }
+
+        boolean add(Alarm alarm) {
+            boolean newStart = false;
+            // narrows the batch if necessary; presumes that canHold(alarm) is true
+            int index = Collections.binarySearch(alarms, alarm, sIncreasingTimeOrder);
+            if (index < 0) {
+                index = 0 - index - 1;
+            }
+            alarms.add(index, alarm);
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "Adding " + alarm + " to " + this);
+            }
+            if (alarm.whenElapsed > start) {
+                start = alarm.whenElapsed;
+                newStart = true;
+            }
+            if (alarm.maxWhen < end) {
+                end = alarm.maxWhen;
+            }
+
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "    => now " + this);
+            }
+            return newStart;
+        }
+
+        boolean remove(final PendingIntent operation) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            for (int i = 0; i < alarms.size(); ) {
+                Alarm alarm = alarms.get(i);
+                if (alarm.operation.equals(operation)) {
+                    alarms.remove(i);
+                    didRemove = true;
+                } else {
+                    if (alarm.whenElapsed > newStart) {
+                        newStart = alarm.whenElapsed;
+                    }
+                    if (alarm.maxWhen < newEnd) {
+                        newEnd = alarm.maxWhen;
+                    }
+                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+            }
+            return didRemove;
+        }
+
+        boolean remove(final String packageName) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            for (int i = 0; i < alarms.size(); ) {
+                Alarm alarm = alarms.get(i);
+                if (alarm.operation.getTargetPackage().equals(packageName)) {
+                    alarms.remove(i);
+                    didRemove = true;
+                } else {
+                    if (alarm.whenElapsed > newStart) {
+                        newStart = alarm.whenElapsed;
+                    }
+                    if (alarm.maxWhen < newEnd) {
+                        newEnd = alarm.maxWhen;
+                    }
+                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+            }
+            return didRemove;
+        }
+
+        boolean remove(final int userHandle) {
+            boolean didRemove = false;
+            long newStart = 0;  // recalculate endpoints as we go
+            long newEnd = Long.MAX_VALUE;
+            for (int i = 0; i < alarms.size(); ) {
+                Alarm alarm = alarms.get(i);
+                if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) {
+                    alarms.remove(i);
+                    didRemove = true;
+                } else {
+                    if (alarm.whenElapsed > newStart) {
+                        newStart = alarm.whenElapsed;
+                    }
+                    if (alarm.maxWhen < newEnd) {
+                        newEnd = alarm.maxWhen;
+                    }
+                    i++;
+                }
+            }
+            if (didRemove) {
+                // commit the new batch bounds
+                start = newStart;
+                end = newEnd;
+            }
+            return didRemove;
+        }
+
+        boolean hasPackage(final String packageName) {
+            final int N = alarms.size();
+            for (int i = 0; i < N; i++) {
+                Alarm a = alarms.get(i);
+                if (a.operation.getTargetPackage().equals(packageName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        boolean hasWakeups() {
+            final int N = alarms.size();
+            for (int i = 0; i < N; i++) {
+                Alarm a = alarms.get(i);
+                // non-wakeup alarms are types 1 and 3, i.e. have the low bit set
+                if ((a.type & TYPE_NONWAKEUP_MASK) == 0) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder b = new StringBuilder(40);
+            b.append("Batch{"); b.append(Integer.toHexString(this.hashCode()));
+            b.append(" num="); b.append(size());
+            b.append(" start="); b.append(start);
+            b.append(" end="); b.append(end);
+            if (standalone) {
+                b.append(" STANDALONE");
+            }
+            b.append('}');
+            return b.toString();
+        }
+    }
+
+    static class BatchTimeOrder implements Comparator<Batch> {
+        public int compare(Batch b1, Batch b2) {
+            long when1 = b1.start;
+            long when2 = b2.start;
+            if (when1 - when2 > 0) {
+                return 1;
+            }
+            if (when1 - when2 < 0) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+    
+    // minimum recurrence period or alarm futurity for us to be able to fuzz it
+    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);
+        if (isRtc) {
+            when -= System.currentTimeMillis() - SystemClock.elapsedRealtime();
+        }
+        return when;
+    }
+
+    // Apply a heuristic to { recurrence interval, futurity of the trigger time } to
+    // calculate the end of our nominal delivery window for the alarm.
+    static long maxTriggerTime(long now, long triggerAtTime, long interval) {
+        // Current heuristic: batchable window is 75% of either the recurrence interval
+        // [for a periodic alarm] or of the time from now to the desired delivery time,
+        // with a minimum delay/interval of 10 seconds, under which we will simply not
+        // defer the alarm.
+        long futurity = (interval == 0)
+                ? (triggerAtTime - now)
+                : interval;
+        if (futurity < MIN_FUZZABLE_INTERVAL) {
+            futurity = 0;
+        }
+        return triggerAtTime + (long)(.75 * futurity);
+    }
+
+    // returns true if the batch was added at the head
+    static boolean addBatchLocked(ArrayList<Batch> list, Batch newBatch) {
+        int index = Collections.binarySearch(list, newBatch, sBatchOrder);
+        if (index < 0) {
+            index = 0 - index - 1;
+        }
+        list.add(index, newBatch);
+        return (index == 0);
+    }
+
+    // Return the index of the matching batch, or -1 if none found.
+    int attemptCoalesceLocked(long whenElapsed, long maxWhen) {
+        final int N = mAlarmBatches.size();
+        for (int i = 0; i < N; i++) {
+            Batch b = mAlarmBatches.get(i);
+            if (!b.standalone && b.canHold(whenElapsed, maxWhen)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    // The RTC clock has moved arbitrarily, so we need to recalculate all the batching
+    void rebatchAllAlarms() {
+        synchronized (mLock) {
+            rebatchAllAlarmsLocked(true);
+        }
+    }
+
+    void rebatchAllAlarmsLocked(boolean doValidate) {
+        ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone();
+        mAlarmBatches.clear();
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final int oldBatches = oldSet.size();
+        for (int batchNum = 0; batchNum < oldBatches; batchNum++) {
+            Batch batch = oldSet.get(batchNum);
+            final int N = batch.size();
+            for (int i = 0; i < N; i++) {
+                Alarm a = batch.get(i);
+                long whenElapsed = convertToElapsed(a.when, a.type);
+                final long maxElapsed;
+                if (a.whenElapsed == a.maxWhen) {
+                    // Exact
+                    maxElapsed = whenElapsed;
+                } else {
+                    // Not exact.  Preserve any explicit window, otherwise recalculate
+                    // the window based on the alarm's new futurity.  Note that this
+                    // reflects a policy of preferring timely to deferred delivery.
+                    maxElapsed = (a.windowLength > 0)
+                            ? (whenElapsed + a.windowLength)
+                            : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval);
+                }
+                setImplLocked(a.type, a.when, whenElapsed, a.windowLength, maxElapsed,
+                        a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource);
+            }
+        }
+    }
+
+    static final class InFlight extends Intent {
+        final PendingIntent mPendingIntent;
+        final WorkSource mWorkSource;
+        final Pair<String, ComponentName> mTarget;
+        final BroadcastStats mBroadcastStats;
+        final FilterStats mFilterStats;
+
+        InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource) {
+            mPendingIntent = pendingIntent;
+            mWorkSource = workSource;
+            Intent intent = pendingIntent.getIntent();
+            mTarget = intent != null
+                    ? new Pair<String, ComponentName>(intent.getAction(), intent.getComponent())
+                    : null;
+            mBroadcastStats = service.getStatsLocked(pendingIntent);
+            FilterStats fs = mBroadcastStats.filterStats.get(mTarget);
+            if (fs == null) {
+                fs = new FilterStats(mBroadcastStats, mTarget);
+                mBroadcastStats.filterStats.put(mTarget, fs);
+            }
+            mFilterStats = fs;
+        }
+    }
+
+    static final class FilterStats {
+        final BroadcastStats mBroadcastStats;
+        final Pair<String, ComponentName> mTarget;
+
+        long aggregateTime;
+        int count;
+        int numWakeup;
+        long startTime;
+        int nesting;
+
+        FilterStats(BroadcastStats broadcastStats, Pair<String, ComponentName> target) {
+            mBroadcastStats = broadcastStats;
+            mTarget = target;
+        }
+    }
+    
+    static final class BroadcastStats {
+        final String mPackageName;
+
+        long aggregateTime;
+        int count;
+        int numWakeup;
+        long startTime;
+        int nesting;
+        final HashMap<Pair<String, ComponentName>, FilterStats> filterStats
+                = new HashMap<Pair<String, ComponentName>, FilterStats>();
+
+        BroadcastStats(String packageName) {
+            mPackageName = packageName;
+        }
+    }
+    
+    final HashMap<String, BroadcastStats> mBroadcastStats
+            = new HashMap<String, BroadcastStats>();
+    
+    @Override
+    public void onStart() {
+        mNativeData = init();
+        mNextWakeup = mNextNonWakeup = 0;
+
+        // We have to set current TimeZone info to kernel
+        // because kernel doesn't keep this after reboot
+        setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));
+
+        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        
+        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(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.scheduleTimeTickEvent();
+        mClockReceiver.scheduleDateChangedEvent();
+        mUninstallReceiver = new UninstallReceiver();
+        
+        if (mNativeData != 0) {
+            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(mNativeData);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    void setTimeZoneImpl(String tz) {
+        if (TextUtils.isEmpty(tz)) {
+            return;
+        }
+
+        TimeZone zone = TimeZone.getTimeZone(tz);
+        // Prevent reentrant calls from stepping on each other when writing
+        // the time zone property
+        boolean timeZoneWasChanged = false;
+        synchronized (this) {
+            String current = SystemProperties.get(TIMEZONE_PROPERTY);
+            if (current == null || !current.equals(zone.getID())) {
+                if (localLOGV) {
+                    Slog.v(TAG, "timezone changed: " + current + ", new=" + zone.getID());
+                }
+                timeZoneWasChanged = true;
+                SystemProperties.set(TIMEZONE_PROPERTY, zone.getID());
+            }
+
+            // Update the kernel timezone information
+            // Kernel tracks time offsets as 'minutes west of GMT'
+            int gmtOffset = zone.getOffset(System.currentTimeMillis());
+            setKernelTimezone(mNativeData, -(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);
+        }
+    }
+
+    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");
+            return;
+        }
+
+        // Sanity check the window length.  This will catch people mistakenly
+        // trying to pass an end-of-window timestamp rather than a duration.
+        if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
+            Slog.w(TAG, "Window length " + windowLength
+                    + "ms suspiciously long; limiting to 1 hour");
+            windowLength = AlarmManager.INTERVAL_HOUR;
+        }
+
+        if (type < RTC_WAKEUP || type > ELAPSED_REALTIME) {
+            throw new IllegalArgumentException("Invalid alarm type " + type);
+        }
+
+        if (triggerAtTime < 0) {
+            final long who = Binder.getCallingUid();
+            final long what = Binder.getCallingPid();
+            Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + who
+                    + " pid=" + what);
+            triggerAtTime = 0;
+        }
+
+        final long nowElapsed = SystemClock.elapsedRealtime();
+        final long triggerElapsed = convertToElapsed(triggerAtTime, type);
+        final long maxElapsed;
+        if (windowLength == AlarmManager.WINDOW_EXACT) {
+            maxElapsed = triggerElapsed;
+        } else if (windowLength < 0) {
+            maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval);
+        } else {
+            maxElapsed = triggerElapsed + windowLength;
+        }
+
+        synchronized (mLock) {
+            if (DEBUG_BATCH) {
+                Slog.v(TAG, "set(" + operation + ") : type=" + type
+                        + " triggerAtTime=" + triggerAtTime + " win=" + windowLength
+                        + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed
+                        + " interval=" + interval + " standalone=" + isStandalone);
+            }
+            setImplLocked(type, triggerAtTime, triggerElapsed, windowLength, maxElapsed,
+                    interval, operation, isStandalone, true, workSource);
+        }
+    }
+
+    private void setImplLocked(int type, long when, long whenElapsed, long windowLength,
+            long maxWhen, long interval, PendingIntent operation, boolean isStandalone,
+            boolean doValidate, WorkSource workSource) {
+        Alarm a = new Alarm(type, when, whenElapsed, windowLength, maxWhen, interval,
+                operation, workSource);
+        removeLocked(operation);
+
+        int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen);
+        if (whichBatch < 0) {
+            Batch batch = new Batch(a);
+            batch.standalone = isStandalone;
+            addBatchLocked(mAlarmBatches, batch);
+        } else {
+            Batch batch = mAlarmBatches.get(whichBatch);
+            if (batch.add(a)) {
+                // The start time of this batch advanced, so batch ordering may
+                // have just been broken.  Move it to where it now belongs.
+                mAlarmBatches.remove(whichBatch);
+                addBatchLocked(mAlarmBatches, batch);
+            }
+        }
+
+        if (DEBUG_VALIDATE) {
+            if (doValidate && !validateConsistencyLocked()) {
+                Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when
+                        + " when(hex)=" + Long.toHexString(when)
+                        + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen
+                        + " interval=" + interval + " op=" + operation
+                        + " standalone=" + isStandalone);
+                rebatchAllAlarmsLocked(false);
+            }
+        }
+
+        rescheduleKernelAlarmsLocked();
+    }
+
+    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");
+            }
+
+            setImpl(type, triggerAtTime, windowLength, interval, operation,
+                    false, workSource);
+        }
+
+        @Override
+        public void setTime(long millis) {
+            getContext().enforceCallingOrSelfPermission(
+                    "android.permission.SET_TIME",
+                    "setTime");
+
+            SystemClock.setCurrentTimeMillis(millis);
+        }
+
+        @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);
+            }
+        }
+
+        @Override
+        public void remove(PendingIntent operation) {
+            removeImpl(operation);
+
+        }
+
+        @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;
+            }
+
+            dumpImpl(pw);
+        }
+    };
+
+    void dumpImpl(PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("Current Alarm Manager state:");
+            final long nowRTC = System.currentTimeMillis();
+            final long nowELAPSED = SystemClock.elapsedRealtime();
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+            pw.print("nowRTC="); pw.print(nowRTC);
+            pw.print("="); pw.print(sdf.format(new Date(nowRTC)));
+            pw.print(" nowELAPSED="); pw.println(nowELAPSED);
+
+            long nextWakeupRTC = mNextWakeup + (nowRTC - nowELAPSED);
+            long nextNonWakeupRTC = mNextNonWakeup + (nowRTC - nowELAPSED);
+            pw.print("Next alarm: "); pw.print(mNextNonWakeup);
+                    pw.print(" = "); pw.println(sdf.format(new Date(nextNonWakeupRTC)));
+            pw.print("Next wakeup: "); pw.print(mNextWakeup);
+                    pw.print(" = "); pw.println(sdf.format(new Date(nextWakeupRTC)));
+
+            if (mAlarmBatches.size() > 0) {
+                pw.println();
+                pw.print("Pending alarm batches: ");
+                pw.println(mAlarmBatches.size());
+                for (Batch b : mAlarmBatches) {
+                    pw.print(b); pw.println(':');
+                    dumpAlarmList(pw, b.alarms, "  ", nowELAPSED, nowRTC);
+                }
+            }
+
+            pw.println();
+            pw.print("  Broadcast ref count: "); pw.println(mBroadcastRefCount);
+            pw.println();
+
+            if (mLog.dump(pw, "  Recent problems", "    ")) {
+                pw.println();
+            }
+
+            final FilterStats[] topFilters = new FilterStats[10];
+            final Comparator<FilterStats> comparator = new Comparator<FilterStats>() {
+                @Override
+                public int compare(FilterStats lhs, FilterStats rhs) {
+                    if (lhs.aggregateTime < rhs.aggregateTime) {
+                        return 1;
+                    } else if (lhs.aggregateTime > rhs.aggregateTime) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            };
+            int len = 0;
+            for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) {
+                BroadcastStats bs = be.getValue();
+                for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe
+                        : bs.filterStats.entrySet()) {
+                    FilterStats fs = fe.getValue();
+                    int pos = len > 0
+                            ? Arrays.binarySearch(topFilters, 0, len, fs, comparator) : 0;
+                    if (pos < 0) {
+                        pos = -pos - 1;
+                    }
+                    if (pos < topFilters.length) {
+                        int copylen = topFilters.length - pos - 1;
+                        if (copylen > 0) {
+                            System.arraycopy(topFilters, pos, topFilters, pos+1, copylen);
+                        }
+                        topFilters[pos] = fs;
+                        if (len < topFilters.length) {
+                            len++;
+                        }
+                    }
+                }
+            }
+            if (len > 0) {
+                pw.println("  Top Alarms:");
+                for (int i=0; i<len; i++) {
+                    FilterStats fs = topFilters[i];
+                    pw.print("    ");
+                    if (fs.nesting > 0) pw.print("*ACTIVE* ");
+                    TimeUtils.formatDuration(fs.aggregateTime, pw);
+                    pw.print(" running, "); pw.print(fs.numWakeup);
+                    pw.print(" wakeups, "); pw.print(fs.count);
+                    pw.print(" alarms: "); pw.print(fs.mBroadcastStats.mPackageName);
+                    pw.println();
+                    pw.print("      ");
+                    if (fs.mTarget.first != null) {
+                        pw.print(" act="); pw.print(fs.mTarget.first);
+                    }
+                    if (fs.mTarget.second != null) {
+                        pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString());
+                    }
+                    pw.println();
+                }
+            }
+
+            pw.println(" ");
+            pw.println("  Alarm Stats:");
+            final ArrayList<FilterStats> tmpFilters = new ArrayList<FilterStats>();
+            for (Map.Entry<String, BroadcastStats> be : mBroadcastStats.entrySet()) {
+                BroadcastStats bs = be.getValue();
+                pw.print("  ");
+                if (bs.nesting > 0) pw.print("*ACTIVE* ");
+                pw.print(be.getKey());
+                pw.print(" "); TimeUtils.formatDuration(bs.aggregateTime, pw);
+                        pw.print(" running, "); pw.print(bs.numWakeup);
+                        pw.println(" wakeups:");
+                tmpFilters.clear();
+                for (Map.Entry<Pair<String, ComponentName>, FilterStats> fe
+                        : bs.filterStats.entrySet()) {
+                    tmpFilters.add(fe.getValue());
+                }
+                Collections.sort(tmpFilters, comparator);
+                for (int i=0; i<tmpFilters.size(); i++) {
+                    FilterStats fs = tmpFilters.get(i);
+                    pw.print("    ");
+                            if (fs.nesting > 0) pw.print("*ACTIVE* ");
+                            TimeUtils.formatDuration(fs.aggregateTime, pw);
+                            pw.print(" "); pw.print(fs.numWakeup);
+                            pw.print(" wakes " ); pw.print(fs.count);
+                            pw.print(" alarms:");
+                            if (fs.mTarget.first != null) {
+                                pw.print(" act="); pw.print(fs.mTarget.first);
+                            }
+                            if (fs.mTarget.second != null) {
+                                pw.print(" cmp="); pw.print(fs.mTarget.second.toShortString());
+                            }
+                            pw.println();
+                }
+            }
+
+            if (WAKEUP_STATS) {
+                pw.println();
+                pw.println("  Recent Wakeup History:");
+                long last = -1;
+                for (WakeupEvent event : mRecentWakeups) {
+                    pw.print("    "); pw.print(sdf.format(new Date(event.when)));
+                    pw.print('|');
+                    if (last < 0) {
+                        pw.print('0');
+                    } else {
+                        pw.print(event.when - last);
+                    }
+                    last = event.when;
+                    pw.print('|'); pw.print(event.uid);
+                    pw.print('|'); pw.print(event.action);
+                    pw.println();
+                }
+                pw.println();
+            }
+        }
+    }
+
+    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 (mNativeData != 0) {
+            // 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(mNativeData, 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--) {
+            Alarm a = list.get(i);
+            pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
+                    pw.print(": "); pw.println(a);
+            a.dump(pw, prefix + "  ", now);
+        }
+    }
+
+    private static final String labelForType(int type) {
+        switch (type) {
+        case RTC: return "RTC";
+        case RTC_WAKEUP : return "RTC_WAKEUP";
+        case ELAPSED_REALTIME : return "ELAPSED";
+        case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP";
+        default:
+            break;
+        }
+        return "--unknown--";
+    }
+
+    private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
+            String prefix, long nowELAPSED, long nowRTC) {
+        for (int i=list.size()-1; i>=0; i--) {
+            Alarm a = list.get(i);
+            final String label = labelForType(a.type);
+            long now = (a.type <= RTC) ? nowRTC : nowELAPSED;
+            pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
+                    pw.print(": "); pw.println(a);
+            a.dump(pw, prefix + "  ", now);
+        }
+    }
+
+    private native long init();
+    private native void close(long nativeData);
+    private native void set(long nativeData, int type, long seconds, long nanoseconds);
+    private native int waitForAlarm(long nativeData);
+    private native int setKernelTimezone(long nativeData, int minuteswest);
+
+    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
+        while (mAlarmBatches.size() > 0) {
+            Batch batch = mAlarmBatches.get(0);
+            if (batch.start > nowELAPSED) {
+                // Everything else is scheduled for the future
+                break;
+            }
+
+            // We will (re)schedule some alarms now; don't let that interfere
+            // with delivery of this current batch
+            mAlarmBatches.remove(0);
+
+            final int N = batch.size();
+            for (int i = 0; i < N; i++) {
+                Alarm alarm = batch.get(i);
+                alarm.count = 1;
+                triggerList.add(alarm);
+
+                // Recurring alarms may have passed several alarm intervals while the
+                // phone was asleep or off, so pass a trigger count when sending them.
+                if (alarm.repeatInterval > 0) {
+                    // this adjustment will be zero if we're late by
+                    // less than one full repeat interval
+                    alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval;
+
+                    // Also schedule its next recurrence
+                    final long delta = alarm.count * alarm.repeatInterval;
+                    final long nextElapsed = alarm.whenElapsed + delta;
+                    setImplLocked(alarm.type, alarm.when + delta, nextElapsed, alarm.windowLength,
+                            maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval),
+                            alarm.repeatInterval, alarm.operation, batch.standalone, true,
+                            alarm.workSource);
+                }
+
+            }
+        }
+    }
+
+    /**
+     * This Comparator sorts Alarms into increasing time order.
+     */
+    public static class IncreasingTimeOrder implements Comparator<Alarm> {
+        public int compare(Alarm a1, Alarm a2) {
+            long when1 = a1.when;
+            long when2 = a2.when;
+            if (when1 - when2 > 0) {
+                return 1;
+            }
+            if (when1 - when2 < 0) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+    
+    private static class Alarm {
+        public int type;
+        public int count;
+        public long when;
+        public long windowLength;
+        public long whenElapsed;    // 'when' in the elapsed time base
+        public long maxWhen;        // also in the elapsed time base
+        public long repeatInterval;
+        public PendingIntent operation;
+        public WorkSource workSource;
+        
+        public Alarm(int _type, long _when, long _whenElapsed, long _windowLength, long _maxWhen,
+                long _interval, PendingIntent _op, WorkSource _ws) {
+            type = _type;
+            when = _when;
+            whenElapsed = _whenElapsed;
+            windowLength = _windowLength;
+            maxWhen = _maxWhen;
+            repeatInterval = _interval;
+            operation = _op;
+            workSource = _ws;
+        }
+
+        @Override
+        public String toString()
+        {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Alarm{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" type ");
+            sb.append(type);
+            sb.append(" ");
+            sb.append(operation.getTargetPackage());
+            sb.append('}');
+            return sb.toString();
+        }
+
+        public void dump(PrintWriter pw, String prefix, long now) {
+            pw.print(prefix); pw.print("type="); pw.print(type);
+                    pw.print(" whenElapsed="); pw.print(whenElapsed);
+                    pw.print(" when="); TimeUtils.formatDuration(when, now, pw);
+                    pw.print(" window="); pw.print(windowLength);
+                    pw.print(" repeatInterval="); pw.print(repeatInterval);
+                    pw.print(" count="); pw.println(count);
+            pw.print(prefix); pw.print("operation="); pw.println(operation);
+        }
+    }
+
+    void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) {
+        final int numBatches = batches.size();
+        for (int nextBatch = 0; nextBatch < numBatches; nextBatch++) {
+            Batch b = batches.get(nextBatch);
+            if (b.start > nowELAPSED) {
+                break;
+            }
+
+            final int numAlarms = b.alarms.size();
+            for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) {
+                Alarm a = b.alarms.get(nextAlarm);
+                WakeupEvent e = new WakeupEvent(nowRTC,
+                        a.operation.getCreatorUid(),
+                        a.operation.getIntent().getAction());
+                mRecentWakeups.add(e);
+            }
+        }
+    }
+
+    private class AlarmThread extends Thread
+    {
+        public AlarmThread()
+        {
+            super("AlarmManager");
+        }
+        
+        public void run()
+        {
+            ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+
+            while (true)
+            {
+                int result = waitForAlarm(mNativeData);
+
+                triggerList.clear();
+
+                if ((result & TIME_CHANGED_MASK) != 0) {
+                    if (DEBUG_BATCH) {
+                        Slog.v(TAG, "Time changed notification from kernel; rebatching");
+                    }
+                    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);
+                    getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+                }
+                
+                synchronized (mLock) {
+                    final long nowRTC = System.currentTimeMillis();
+                    final long nowELAPSED = SystemClock.elapsedRealtime();
+                    if (localLOGV) Slog.v(
+                        TAG, "Checking for alarms... rtc=" + nowRTC
+                        + ", elapsed=" + nowELAPSED);
+
+                    if (WAKEUP_STATS) {
+                        if ((result & IS_WAKEUP_MASK) != 0) {
+                            long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD;
+                            int n = 0;
+                            for (WakeupEvent event : mRecentWakeups) {
+                                if (event.when > newEarliest) break;
+                                n++; // number of now-stale entries at the list head
+                            }
+                            for (int i = 0; i < n; i++) {
+                                mRecentWakeups.remove();
+                            }
+
+                            recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC);
+                        }
+                    }
+
+                    triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+                    rescheduleKernelAlarmsLocked();
+
+                    // now deliver the alarm intents
+                    for (int i=0; i<triggerList.size(); i++) {
+                        Alarm alarm = triggerList.get(i);
+                        try {
+                            if (localLOGV) Slog.v(TAG, "sending alarm " + alarm);
+                            alarm.operation.send(getContext(), 0,
+                                    mBackgroundIntent.putExtra(
+                                            Intent.EXTRA_ALARM_COUNT, alarm.count),
+                                    mResultReceiver, mHandler);
+                            
+                            // we have an active broadcast so stay awake.
+                            if (mBroadcastRefCount == 0) {
+                                setWakelockWorkSource(alarm.operation, alarm.workSource);
+                                mWakeLock.acquire();
+                            }
+                            final InFlight inflight = new InFlight(AlarmManagerService.this,
+                                    alarm.operation, alarm.workSource);
+                            mInFlight.add(inflight);
+                            mBroadcastRefCount++;
+
+                            final BroadcastStats bs = inflight.mBroadcastStats;
+                            bs.count++;
+                            if (bs.nesting == 0) {
+                                bs.nesting = 1;
+                                bs.startTime = nowELAPSED;
+                            } else {
+                                bs.nesting++;
+                            }
+                            final FilterStats fs = inflight.mFilterStats;
+                            fs.count++;
+                            if (fs.nesting == 0) {
+                                fs.nesting = 1;
+                                fs.startTime = nowELAPSED;
+                            } else {
+                                fs.nesting++;
+                            }
+                            if (alarm.type == ELAPSED_REALTIME_WAKEUP
+                                    || alarm.type == RTC_WAKEUP) {
+                                bs.numWakeup++;
+                                fs.numWakeup++;
+                                ActivityManagerNative.noteWakeupAlarm(
+                                        alarm.operation);
+                            }
+                        } catch (PendingIntent.CanceledException e) {
+                            if (alarm.repeatInterval > 0) {
+                                // This IntentSender is no longer valid, but this
+                                // is a repeating alarm, so toss the hoser.
+                                removeImpl(alarm.operation);
+                            }
+                        } catch (RuntimeException e) {
+                            Slog.w(TAG, "Failure sending alarm.", e);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Attribute blame for a WakeLock.
+     * @param pi PendingIntent to attribute blame to if ws is null.
+     * @param ws WorkSource to attribute blame.
+     */
+    void setWakelockWorkSource(PendingIntent pi, WorkSource ws) {
+        try {
+            if (ws != null) {
+                mWakeLock.setWorkSource(ws);
+                return;
+            }
+
+            final int uid = ActivityManagerNative.getDefault()
+                    .getUidForIntentSender(pi.getTarget());
+            if (uid >= 0) {
+                mWakeLock.setWorkSource(new WorkSource(uid));
+                return;
+            }
+        } catch (Exception e) {
+        }
+
+        // Something went wrong; fall back to attributing the lock to the OS
+        mWakeLock.setWorkSource(null);
+    }
+
+    private class AlarmHandler extends Handler {
+        public static final int ALARM_EVENT = 1;
+        public static final int MINUTE_CHANGE_EVENT = 2;
+        public static final int DATE_CHANGE_EVENT = 3;
+        
+        public AlarmHandler() {
+        }
+        
+        public void handleMessage(Message msg) {
+            if (msg.what == ALARM_EVENT) {
+                ArrayList<Alarm> triggerList = new ArrayList<Alarm>();
+                synchronized (mLock) {
+                    final long nowRTC = System.currentTimeMillis();
+                    final long nowELAPSED = SystemClock.elapsedRealtime();
+                    triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC);
+                }
+                
+                // now trigger the alarms without the lock held
+                for (int i=0; i<triggerList.size(); i++) {
+                    Alarm alarm = triggerList.get(i);
+                    try {
+                        alarm.operation.send();
+                    } catch (PendingIntent.CanceledException e) {
+                        if (alarm.repeatInterval > 0) {
+                            // This IntentSender is no longer valid, but this
+                            // is a repeating alarm, so toss the hoser.
+                            removeImpl(alarm.operation);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    
+    class ClockReceiver extends BroadcastReceiver {
+        public ClockReceiver() {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_TIME_TICK);
+            filter.addAction(Intent.ACTION_DATE_CHANGED);
+            getContext().registerReceiver(this, filter);
+        }
+        
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) {
+                if (DEBUG_BATCH) {
+                    Slog.v(TAG, "Received TIME_TICK alarm; rescheduling");
+                }
+                scheduleTimeTickEvent();
+            } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) {
+                // Since the kernel does not keep track of DST, we need to
+                // reset the TZ information at the beginning of each day
+                // based off of the current Zone gmt offset + userspace tracked
+                // daylight savings information.
+                TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY));
+                int gmtOffset = zone.getOffset(System.currentTimeMillis());
+                setKernelTimezone(mNativeData, -(gmtOffset / 60000));
+                scheduleDateChangedEvent();
+            }
+        }
+        
+        public void scheduleTimeTickEvent() {
+            final long currentTime = System.currentTimeMillis();
+            final long nextTime = 60000 * ((currentTime / 60000) + 1);
+
+            // Schedule this event for the amount of time that it would take to get to
+            // the top of the next minute.
+            final long tickEventDelay = nextTime - currentTime;
+
+            final WorkSource workSource = null; // Let system take blame for time tick events.
+            setImpl(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0,
+                    0, mTimeTickSender, true, workSource);
+        }
+
+        public void scheduleDateChangedEvent() {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTimeInMillis(System.currentTimeMillis());
+            calendar.set(Calendar.HOUR, 0);
+            calendar.set(Calendar.MINUTE, 0);
+            calendar.set(Calendar.SECOND, 0);
+            calendar.set(Calendar.MILLISECOND, 0);
+            calendar.add(Calendar.DAY_OF_MONTH, 1);
+
+            final WorkSource workSource = null; // Let system take blame for date change events.
+            setImpl(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource);
+        }
+    }
+    
+    class UninstallReceiver extends BroadcastReceiver {
+        public UninstallReceiver() {
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            filter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+            filter.addDataScheme("package");
+            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);
+            getContext().registerReceiver(this, sdFilter);
+        }
+        
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                String action = intent.getAction();
+                String pkgList[] = null;
+                if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) {
+                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                    for (String packageName : pkgList) {
+                        if (lookForPackageLocked(packageName)) {
+                            setResultCode(Activity.RESULT_OK);
+                            return;
+                        }
+                    }
+                    return;
+                } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
+                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+                    int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                    if (userHandle >= 0) {
+                        removeUserLocked(userHandle);
+                    }
+                } else {
+                    if (Intent.ACTION_PACKAGE_REMOVED.equals(action)
+                            && intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                        // This package is being updated; don't kill its alarms.
+                        return;
+                    }
+                    Uri data = intent.getData();
+                    if (data != null) {
+                        String pkg = data.getSchemeSpecificPart();
+                        if (pkg != null) {
+                            pkgList = new String[]{pkg};
+                        }
+                    }
+                }
+                if (pkgList != null && (pkgList.length > 0)) {
+                    for (String pkg : pkgList) {
+                        removeLocked(pkg);
+                        mBroadcastStats.remove(pkg);
+                    }
+                }
+            }
+        }
+    }
+    
+    private final BroadcastStats getStatsLocked(PendingIntent pi) {
+        String pkg = pi.getTargetPackage();
+        BroadcastStats bs = mBroadcastStats.get(pkg);
+        if (bs == null) {
+            bs = new BroadcastStats(pkg);
+            mBroadcastStats.put(pkg, bs);
+        }
+        return bs;
+    }
+
+    class ResultReceiver implements PendingIntent.OnFinished {
+        public void onSendFinished(PendingIntent pi, Intent intent, int resultCode,
+                String resultData, Bundle resultExtras) {
+            synchronized (mLock) {
+                InFlight inflight = null;
+                for (int i=0; i<mInFlight.size(); i++) {
+                    if (mInFlight.get(i).mPendingIntent == pi) {
+                        inflight = mInFlight.remove(i);
+                        break;
+                    }
+                }
+                if (inflight != null) {
+                    final long nowELAPSED = SystemClock.elapsedRealtime();
+                    BroadcastStats bs = inflight.mBroadcastStats;
+                    bs.nesting--;
+                    if (bs.nesting <= 0) {
+                        bs.nesting = 0;
+                        bs.aggregateTime += nowELAPSED - bs.startTime;
+                    }
+                    FilterStats fs = inflight.mFilterStats;
+                    fs.nesting--;
+                    if (fs.nesting <= 0) {
+                        fs.nesting = 0;
+                        fs.aggregateTime += nowELAPSED - fs.startTime;
+                    }
+                } else {
+                    mLog.w("No in-flight alarm for " + pi + " " + intent);
+                }
+                mBroadcastRefCount--;
+                if (mBroadcastRefCount == 0) {
+                    mWakeLock.release();
+                    if (mInFlight.size() > 0) {
+                        mLog.w("Finished all broadcasts with " + mInFlight.size()
+                                + " remaining inflights");
+                        for (int i=0; i<mInFlight.size(); i++) {
+                            mLog.w("  Remaining #" + i + ": " + mInFlight.get(i));
+                        }
+                        mInFlight.clear();
+                    }
+                } else {
+                    // the next of our alarms is now in flight.  reattribute the wakelock.
+                    if (mInFlight.size() > 0) {
+                        InFlight inFlight = mInFlight.get(0);
+                        setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource);
+                    } else {
+                        // should never happen
+                        mLog.w("Alarm wakelock still held but sent queue empty");
+                        mWakeLock.setWorkSource(null);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
new file mode 100644
index 0000000..a1a0d47
--- /dev/null
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -0,0 +1,1083 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+public class AppOpsService extends IAppOpsService.Stub {
+    static final String TAG = "AppOps";
+    static final boolean DEBUG = false;
+
+    // Write at most every 30 minutes.
+    static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
+
+    Context mContext;
+    final AtomicFile mFile;
+    final Handler mHandler;
+
+    boolean mWriteScheduled;
+    final Runnable mWriteRunner = new Runnable() {
+        public void run() {
+            synchronized (AppOpsService.this) {
+                mWriteScheduled = false;
+                AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+                    @Override protected Void doInBackground(Void... params) {
+                        writeState();
+                        return null;
+                    }
+                };
+                task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null);
+            }
+        }
+    };
+
+    final SparseArray<HashMap<String, Ops>> mUidOps
+            = new SparseArray<HashMap<String, Ops>>();
+
+    public final static class Ops extends SparseArray<Op> {
+        public final String packageName;
+        public final int uid;
+
+        public Ops(String _packageName, int _uid) {
+            packageName = _packageName;
+            uid = _uid;
+        }
+    }
+
+    public final static class Op {
+        public final int uid;
+        public final String packageName;
+        public final int op;
+        public int mode;
+        public int duration;
+        public long time;
+        public long rejectTime;
+        public int nesting;
+
+        public Op(int _uid, String _packageName, int _op) {
+            uid = _uid;
+            packageName = _packageName;
+            op = _op;
+            mode = AppOpsManager.opToDefaultMode(op);
+        }
+    }
+
+    final SparseArray<ArrayList<Callback>> mOpModeWatchers
+            = new SparseArray<ArrayList<Callback>>();
+    final ArrayMap<String, ArrayList<Callback>> mPackageModeWatchers
+            = new ArrayMap<String, ArrayList<Callback>>();
+    final ArrayMap<IBinder, Callback> mModeWatchers
+            = new ArrayMap<IBinder, Callback>();
+
+    public final class Callback implements DeathRecipient {
+        final IAppOpsCallback mCallback;
+
+        public Callback(IAppOpsCallback callback) {
+            mCallback = callback;
+            try {
+                mCallback.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void unlinkToDeath() {
+            mCallback.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            stopWatchingMode(mCallback);
+        }
+    }
+
+    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<IBinder, ClientState>();
+
+    public final class ClientState extends Binder implements DeathRecipient {
+        final IBinder mAppToken;
+        final int mPid;
+        final ArrayList<Op> mStartedOps;
+
+        public ClientState(IBinder appToken) {
+            mAppToken = appToken;
+            mPid = Binder.getCallingPid();
+            if (appToken instanceof Binder) {
+                // For local clients, there is no reason to track them.
+                mStartedOps = null;
+            } else {
+                mStartedOps = new ArrayList<Op>();
+                try {
+                    mAppToken.linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "ClientState{" +
+                    "mAppToken=" + mAppToken +
+                    ", " + (mStartedOps != null ? ("pid=" + mPid) : "local") +
+                    '}';
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (AppOpsService.this) {
+                for (int i=mStartedOps.size()-1; i>=0; i--) {
+                    finishOperationLocked(mStartedOps.get(i));
+                }
+                mClients.remove(mAppToken);
+            }
+        }
+    }
+
+    public AppOpsService(File storagePath) {
+        mFile = new AtomicFile(storagePath);
+        mHandler = new Handler();
+        readState();
+    }
+
+    public void publish(Context context) {
+        mContext = context;
+        ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder());
+    }
+
+    public void systemReady() {
+        synchronized (this) {
+            boolean changed = false;
+            for (int i=0; i<mUidOps.size(); i++) {
+                HashMap<String, Ops> pkgs = mUidOps.valueAt(i);
+                Iterator<Ops> it = pkgs.values().iterator();
+                while (it.hasNext()) {
+                    Ops ops = it.next();
+                    int curUid;
+                    try {
+                        curUid = mContext.getPackageManager().getPackageUid(ops.packageName,
+                                UserHandle.getUserId(ops.uid));
+                    } catch (NameNotFoundException e) {
+                        curUid = -1;
+                    }
+                    if (curUid != ops.uid) {
+                        Slog.i(TAG, "Pruning old package " + ops.packageName
+                                + "/" + ops.uid + ": new uid=" + curUid);
+                        it.remove();
+                        changed = true;
+                    }
+                }
+                if (pkgs.size() <= 0) {
+                    mUidOps.removeAt(i);
+                }
+            }
+            if (changed) {
+                scheduleWriteLocked();
+            }
+        }
+    }
+
+    public void packageRemoved(int uid, String packageName) {
+        synchronized (this) {
+            HashMap<String, Ops> pkgs = mUidOps.get(uid);
+            if (pkgs != null) {
+                if (pkgs.remove(packageName) != null) {
+                    if (pkgs.size() <= 0) {
+                        mUidOps.remove(uid);
+                    }
+                    scheduleWriteLocked();
+                }
+            }
+        }
+    }
+
+    public void uidRemoved(int uid) {
+        synchronized (this) {
+            if (mUidOps.indexOfKey(uid) >= 0) {
+                mUidOps.remove(uid);
+                scheduleWriteLocked();
+            }
+        }
+    }
+
+    public void shutdown() {
+        Slog.w(TAG, "Writing app ops before shutdown...");
+        boolean doWrite = false;
+        synchronized (this) {
+            if (mWriteScheduled) {
+                mWriteScheduled = false;
+                doWrite = true;
+            }
+        }
+        if (doWrite) {
+            writeState();
+        }
+    }
+
+    private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
+        ArrayList<AppOpsManager.OpEntry> resOps = null;
+        if (ops == null) {
+            resOps = new ArrayList<AppOpsManager.OpEntry>();
+            for (int j=0; j<pkgOps.size(); j++) {
+                Op curOp = pkgOps.valueAt(j);
+                resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+                        curOp.rejectTime, curOp.duration));
+            }
+        } else {
+            for (int j=0; j<ops.length; j++) {
+                Op curOp = pkgOps.get(ops[j]);
+                if (curOp != null) {
+                    if (resOps == null) {
+                        resOps = new ArrayList<AppOpsManager.OpEntry>();
+                    }
+                    resOps.add(new AppOpsManager.OpEntry(curOp.op, curOp.mode, curOp.time,
+                            curOp.rejectTime, curOp.duration));
+                }
+            }
+        }
+        return resOps;
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        ArrayList<AppOpsManager.PackageOps> res = null;
+        synchronized (this) {
+            for (int i=0; i<mUidOps.size(); i++) {
+                HashMap<String, Ops> packages = mUidOps.valueAt(i);
+                for (Ops pkgOps : packages.values()) {
+                    ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+                    if (resOps != null) {
+                        if (res == null) {
+                            res = new ArrayList<AppOpsManager.PackageOps>();
+                        }
+                        AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                                pkgOps.packageName, pkgOps.uid, resOps);
+                        res.add(resPackage);
+                    }
+                }
+            }
+        }
+        return res;
+    }
+
+    @Override
+    public List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName,
+            int[] ops) {
+        mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        synchronized (this) {
+            Ops pkgOps = getOpsLocked(uid, packageName, false);
+            if (pkgOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.OpEntry> resOps = collectOps(pkgOps, ops);
+            if (resOps == null) {
+                return null;
+            }
+            ArrayList<AppOpsManager.PackageOps> res = new ArrayList<AppOpsManager.PackageOps>();
+            AppOpsManager.PackageOps resPackage = new AppOpsManager.PackageOps(
+                    pkgOps.packageName, pkgOps.uid, resOps);
+            res.add(resPackage);
+            return res;
+        }
+    }
+
+    private void pruneOp(Op op, int uid, String packageName) {
+        if (op.time == 0 && op.rejectTime == 0) {
+            Ops ops = getOpsLocked(uid, packageName, false);
+            if (ops != null) {
+                ops.remove(op.op);
+                if (ops.size() <= 0) {
+                    HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+                    if (pkgOps != null) {
+                        pkgOps.remove(ops.packageName);
+                        if (pkgOps.size() <= 0) {
+                            mUidOps.remove(uid);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setMode(int code, int uid, String packageName, int mode) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        ArrayList<Callback> repCbs = null;
+        code = AppOpsManager.opToSwitch(code);
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, true);
+            if (op != null) {
+                if (op.mode != mode) {
+                    op.mode = mode;
+                    ArrayList<Callback> cbs = mOpModeWatchers.get(code);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArrayList<Callback>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    cbs = mPackageModeWatchers.get(packageName);
+                    if (cbs != null) {
+                        if (repCbs == null) {
+                            repCbs = new ArrayList<Callback>();
+                        }
+                        repCbs.addAll(cbs);
+                    }
+                    if (mode == AppOpsManager.opToDefaultMode(op.op)) {
+                        // If going into the default mode, prune this op
+                        // if there is nothing else interesting in it.
+                        pruneOp(op, uid, packageName);
+                    }
+                    scheduleWriteNowLocked();
+                }
+            }
+        }
+        if (repCbs != null) {
+            for (int i=0; i<repCbs.size(); i++) {
+                try {
+                    repCbs.get(i).mCallback.opChanged(code, packageName);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    private static HashMap<Callback, ArrayList<Pair<String, Integer>>> addCallbacks(
+            HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks,
+            String packageName, int op, ArrayList<Callback> cbs) {
+        if (cbs == null) {
+            return callbacks;
+        }
+        if (callbacks == null) {
+            callbacks = new HashMap<Callback, ArrayList<Pair<String, Integer>>>();
+        }
+        for (int i=0; i<cbs.size(); i++) {
+            Callback cb = cbs.get(i);
+            ArrayList<Pair<String, Integer>> reports = callbacks.get(cb);
+            if (reports == null) {
+                reports = new ArrayList<Pair<String, Integer>>();
+                callbacks.put(cb, reports);
+            }
+            reports.add(new Pair<String, Integer>(packageName, op));
+        }
+        return callbacks;
+    }
+
+    @Override
+    public void resetAllModes() {
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+        HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
+        synchronized (this) {
+            boolean changed = false;
+            for (int i=mUidOps.size()-1; i>=0; i--) {
+                HashMap<String, Ops> packages = mUidOps.valueAt(i);
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
+                    String packageName = ent.getKey();
+                    Ops pkgOps = ent.getValue();
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
+                        Op curOp = pkgOps.valueAt(j);
+                        if (AppOpsManager.opAllowsReset(curOp.op)
+                                && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) {
+                            curOp.mode = AppOpsManager.opToDefaultMode(curOp.op);
+                            changed = true;
+                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                                    mOpModeWatchers.get(curOp.op));
+                            callbacks = addCallbacks(callbacks, packageName, curOp.op,
+                                    mPackageModeWatchers.get(packageName));
+                            if (curOp.time == 0 && curOp.rejectTime == 0) {
+                                pkgOps.removeAt(j);
+                            }
+                        }
+                    }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                    }
+                }
+                if (packages.size() == 0) {
+                    mUidOps.removeAt(i);
+                }
+            }
+            if (changed) {
+                scheduleWriteNowLocked();
+            }
+        }
+        if (callbacks != null) {
+            for (Map.Entry<Callback, ArrayList<Pair<String, Integer>>> ent : callbacks.entrySet()) {
+                Callback cb = ent.getKey();
+                ArrayList<Pair<String, Integer>> reports = ent.getValue();
+                for (int i=0; i<reports.size(); i++) {
+                    Pair<String, Integer> rep = reports.get(i);
+                    try {
+                        cb.mCallback.opChanged(rep.second, rep.first);
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void startWatchingMode(int op, String packageName, IAppOpsCallback callback) {
+        synchronized (this) {
+            op = AppOpsManager.opToSwitch(op);
+            Callback cb = mModeWatchers.get(callback.asBinder());
+            if (cb == null) {
+                cb = new Callback(callback);
+                mModeWatchers.put(callback.asBinder(), cb);
+            }
+            if (op != AppOpsManager.OP_NONE) {
+                ArrayList<Callback> cbs = mOpModeWatchers.get(op);
+                if (cbs == null) {
+                    cbs = new ArrayList<Callback>();
+                    mOpModeWatchers.put(op, cbs);
+                }
+                cbs.add(cb);
+            }
+            if (packageName != null) {
+                ArrayList<Callback> cbs = mPackageModeWatchers.get(packageName);
+                if (cbs == null) {
+                    cbs = new ArrayList<Callback>();
+                    mPackageModeWatchers.put(packageName, cbs);
+                }
+                cbs.add(cb);
+            }
+        }
+    }
+
+    @Override
+    public void stopWatchingMode(IAppOpsCallback callback) {
+        synchronized (this) {
+            Callback cb = mModeWatchers.remove(callback.asBinder());
+            if (cb != null) {
+                cb.unlinkToDeath();
+                for (int i=mOpModeWatchers.size()-1; i>=0; i--) {
+                    ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i);
+                    cbs.remove(cb);
+                    if (cbs.size() <= 0) {
+                        mOpModeWatchers.removeAt(i);
+                    }
+                }
+                for (int i=mPackageModeWatchers.size()-1; i>=0; i--) {
+                    ArrayList<Callback> cbs = mPackageModeWatchers.valueAt(i);
+                    cbs.remove(cb);
+                    if (cbs.size() <= 0) {
+                        mPackageModeWatchers.removeAt(i);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public IBinder getToken(IBinder clientToken) {
+        synchronized (this) {
+            ClientState cs = mClients.get(clientToken);
+            if (cs == null) {
+                cs = new ClientState(clientToken);
+                mClients.put(clientToken, cs);
+            }
+            return cs;
+        }
+    }
+
+    @Override
+    public int checkOperation(int code, int uid, String packageName) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        synchronized (this) {
+            Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false);
+            if (op == null) {
+                return AppOpsManager.opToDefaultMode(code);
+            }
+            return op.mode;
+        }
+    }
+
+    @Override
+    public int checkPackage(int uid, String packageName) {
+        synchronized (this) {
+            if (getOpsLocked(uid, packageName, true) != null) {
+                return AppOpsManager.MODE_ALLOWED;
+            } else {
+                return AppOpsManager.MODE_ERRORED;
+            }
+        }
+    }
+
+    @Override
+    public int noteOperation(int code, int uid, String packageName) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        synchronized (this) {
+            Ops ops = getOpsLocked(uid, packageName, true);
+            if (ops == null) {
+                if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName);
+                return AppOpsManager.MODE_ERRORED;
+            }
+            Op op = getOpLocked(ops, code, true);
+            if (op.duration == -1) {
+                Slog.w(TAG, "Noting op not finished: uid " + uid + " pkg " + packageName
+                        + " code " + code + " time=" + op.time + " duration=" + op.duration);
+            }
+            op.duration = 0;
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+            if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
+                if (DEBUG) Log.d(TAG, "noteOperation: reject #" + op.mode + " for code "
+                        + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+                op.rejectTime = System.currentTimeMillis();
+                return switchOp.mode;
+            }
+            if (DEBUG) Log.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+                    + " package " + packageName);
+            op.time = System.currentTimeMillis();
+            op.rejectTime = 0;
+            return AppOpsManager.MODE_ALLOWED;
+        }
+    }
+
+    @Override
+    public int startOperation(IBinder token, int code, int uid, String packageName) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        ClientState client = (ClientState)token;
+        synchronized (this) {
+            Ops ops = getOpsLocked(uid, packageName, true);
+            if (ops == null) {
+                if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid
+                        + " package " + packageName);
+                return AppOpsManager.MODE_ERRORED;
+            }
+            Op op = getOpLocked(ops, code, true);
+            final int switchCode = AppOpsManager.opToSwitch(code);
+            final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
+            if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
+                if (DEBUG) Log.d(TAG, "startOperation: reject #" + op.mode + " for code "
+                        + switchCode + " (" + code + ") uid " + uid + " package " + packageName);
+                op.rejectTime = System.currentTimeMillis();
+                return switchOp.mode;
+            }
+            if (DEBUG) Log.d(TAG, "startOperation: allowing code " + code + " uid " + uid
+                    + " package " + packageName);
+            if (op.nesting == 0) {
+                op.time = System.currentTimeMillis();
+                op.rejectTime = 0;
+                op.duration = -1;
+            }
+            op.nesting++;
+            if (client.mStartedOps != null) {
+                client.mStartedOps.add(op);
+            }
+            return AppOpsManager.MODE_ALLOWED;
+        }
+    }
+
+    @Override
+    public void finishOperation(IBinder token, int code, int uid, String packageName) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        ClientState client = (ClientState)token;
+        synchronized (this) {
+            Op op = getOpLocked(code, uid, packageName, true);
+            if (op == null) {
+                return;
+            }
+            if (client.mStartedOps != null) {
+                if (!client.mStartedOps.remove(op)) {
+                    throw new IllegalStateException("Operation not started: uid" + op.uid
+                            + " pkg=" + op.packageName + " op=" + op.op);
+                }
+            }
+            finishOperationLocked(op);
+        }
+    }
+
+    void finishOperationLocked(Op op) {
+        if (op.nesting <= 1) {
+            if (op.nesting == 1) {
+                op.duration = (int)(System.currentTimeMillis() - op.time);
+                op.time += op.duration;
+            } else {
+                Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg "
+                        + op.packageName + " code " + op.op + " time=" + op.time
+                        + " duration=" + op.duration + " nesting=" + op.nesting);
+            }
+            op.nesting = 0;
+        } else {
+            op.nesting--;
+        }
+    }
+
+    private void verifyIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    private void verifyIncomingOp(int op) {
+        if (op >= 0 && op < AppOpsManager._NUM_OP) {
+            return;
+        }
+        throw new IllegalArgumentException("Bad operation #" + op);
+    }
+
+    private Ops getOpsLocked(int uid, String packageName, boolean edit) {
+        HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+        if (pkgOps == null) {
+            if (!edit) {
+                return null;
+            }
+            pkgOps = new HashMap<String, Ops>();
+            mUidOps.put(uid, pkgOps);
+        }
+        if (uid == 0) {
+            packageName = "root";
+        } else if (uid == Process.SHELL_UID) {
+            packageName = "com.android.shell";
+        }
+        Ops ops = pkgOps.get(packageName);
+        if (ops == null) {
+            if (!edit) {
+                return null;
+            }
+            // This is the first time we have seen this package name under this uid,
+            // so let's make sure it is valid.
+            if (uid != 0) {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    int pkgUid = -1;
+                    try {
+                        pkgUid = mContext.getPackageManager().getPackageUid(packageName,
+                                UserHandle.getUserId(uid));
+                    } catch (NameNotFoundException e) {
+                        if ("media".equals(packageName)) {
+                            pkgUid = Process.MEDIA_UID;
+                        }
+                    }
+                    if (pkgUid != uid) {
+                        // Oops!  The package name is not valid for the uid they are calling
+                        // under.  Abort.
+                        Slog.w(TAG, "Bad call: specified package " + packageName
+                                + " under uid " + uid + " but it is really " + pkgUid);
+                        return null;
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+            ops = new Ops(packageName, uid);
+            pkgOps.put(packageName, ops);
+        }
+        return ops;
+    }
+
+    private void scheduleWriteLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+            mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
+        }
+    }
+
+    private void scheduleWriteNowLocked() {
+        if (!mWriteScheduled) {
+            mWriteScheduled = true;
+        }
+        mHandler.removeCallbacks(mWriteRunner);
+        mHandler.post(mWriteRunner);
+    }
+
+    private Op getOpLocked(int code, int uid, String packageName, boolean edit) {
+        Ops ops = getOpsLocked(uid, packageName, edit);
+        if (ops == null) {
+            return null;
+        }
+        return getOpLocked(ops, code, edit);
+    }
+
+    private Op getOpLocked(Ops ops, int code, boolean edit) {
+        Op op = ops.get(code);
+        if (op == null) {
+            if (!edit) {
+                return null;
+            }
+            op = new Op(ops.uid, ops.packageName, code);
+            ops.put(code, op);
+        }
+        if (edit) {
+            scheduleWriteLocked();
+        }
+        return op;
+    }
+
+    void readState() {
+        synchronized (mFile) {
+            synchronized (this) {
+                FileInputStream stream;
+                try {
+                    stream = mFile.openRead();
+                } catch (FileNotFoundException e) {
+                    Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
+                    return;
+                }
+                boolean success = false;
+                try {
+                    XmlPullParser parser = Xml.newPullParser();
+                    parser.setInput(stream, null);
+                    int type;
+                    while ((type = parser.next()) != XmlPullParser.START_TAG
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        ;
+                    }
+
+                    if (type != XmlPullParser.START_TAG) {
+                        throw new IllegalStateException("no start tag found");
+                    }
+
+                    int outerDepth = parser.getDepth();
+                    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+
+                        String tagName = parser.getName();
+                        if (tagName.equals("pkg")) {
+                            readPackage(parser);
+                        } else {
+                            Slog.w(TAG, "Unknown element under <app-ops>: "
+                                    + parser.getName());
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    }
+                    success = true;
+                } catch (IllegalStateException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NullPointerException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (XmlPullParserException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IOException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } catch (IndexOutOfBoundsException e) {
+                    Slog.w(TAG, "Failed parsing " + e);
+                } finally {
+                    if (!success) {
+                        mUidOps.clear();
+                    }
+                    try {
+                        stream.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    void readPackage(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        String pkgName = parser.getAttributeValue(null, "n");
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("uid")) {
+                readUid(parser, pkgName);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    void readUid(XmlPullParser parser, String pkgName) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        int uid = Integer.parseInt(parser.getAttributeValue(null, "n"));
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("op")) {
+                Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n")));
+                String mode = parser.getAttributeValue(null, "m");
+                if (mode != null) {
+                    op.mode = Integer.parseInt(mode);
+                }
+                String time = parser.getAttributeValue(null, "t");
+                if (time != null) {
+                    op.time = Long.parseLong(time);
+                }
+                time = parser.getAttributeValue(null, "r");
+                if (time != null) {
+                    op.rejectTime = Long.parseLong(time);
+                }
+                String dur = parser.getAttributeValue(null, "d");
+                if (dur != null) {
+                    op.duration = Integer.parseInt(dur);
+                }
+                HashMap<String, Ops> pkgOps = mUidOps.get(uid);
+                if (pkgOps == null) {
+                    pkgOps = new HashMap<String, Ops>();
+                    mUidOps.put(uid, pkgOps);
+                }
+                Ops ops = pkgOps.get(pkgName);
+                if (ops == null) {
+                    ops = new Ops(pkgName, uid);
+                    pkgOps.put(pkgName, ops);
+                }
+                ops.put(op.op, op);
+            } else {
+                Slog.w(TAG, "Unknown element under <pkg>: "
+                        + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    void writeState() {
+        synchronized (mFile) {
+            List<AppOpsManager.PackageOps> allOps = getPackagesForOps(null);
+
+            FileOutputStream stream;
+            try {
+                stream = mFile.startWrite();
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state: " + e);
+                return;
+            }
+
+            try {
+                XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(stream, "utf-8");
+                out.startDocument(null, true);
+                out.startTag(null, "app-ops");
+
+                if (allOps != null) {
+                    String lastPkg = null;
+                    for (int i=0; i<allOps.size(); i++) {
+                        AppOpsManager.PackageOps pkg = allOps.get(i);
+                        if (!pkg.getPackageName().equals(lastPkg)) {
+                            if (lastPkg != null) {
+                                out.endTag(null, "pkg");
+                            }
+                            lastPkg = pkg.getPackageName();
+                            out.startTag(null, "pkg");
+                            out.attribute(null, "n", lastPkg);
+                        }
+                        out.startTag(null, "uid");
+                        out.attribute(null, "n", Integer.toString(pkg.getUid()));
+                        List<AppOpsManager.OpEntry> ops = pkg.getOps();
+                        for (int j=0; j<ops.size(); j++) {
+                            AppOpsManager.OpEntry op = ops.get(j);
+                            out.startTag(null, "op");
+                            out.attribute(null, "n", Integer.toString(op.getOp()));
+                            if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) {
+                                out.attribute(null, "m", Integer.toString(op.getMode()));
+                            }
+                            long time = op.getTime();
+                            if (time != 0) {
+                                out.attribute(null, "t", Long.toString(time));
+                            }
+                            time = op.getRejectTime();
+                            if (time != 0) {
+                                out.attribute(null, "r", Long.toString(time));
+                            }
+                            int dur = op.getDuration();
+                            if (dur != 0) {
+                                out.attribute(null, "d", Integer.toString(dur));
+                            }
+                            out.endTag(null, "op");
+                        }
+                        out.endTag(null, "uid");
+                    }
+                    if (lastPkg != null) {
+                        out.endTag(null, "pkg");
+                    }
+                }
+
+                out.endTag(null, "app-ops");
+                out.endDocument();
+                mFile.finishWrite(stream);
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed to write state, restoring backup.", e);
+                mFile.failWrite(stream);
+            }
+        }
+    }
+
+    @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 ApOps service from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (this) {
+            pw.println("Current AppOps Service state:");
+            final long now = System.currentTimeMillis();
+            boolean needSep = false;
+            if (mOpModeWatchers.size() > 0) {
+                needSep = true;
+                pw.println("  Op mode watchers:");
+                for (int i=0; i<mOpModeWatchers.size(); i++) {
+                    pw.print("    Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
+                    pw.println(":");
+                    ArrayList<Callback> callbacks = mOpModeWatchers.valueAt(i);
+                    for (int j=0; j<callbacks.size(); j++) {
+                        pw.print("      #"); pw.print(j); pw.print(": ");
+                        pw.println(callbacks.get(j));
+                    }
+                }
+            }
+            if (mPackageModeWatchers.size() > 0) {
+                needSep = true;
+                pw.println("  Package mode watchers:");
+                for (int i=0; i<mPackageModeWatchers.size(); i++) {
+                    pw.print("    Pkg "); pw.print(mPackageModeWatchers.keyAt(i));
+                    pw.println(":");
+                    ArrayList<Callback> callbacks = mPackageModeWatchers.valueAt(i);
+                    for (int j=0; j<callbacks.size(); j++) {
+                        pw.print("      #"); pw.print(j); pw.print(": ");
+                        pw.println(callbacks.get(j));
+                    }
+                }
+            }
+            if (mModeWatchers.size() > 0) {
+                needSep = true;
+                pw.println("  All mode watchers:");
+                for (int i=0; i<mModeWatchers.size(); i++) {
+                    pw.print("    "); pw.print(mModeWatchers.keyAt(i));
+                    pw.print(" -> "); pw.println(mModeWatchers.valueAt(i));
+                }
+            }
+            if (mClients.size() > 0) {
+                needSep = true;
+                pw.println("  Clients:");
+                for (int i=0; i<mClients.size(); i++) {
+                    pw.print("    "); pw.print(mClients.keyAt(i)); pw.println(":");
+                    ClientState cs = mClients.valueAt(i);
+                    pw.print("      "); pw.println(cs);
+                    if (cs.mStartedOps != null && cs.mStartedOps.size() > 0) {
+                        pw.println("      Started ops:");
+                        for (int j=0; j<cs.mStartedOps.size(); j++) {
+                            Op op = cs.mStartedOps.get(j);
+                            pw.print("        "); pw.print("uid="); pw.print(op.uid);
+                            pw.print(" pkg="); pw.print(op.packageName);
+                            pw.print(" op="); pw.println(AppOpsManager.opToName(op.op));
+                        }
+                    }
+                }
+            }
+            if (needSep) {
+                pw.println();
+            }
+            for (int i=0; i<mUidOps.size(); i++) {
+                pw.print("  Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":");
+                HashMap<String, Ops> pkgOps = mUidOps.valueAt(i);
+                for (Ops ops : pkgOps.values()) {
+                    pw.print("    Package "); pw.print(ops.packageName); pw.println(":");
+                    for (int j=0; j<ops.size(); j++) {
+                        Op op = ops.valueAt(j);
+                        pw.print("      "); pw.print(AppOpsManager.opToName(op.op));
+                        pw.print(": mode="); pw.print(op.mode);
+                        if (op.time != 0) {
+                            pw.print("; time="); TimeUtils.formatDuration(now-op.time, pw);
+                            pw.print(" ago");
+                        }
+                        if (op.rejectTime != 0) {
+                            pw.print("; rejectTime="); TimeUtils.formatDuration(now-op.rejectTime, pw);
+                            pw.print(" ago");
+                        }
+                        if (op.duration == -1) {
+                            pw.println(" (running)");
+                        } else {
+                            pw.print("; duration=");
+                                    TimeUtils.formatDuration(op.duration, pw);
+                                    pw.println();
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/AssetAtlasService.java b/services/core/java/com/android/server/AssetAtlasService.java
new file mode 100644
index 0000000..26b4652
--- /dev/null
+++ b/services/core/java/com/android/server/AssetAtlasService.java
@@ -0,0 +1,735 @@
+/*
+ * 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.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Atlas;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.view.GraphicBuffer;
+import android.view.IAssetAtlas;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This service is responsible for packing preloaded bitmaps into a single
+ * atlas texture. The resulting texture can be shared across processes to
+ * reduce overall memory usage.
+ *
+ * @hide
+ */
+public class AssetAtlasService extends IAssetAtlas.Stub {
+    /**
+     * Name of the <code>AssetAtlasService</code>.
+     */
+    public static final String ASSET_ATLAS_SERVICE = "assetatlas";
+
+    private static final String LOG_TAG = "Atlas";
+
+    // Turns debug logs on/off. Debug logs are kept to a minimum and should
+    // remain on to diagnose issues
+    private static final boolean DEBUG_ATLAS = true;
+
+    // When set to true the content of the atlas will be saved to disk
+    // in /data/system/atlas.png. The shared GraphicBuffer may be empty
+    private static final boolean DEBUG_ATLAS_TEXTURE = false;
+
+    // Minimum size in pixels to consider for the resulting texture
+    private static final int MIN_SIZE = 768;
+    // Maximum size in pixels to consider for the resulting texture
+    private static final int MAX_SIZE = 2048;
+    // Increment in number of pixels between size variants when looking
+    // for the best texture dimensions
+    private static final int STEP = 64;
+
+    // This percentage of the total number of pixels represents the minimum
+    // number of pixels we want to be able to pack in the atlas
+    private static final float PACKING_THRESHOLD = 0.8f;
+
+    // Defines the number of int fields used to represent a single entry
+    // in the atlas map. This number defines the size of the array returned
+    // by the getMap(). See the mAtlasMap field for more information
+    private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4;
+
+    // Specifies how our GraphicBuffer will be used. To get proper swizzling
+    // the buffer will be written to using OpenGL (from JNI) so we can leave
+    // the software flag set to "never"
+    private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER |
+            GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE;
+
+    // This boolean is set to true if an atlas was successfully
+    // computed and rendered
+    private final AtomicBoolean mAtlasReady = new AtomicBoolean(false);
+
+    private final Context mContext;
+
+    // Version name of the current build, used to identify changes to assets list
+    private final String mVersionName;
+
+    // Holds the atlas' data. This buffer can be mapped to
+    // OpenGL using an EGLImage
+    private GraphicBuffer mBuffer;
+
+    // Describes how bitmaps are placed in the atlas. Each bitmap is
+    // represented by several entries in the array:
+    // int0: SkBitmap*, the native bitmap object
+    // int1: x position
+    // int2: y position
+    // int3: rotated, 1 if the bitmap must be rotated, 0 otherwise
+    // NOTE: This will need to be handled differently to support 64 bit pointers
+    private int[] mAtlasMap;
+
+    /**
+     * Creates a new service. Upon creating, the service will gather the list of
+     * assets to consider for packing into the atlas and spawn a new thread to
+     * start the packing work.
+     *
+     * @param context The context giving access to preloaded resources
+     */
+    public AssetAtlasService(Context context) {
+        mContext = context;
+        mVersionName = queryVersionName(context);
+
+        ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(300);
+        int totalPixelCount = 0;
+
+        // We only care about drawables that hold bitmaps
+        final Resources resources = context.getResources();
+        final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables();
+
+        final int count = drawables.size();
+        for (int i = 0; i < count; i++) {
+            final Bitmap bitmap = drawables.valueAt(i).getBitmap();
+            if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) {
+                bitmaps.add(bitmap);
+                totalPixelCount += bitmap.getWidth() * bitmap.getHeight();
+            }
+        }
+
+        // Our algorithms perform better when the bitmaps are first sorted
+        // The comparator will sort the bitmap by width first, then by height
+        Collections.sort(bitmaps, new Comparator<Bitmap>() {
+            @Override
+            public int compare(Bitmap b1, Bitmap b2) {
+                if (b1.getWidth() == b2.getWidth()) {
+                    return b2.getHeight() - b1.getHeight();
+                }
+                return b2.getWidth() - b1.getWidth();
+            }
+        });
+
+        // Kick off the packing work on a worker thread
+        new Thread(new Renderer(bitmaps, totalPixelCount)).start();
+    }
+
+    /**
+     * Queries the version name stored in framework's AndroidManifest.
+     * The version name can be used to identify possible changes to
+     * framework resources.
+     *
+     * @see #getBuildIdentifier(String)
+     */
+    private static String queryVersionName(Context context) {
+        try {
+            String packageName = context.getPackageName();
+            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(LOG_TAG, "Could not get package info", e);
+        }
+        return null;
+    }
+
+    /**
+     * Callback invoked by the server thread to indicate we can now run
+     * 3rd party code.
+     */
+    public void systemRunning() {
+    }
+
+    /**
+     * The renderer does all the work:
+     */
+    private class Renderer implements Runnable {
+        private final ArrayList<Bitmap> mBitmaps;
+        private final int mPixelCount;
+
+        private int mNativeBitmap;
+
+        // Used for debugging only
+        private Bitmap mAtlasBitmap;
+
+        Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) {
+            mBitmaps = bitmaps;
+            mPixelCount = pixelCount;
+        }
+
+        /**
+         * 1. On first boot or after every update, brute-force through all the
+         *    possible atlas configurations and look for the best one (maximimize
+         *    number of packed assets and minimize texture size)
+         *    a. If a best configuration was computed, write it out to disk for
+         *       future use
+         * 2. Read best configuration from disk
+         * 3. Compute the packing using the best configuration
+         * 4. Allocate a GraphicBuffer
+         * 5. Render assets in the buffer
+         */
+        @Override
+        public void run() {
+            Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName);
+            if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config);
+
+            if (config != null) {
+                mBuffer = GraphicBuffer.create(config.width, config.height,
+                        PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE);
+
+                if (mBuffer != null) {
+                    Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags);
+                    if (renderAtlas(mBuffer, atlas, config.count)) {
+                        mAtlasReady.set(true);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Renders a list of bitmaps into the atlas. The position of each bitmap
+         * was decided by the packing algorithm and will be honored by this
+         * method. If need be this method will also rotate bitmaps.
+         *
+         * @param buffer The buffer to render the atlas entries into
+         * @param atlas The atlas to pack the bitmaps into
+         * @param packCount The number of bitmaps that will be packed in the atlas
+         *
+         * @return true if the atlas was rendered, false otherwise
+         */
+        @SuppressWarnings("MismatchedReadAndWriteOfArray")
+        private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) {
+            // Use a Source blend mode to improve performance, the target bitmap
+            // will be zero'd out so there's no need to waste time applying blending
+            final Paint paint = new Paint();
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+            // We always render the atlas into a bitmap. This bitmap is then
+            // uploaded into the GraphicBuffer using OpenGL to swizzle the content
+            final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight());
+            if (canvas == null) return false;
+
+            final Atlas.Entry entry = new Atlas.Entry();
+
+            mAtlasMap = new int[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT];
+            int[] atlasMap = mAtlasMap;
+            int mapIndex = 0;
+
+            boolean result = false;
+            try {
+                final long startRender = System.nanoTime();
+                final int count = mBitmaps.size();
+
+                for (int i = 0; i < count; i++) {
+                    final Bitmap bitmap = mBitmaps.get(i);
+                    if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
+                        // We have more bitmaps to pack than the current configuration
+                        // says, we were most likely not able to detect a change in the
+                        // list of preloaded drawables, abort and delete the configuration
+                        if (mapIndex >= mAtlasMap.length) {
+                            deleteDataFile();
+                            break;
+                        }
+
+                        canvas.save();
+                        canvas.translate(entry.x, entry.y);
+                        if (entry.rotated) {
+                            canvas.translate(bitmap.getHeight(), 0.0f);
+                            canvas.rotate(90.0f);
+                        }
+                        canvas.drawBitmap(bitmap, 0.0f, 0.0f, null);
+                        canvas.restore();
+
+                        atlasMap[mapIndex++] = bitmap.mNativeBitmap;
+                        atlasMap[mapIndex++] = entry.x;
+                        atlasMap[mapIndex++] = entry.y;
+                        atlasMap[mapIndex++] = entry.rotated ? 1 : 0;
+                    }
+                }
+
+                final long endRender = System.nanoTime();
+                if (mNativeBitmap != 0) {
+                    result = nUploadAtlas(buffer, mNativeBitmap);
+                }
+
+                final long endUpload = System.nanoTime();
+                if (DEBUG_ATLAS) {
+                    float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f;
+                    float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f;
+                    Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)",
+                            renderDuration + uploadDuration, renderDuration, uploadDuration));
+                }
+
+            } finally {
+                releaseCanvas(canvas);
+            }
+
+            return result;
+        }
+
+        /**
+         * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE}
+         * is turned on, the returned Canvas will render into a local bitmap that
+         * will then be saved out to disk for debugging purposes.
+         * @param width
+         * @param height
+         */
+        private Canvas acquireCanvas(int width, int height) {
+            if (DEBUG_ATLAS_TEXTURE) {
+                mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                return new Canvas(mAtlasBitmap);
+            } else {
+                Canvas canvas = new Canvas();
+                mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height);
+                return canvas;
+            }
+        }
+
+        /**
+         * Releases the canvas used to render into the buffer. Calling this method
+         * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE}
+         * is turend on, calling this method will write the content of the atlas
+         * to disk in /data/system/atlas.png for debugging.
+         */
+        private void releaseCanvas(Canvas canvas) {
+            if (DEBUG_ATLAS_TEXTURE) {
+                canvas.setBitmap(null);
+
+                File systemDirectory = new File(Environment.getDataDirectory(), "system");
+                File dataFile = new File(systemDirectory, "atlas.png");
+
+                try {
+                    FileOutputStream out = new FileOutputStream(dataFile);
+                    mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+                    out.close();
+                } catch (FileNotFoundException e) {
+                    // Ignore
+                } catch (IOException e) {
+                    // Ignore
+                }
+
+                mAtlasBitmap.recycle();
+                mAtlasBitmap = null;
+            } else {
+                nReleaseAtlasCanvas(canvas, mNativeBitmap);
+            }
+        }
+    }
+
+    private static native int nAcquireAtlasCanvas(Canvas canvas, int width, int height);
+    private static native void nReleaseAtlasCanvas(Canvas canvas, int bitmap);
+    private static native boolean nUploadAtlas(GraphicBuffer buffer, int bitmap);
+
+    @Override
+    public boolean isCompatible(int ppid) {
+        return ppid == android.os.Process.myPpid();
+    }
+
+    @Override
+    public GraphicBuffer getBuffer() throws RemoteException {
+        return mAtlasReady.get() ? mBuffer : null;
+    }
+
+    @Override
+    public int[] getMap() throws RemoteException {
+        return mAtlasReady.get() ? mAtlasMap : null;
+    }
+
+    /**
+     * Finds the best atlas configuration to pack the list of supplied bitmaps.
+     * This method takes advantage of multi-core systems by spawning a number
+     * of threads equal to the number of available cores.
+     */
+    private static Configuration computeBestConfiguration(
+            ArrayList<Bitmap> bitmaps, int pixelCount) {
+        if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration...");
+
+        long begin = System.nanoTime();
+        List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>());
+
+        // Don't bother with an extra thread if there's only one processor
+        int cpuCount = Runtime.getRuntime().availableProcessors();
+        if (cpuCount == 1) {
+            new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run();
+        } else {
+            int start = MIN_SIZE;
+            int end = MAX_SIZE - (cpuCount - 1) * STEP;
+            int step = STEP * cpuCount;
+
+            final CountDownLatch signal = new CountDownLatch(cpuCount);
+
+            for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) {
+                ComputeWorker worker = new ComputeWorker(start, end, step,
+                        bitmaps, pixelCount, results, signal);
+                new Thread(worker, "Atlas Worker #" + (i + 1)).start();
+            }
+
+            try {
+                signal.await(10, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                Log.w(LOG_TAG, "Could not complete configuration computation");
+                return null;
+            }
+        }
+
+        // Maximize the number of packed bitmaps, minimize the texture size
+        Collections.sort(results, new Comparator<WorkerResult>() {
+            @Override
+            public int compare(WorkerResult r1, WorkerResult r2) {
+                int delta = r2.count - r1.count;
+                if (delta != 0) return delta;
+                return r1.width * r1.height - r2.width * r2.height;
+            }
+        });
+
+        if (DEBUG_ATLAS) {
+            float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f;
+            Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay));
+        }
+
+        WorkerResult result = results.get(0);
+        return new Configuration(result.type, result.width, result.height, result.count);
+    }
+
+    /**
+     * Returns the path to the file containing the best computed
+     * atlas configuration.
+     */
+    private static File getDataFile() {
+        File systemDirectory = new File(Environment.getDataDirectory(), "system");
+        return new File(systemDirectory, "framework_atlas.config");
+    }
+
+    private static void deleteDataFile() {
+        Log.w(LOG_TAG, "Current configuration inconsistent with assets list");
+        if (!getDataFile().delete()) {
+            Log.w(LOG_TAG, "Could not delete the current configuration");
+        }
+    }
+
+    private File getFrameworkResourcesFile() {
+        return new File(mContext.getApplicationInfo().sourceDir);
+    }
+
+    /**
+     * Returns the best known atlas configuration. This method will either
+     * read the configuration from disk or start a brute-force search
+     * and save the result out to disk.
+     */
+    private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount,
+            String versionName) {
+        Configuration config = null;
+
+        final File dataFile = getDataFile();
+        if (dataFile.exists()) {
+            config = readConfiguration(dataFile, versionName);
+        }
+
+        if (config == null) {
+            config = computeBestConfiguration(bitmaps, pixelCount);
+            if (config != null) writeConfiguration(config, dataFile, versionName);
+        }
+
+        return config;
+    }
+
+    /**
+     * Writes the specified atlas configuration to the specified file.
+     */
+    private void writeConfiguration(Configuration config, File file, String versionName) {
+        BufferedWriter writer = null;
+        try {
+            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
+            writer.write(getBuildIdentifier(versionName));
+            writer.newLine();
+            writer.write(config.type.toString());
+            writer.newLine();
+            writer.write(String.valueOf(config.width));
+            writer.newLine();
+            writer.write(String.valueOf(config.height));
+            writer.newLine();
+            writer.write(String.valueOf(config.count));
+            writer.newLine();
+            writer.write(String.valueOf(config.flags));
+            writer.newLine();
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "Could not write " + file, e);
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Could not write " + file, e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+    }
+
+    /**
+     * Reads an atlas configuration from the specified file. This method
+     * returns null if an error occurs or if the configuration is invalid.
+     */
+    private Configuration readConfiguration(File file, String versionName) {
+        BufferedReader reader = null;
+        Configuration config = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));
+
+            if (checkBuildIdentifier(reader, versionName)) {
+                Atlas.Type type = Atlas.Type.valueOf(reader.readLine());
+                int width = readInt(reader, MIN_SIZE, MAX_SIZE);
+                int height = readInt(reader, MIN_SIZE, MAX_SIZE);
+                int count = readInt(reader, 0, Integer.MAX_VALUE);
+                int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE);
+
+                config = new Configuration(type, width, height, count, flags);
+            }
+        } catch (IllegalArgumentException e) {
+            Log.w(LOG_TAG, "Invalid parameter value in " + file, e);
+        } catch (FileNotFoundException e) {
+            Log.w(LOG_TAG, "Could not read " + file, e);
+        } catch (IOException e) {
+            Log.w(LOG_TAG, "Could not read " + file, e);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+        return config;
+    }
+
+    private static int readInt(BufferedReader reader, int min, int max) throws IOException {
+        return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine())));
+    }
+
+    /**
+     * Compares the next line in the specified buffered reader to the current
+     * build identifier. Returns whether the two values are equal.
+     *
+     * @see #getBuildIdentifier(String)
+     */
+    private boolean checkBuildIdentifier(BufferedReader reader, String versionName)
+            throws IOException {
+        String deviceBuildId = getBuildIdentifier(versionName);
+        String buildId = reader.readLine();
+        return deviceBuildId.equals(buildId);
+    }
+
+    /**
+     * Returns an identifier for the current build that can be used to detect
+     * likely changes to framework resources. The build identifier is made of
+     * several distinct values:
+     *
+     * build fingerprint/framework version name/file size of framework resources apk
+     *
+     * Only the build fingerprint should be necessary on user builds but
+     * the other values are useful to detect changes on eng builds during
+     * development.
+     *
+     * This identifier does not attempt to be exact: a new identifier does not
+     * necessarily mean the preloaded drawables have changed. It is important
+     * however that whenever the list of preloaded drawables changes, this
+     * identifier changes as well.
+     *
+     * @see #checkBuildIdentifier(java.io.BufferedReader, String)
+     */
+    private String getBuildIdentifier(String versionName) {
+        return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' +
+                String.valueOf(getFrameworkResourcesFile().length());
+    }
+
+    /**
+     * Atlas configuration. Specifies the algorithm, dimensions and flags to use.
+     */
+    private static class Configuration {
+        final Atlas.Type type;
+        final int width;
+        final int height;
+        final int count;
+        final int flags;
+
+        Configuration(Atlas.Type type, int width, int height, int count) {
+            this(type, width, height, count, Atlas.FLAG_DEFAULTS);
+        }
+
+        Configuration(Atlas.Type type, int width, int height, int count, int flags) {
+            this.type = type;
+            this.width = width;
+            this.height = height;
+            this.count = count;
+            this.flags = flags;
+        }
+
+        @Override
+        public String toString() {
+            return type.toString() + " (" + width + "x" + height + ") flags=0x" +
+                    Integer.toHexString(flags) + " count=" + count;
+        }
+    }
+
+    /**
+     * Used during the brute-force search to gather information about each
+     * variant of the packing algorithm.
+     */
+    private static class WorkerResult {
+        Atlas.Type type;
+        int width;
+        int height;
+        int count;
+
+        WorkerResult(Atlas.Type type, int width, int height, int count) {
+            this.type = type;
+            this.width = width;
+            this.height = height;
+            this.count = count;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s %dx%d", type.toString(), width, height);
+        }
+    }
+
+    /**
+     * A compute worker will try a finite number of variations of the packing
+     * algorithms and save the results in a supplied list.
+     */
+    private static class ComputeWorker implements Runnable {
+        private final int mStart;
+        private final int mEnd;
+        private final int mStep;
+        private final List<Bitmap> mBitmaps;
+        private final List<WorkerResult> mResults;
+        private final CountDownLatch mSignal;
+        private final int mThreshold;
+
+        /**
+         * Creates a new compute worker to brute-force through a range of
+         * packing algorithms variants.
+         *
+         * @param start The minimum texture width to try
+         * @param end The maximum texture width to try
+         * @param step The number of pixels to increment the texture width by at each step
+         * @param bitmaps The list of bitmaps to pack in the atlas
+         * @param pixelCount The total number of pixels occupied by the list of bitmaps
+         * @param results The list of results in which to save the brute-force search results
+         * @param signal Latch to decrement when this worker is done, may be null
+         */
+        ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount,
+                List<WorkerResult> results, CountDownLatch signal) {
+            mStart = start;
+            mEnd = end;
+            mStep = step;
+            mBitmaps = bitmaps;
+            mResults = results;
+            mSignal = signal;
+
+            // Minimum number of pixels we want to be able to pack
+            int threshold = (int) (pixelCount * PACKING_THRESHOLD);
+            // Make sure we can find at least one configuration
+            while (threshold > MAX_SIZE * MAX_SIZE) {
+                threshold >>= 1;
+            }
+            mThreshold = threshold;
+        }
+
+        @Override
+        public void run() {
+            if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName());
+
+            Atlas.Entry entry = new Atlas.Entry();
+            for (Atlas.Type type : Atlas.Type.values()) {
+                for (int width = mStart; width < mEnd; width += mStep) {
+                    for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) {
+                        // If the atlas is not big enough, skip it
+                        if (width * height <= mThreshold) continue;
+
+                        final int count = packBitmaps(type, width, height, entry);
+                        if (count > 0) {
+                            mResults.add(new WorkerResult(type, width, height, count));
+                            // If we were able to pack everything let's stop here
+                            // Increasing the height further won't make things better
+                            if (count == mBitmaps.size()) {
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (mSignal != null) {
+                mSignal.countDown();
+            }
+        }
+
+        private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) {
+            int total = 0;
+            Atlas atlas = new Atlas(type, width, height);
+
+            final int count = mBitmaps.size();
+            for (int i = 0; i < count; i++) {
+                final Bitmap bitmap = mBitmaps.get(i);
+                if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) {
+                    total++;
+                }
+            }
+
+            return total;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/AttributeCache.java b/services/core/java/com/android/server/AttributeCache.java
new file mode 100644
index 0000000..427dbc0
--- /dev/null
+++ b/services/core/java/com/android/server/AttributeCache.java
@@ -0,0 +1,143 @@
+/*
+**
+** Copyright 2007, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License"); 
+** you may not use this file except in compliance with the License. 
+** You may obtain a copy of the License at 
+**
+**     http://www.apache.org/licenses/LICENSE-2.0 
+**
+** Unless required by applicable law or agreed to in writing, software 
+** distributed under the License is distributed on an "AS IS" BASIS, 
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
+** See the License for the specific language governing permissions and 
+** limitations under the License.
+*/
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import java.util.HashMap;
+import java.util.WeakHashMap;
+
+/**
+ * TODO: This should be better integrated into the system so it doesn't need
+ * special calls from the activity manager to clear it.
+ */
+public final class AttributeCache {
+    private static AttributeCache sInstance = null;
+    
+    private final Context mContext;
+    private final WeakHashMap<String, Package> mPackages =
+            new WeakHashMap<String, Package>();
+    private final Configuration mConfiguration = new Configuration();
+    
+    public final static class Package {
+        public final Context context;
+        private final SparseArray<HashMap<int[], Entry>> mMap
+                = new SparseArray<HashMap<int[], Entry>>();
+        
+        public Package(Context c) {
+            context = c;
+        }
+    }
+    
+    public final static class Entry {
+        public final Context context;
+        public final TypedArray array;
+        
+        public Entry(Context c, TypedArray ta) {
+            context = c;
+            array = ta;
+        }
+    }
+    
+    public static void init(Context context) {
+        if (sInstance == null) {
+            sInstance = new AttributeCache(context);
+        }
+    }
+    
+    public static AttributeCache instance() {
+        return sInstance;
+    }
+    
+    public AttributeCache(Context context) {
+        mContext = context;
+    }
+    
+    public void removePackage(String packageName) {
+        synchronized (this) {
+            mPackages.remove(packageName);
+        }
+    }
+    
+    public void updateConfiguration(Configuration config) {
+        synchronized (this) {
+            int changes = mConfiguration.updateFrom(config);
+            if ((changes & ~(ActivityInfo.CONFIG_FONT_SCALE |
+                    ActivityInfo.CONFIG_KEYBOARD_HIDDEN |
+                    ActivityInfo.CONFIG_ORIENTATION)) != 0) {
+                // The configurations being masked out are ones that commonly
+                // change so we don't want flushing the cache... all others
+                // will flush the cache.
+                mPackages.clear();
+            }
+        }
+    }
+    
+    public Entry get(String packageName, int resId, int[] styleable, int userId) {
+        synchronized (this) {
+            Package pkg = mPackages.get(packageName);
+            HashMap<int[], Entry> map = null;
+            Entry ent = null;
+            if (pkg != null) {
+                map = pkg.mMap.get(resId);
+                if (map != null) {
+                    ent = map.get(styleable);
+                    if (ent != null) {
+                        return ent;
+                    }
+                }
+            } else {
+                Context context;
+                try {
+                    context = mContext.createPackageContextAsUser(packageName, 0,
+                            new UserHandle(userId));
+                    if (context == null) {
+                        return null;
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    return null;
+                }
+                pkg = new Package(context);
+                mPackages.put(packageName, pkg);
+            }
+            
+            if (map == null) {
+                map = new HashMap<int[], Entry>();
+                pkg.mMap.put(resId, map);
+            }
+            
+            try {
+                ent = new Entry(pkg.context,
+                        pkg.context.obtainStyledAttributes(resId, styleable));
+                map.put(styleable, ent);
+            } catch (Resources.NotFoundException e) {
+                return null;
+            }
+            
+            return ent;
+        }
+    }
+}
+
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
new file mode 100644
index 0000000..cc9055d
--- /dev/null
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -0,0 +1,753 @@
+/*
+ * Copyright (C) 2006 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.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;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.BatteryManager;
+import android.os.BatteryProperties;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBatteryPropertiesListener;
+import android.os.IBatteryPropertiesRegistrar;
+import android.os.IBinder;
+import android.os.DropBoxManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+/**
+ * <p>BatteryService monitors the charging status, and charge level of the device
+ * battery.  When these values change this service broadcasts the new values
+ * to all {@link android.content.BroadcastReceiver IntentReceivers} that are
+ * watching the {@link android.content.Intent#ACTION_BATTERY_CHANGED
+ * BATTERY_CHANGED} action.</p>
+ * <p>The new values are stored in the Intent data and can be retrieved by
+ * calling {@link android.content.Intent#getExtra Intent.getExtra} with the
+ * following keys:</p>
+ * <p>&quot;scale&quot; - int, the maximum value for the charge level</p>
+ * <p>&quot;level&quot; - int, charge level, from 0 through &quot;scale&quot; inclusive</p>
+ * <p>&quot;status&quot; - String, the current charging status.<br />
+ * <p>&quot;health&quot; - String, the current battery health.<br />
+ * <p>&quot;present&quot; - boolean, true if the battery is present<br />
+ * <p>&quot;icon-small&quot; - int, suggested small icon to use for this state</p>
+ * <p>&quot;plugged&quot; - int, 0 if the device is not plugged in; 1 if plugged
+ * into an AC power adapter; 2 if plugged in via USB.</p>
+ * <p>&quot;voltage&quot; - int, current battery voltage in millivolts</p>
+ * <p>&quot;temperature&quot; - int, current battery temperature in tenths of
+ * a degree Centigrade</p>
+ * <p>&quot;technology&quot; - String, the type of battery installed, e.g. "Li-ion"</p>
+ *
+ * <p>
+ * The battery service may be called by the power manager while holding its locks so
+ * we take care to post all outcalls into the activity manager to a handler.
+ *
+ * FIXME: Ideally the power manager would perform all of its calls into the battery
+ * service asynchronously itself.
+ * </p>
+ */
+public final class BatteryService extends Binder {
+    private static final String TAG = BatteryService.class.getSimpleName();
+
+    private static final boolean DEBUG = false;
+
+    private static final int BATTERY_SCALE = 100;    // battery capacity is a percentage
+
+    // Used locally for determining when to make a last ditch effort to log
+    // discharge stats before the device dies.
+    private int mCriticalBatteryLevel;
+
+    private static final int DUMP_MAX_LENGTH = 24 * 1024;
+    private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" };
+
+    private static final String DUMPSYS_DATA_PATH = "/data/system/";
+
+    // This should probably be exposed in the API, though it's not critical
+    private static final int BATTERY_PLUGGED_NONE = 0;
+
+    private final Context mContext;
+    private final IBatteryStats mBatteryStats;
+    private final Handler mHandler;
+
+    private final Object mLock = new Object();
+
+    private BatteryProperties mBatteryProps;
+    private boolean mBatteryLevelCritical;
+    private int mLastBatteryStatus;
+    private int mLastBatteryHealth;
+    private boolean mLastBatteryPresent;
+    private int mLastBatteryLevel;
+    private int mLastBatteryVoltage;
+    private int mLastBatteryTemperature;
+    private boolean mLastBatteryLevelCritical;
+
+    private int mInvalidCharger;
+    private int mLastInvalidCharger;
+
+    private int mLowBatteryWarningLevel;
+    private int mLowBatteryCloseWarningLevel;
+    private int mShutdownBatteryTemperature;
+
+    private int mPlugType;
+    private int mLastPlugType = -1; // Extra state so we can detect first run
+
+    private long mDischargeStartTime;
+    private int mDischargeStartLevel;
+
+    private boolean mUpdatesStopped;
+
+    private Led mLed;
+
+    private boolean mSentLowBatteryBroadcast = false;
+
+    public BatteryService(Context context, LightsManager lightsManager) {
+        mContext = context;
+        mHandler = new Handler(true /*async*/);
+        mLed = new Led(context, lightsManager);
+        mBatteryStats = BatteryStatsService.getService();
+
+        mCriticalBatteryLevel = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_criticalBatteryWarningLevel);
+        mLowBatteryWarningLevel = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_lowBatteryWarningLevel);
+        mLowBatteryCloseWarningLevel = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_lowBatteryCloseWarningLevel);
+        mShutdownBatteryTemperature = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_shutdownBatteryTemperature);
+
+        // watch for invalid charger messages if the invalid_charger switch exists
+        if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) {
+            mInvalidChargerObserver.startObserving(
+                    "DEVPATH=/devices/virtual/switch/invalid_charger");
+        }
+
+        IBinder b = ServiceManager.getService("batterypropreg");
+        final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
+                IBatteryPropertiesRegistrar.Stub.asInterface(b);
+        try {
+            batteryPropertiesRegistrar.registerListener(new BatteryListener());
+        } catch (RemoteException e) {
+            // Should never happen.
+        }
+    }
+
+    void systemReady() {
+        // check our power situation now that it is safe to display the shutdown dialog.
+        synchronized (mLock) {
+            shutdownIfNoPowerLocked();
+            shutdownIfOverTempLocked();
+        }
+    }
+
+    /**
+     * Returns true if the device is plugged into any of the specified plug types.
+     */
+    public boolean isPowered(int plugTypeSet) {
+        synchronized (mLock) {
+            return isPoweredLocked(plugTypeSet);
+        }
+    }
+
+    private boolean isPoweredLocked(int plugTypeSet) {
+        // assume we are powered if battery state is unknown so
+        // the "stay on while plugged in" option will work.
+        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
+            return true;
+        }
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) {
+            return true;
+        }
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) {
+            return true;
+        }
+        if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns the current plug type.
+     */
+    public int getPlugType() {
+        synchronized (mLock) {
+            return mPlugType;
+        }
+    }
+
+    /**
+     * Returns battery level as a percentage.
+     */
+    public int getBatteryLevel() {
+        synchronized (mLock) {
+            return mBatteryProps.batteryLevel;
+        }
+    }
+
+    /**
+     * Returns true if battery level is below the first warning threshold.
+     */
+    public boolean isBatteryLow() {
+        synchronized (mLock) {
+            return mBatteryProps.batteryPresent && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel;
+        }
+    }
+
+    /**
+     * Returns a non-zero value if an  unsupported charger is attached.
+     */
+    public int getInvalidCharger() {
+        synchronized (mLock) {
+            return mInvalidCharger;
+        }
+    }
+
+    private void shutdownIfNoPowerLocked() {
+        // shut down gracefully if our battery is critically low and we are not powered.
+        // wait until the system has booted before attempting to display the shutdown dialog.
+        if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (ActivityManagerNative.isSystemReady()) {
+                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
+                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                    }
+                }
+            });
+        }
+    }
+
+    private void shutdownIfOverTempLocked() {
+        // shut down gracefully if temperature is too high (> 68.0C by default)
+        // wait until the system has booted before attempting to display the
+        // shutdown dialog.
+        if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (ActivityManagerNative.isSystemReady()) {
+                        Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
+                        intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+                    }
+                }
+            });
+        }
+    }
+
+    private void update(BatteryProperties props) {
+        synchronized (mLock) {
+            if (!mUpdatesStopped) {
+                mBatteryProps = props;
+                // Process the new values.
+                processValuesLocked();
+            }
+        }
+    }
+
+    private void processValuesLocked() {
+        boolean logOutlier = false;
+        long dischargeDuration = 0;
+
+        mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel);
+        if (mBatteryProps.chargerAcOnline) {
+            mPlugType = BatteryManager.BATTERY_PLUGGED_AC;
+        } else if (mBatteryProps.chargerUsbOnline) {
+            mPlugType = BatteryManager.BATTERY_PLUGGED_USB;
+        } else if (mBatteryProps.chargerWirelessOnline) {
+            mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        } else {
+            mPlugType = BATTERY_PLUGGED_NONE;
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "Processing new values: "
+                    + "chargerAcOnline=" + mBatteryProps.chargerAcOnline
+                    + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline
+                    + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline
+                    + ", batteryStatus=" + mBatteryProps.batteryStatus
+                    + ", batteryHealth=" + mBatteryProps.batteryHealth
+                    + ", batteryPresent=" + mBatteryProps.batteryPresent
+                    + ", batteryLevel=" + mBatteryProps.batteryLevel
+                    + ", batteryTechnology=" + mBatteryProps.batteryTechnology
+                    + ", batteryVoltage=" + mBatteryProps.batteryVoltage
+                    + ", batteryCurrentNow=" + mBatteryProps.batteryCurrentNow
+                    + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter
+                    + ", batteryTemperature=" + mBatteryProps.batteryTemperature
+                    + ", mBatteryLevelCritical=" + mBatteryLevelCritical
+                    + ", mPlugType=" + mPlugType);
+        }
+
+        // Let the battery stats keep track of the current level.
+        try {
+            mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth,
+                    mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature,
+                    mBatteryProps.batteryVoltage);
+        } catch (RemoteException e) {
+            // Should never happen.
+        }
+
+        shutdownIfNoPowerLocked();
+        shutdownIfOverTempLocked();
+
+        if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
+                mBatteryProps.batteryHealth != mLastBatteryHealth ||
+                mBatteryProps.batteryPresent != mLastBatteryPresent ||
+                mBatteryProps.batteryLevel != mLastBatteryLevel ||
+                mPlugType != mLastPlugType ||
+                mBatteryProps.batteryVoltage != mLastBatteryVoltage ||
+                mBatteryProps.batteryTemperature != mLastBatteryTemperature ||
+                mInvalidCharger != mLastInvalidCharger) {
+
+            if (mPlugType != mLastPlugType) {
+                if (mLastPlugType == BATTERY_PLUGGED_NONE) {
+                    // discharging -> charging
+
+                    // There's no value in this data unless we've discharged at least once and the
+                    // battery level has changed; so don't log until it does.
+                    if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) {
+                        dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
+                        logOutlier = true;
+                        EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration,
+                                mDischargeStartLevel, mBatteryProps.batteryLevel);
+                        // make sure we see a discharge event before logging again
+                        mDischargeStartTime = 0;
+                    }
+                } else if (mPlugType == BATTERY_PLUGGED_NONE) {
+                    // charging -> discharging or we just powered up
+                    mDischargeStartTime = SystemClock.elapsedRealtime();
+                    mDischargeStartLevel = mBatteryProps.batteryLevel;
+                }
+            }
+            if (mBatteryProps.batteryStatus != mLastBatteryStatus ||
+                    mBatteryProps.batteryHealth != mLastBatteryHealth ||
+                    mBatteryProps.batteryPresent != mLastBatteryPresent ||
+                    mPlugType != mLastPlugType) {
+                EventLog.writeEvent(EventLogTags.BATTERY_STATUS,
+                        mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0,
+                        mPlugType, mBatteryProps.batteryTechnology);
+            }
+            if (mBatteryProps.batteryLevel != mLastBatteryLevel) {
+                // Don't do this just from voltage or temperature changes, that is
+                // too noisy.
+                EventLog.writeEvent(EventLogTags.BATTERY_LEVEL,
+                        mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature);
+            }
+            if (mBatteryLevelCritical && !mLastBatteryLevelCritical &&
+                    mPlugType == BATTERY_PLUGGED_NONE) {
+                // We want to make sure we log discharge cycle outliers
+                // if the battery is about to die.
+                dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime;
+                logOutlier = true;
+            }
+
+            final boolean plugged = mPlugType != BATTERY_PLUGGED_NONE;
+            final boolean oldPlugged = mLastPlugType != BATTERY_PLUGGED_NONE;
+
+            /* The ACTION_BATTERY_LOW broadcast is sent in these situations:
+             * - is just un-plugged (previously was plugged) and battery level is
+             *   less than or equal to WARNING, or
+             * - is not plugged and battery level falls to WARNING boundary
+             *   (becomes <= mLowBatteryWarningLevel).
+             */
+            final boolean sendBatteryLow = !plugged
+                    && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
+                    && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel
+                    && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
+
+            sendIntentLocked();
+
+            // Separate broadcast is sent for power connected / not connected
+            // since the standard intent will not wake any applications and some
+            // applications may want to have smart behavior based on this.
+            if (mPlugType != 0 && mLastPlugType == 0) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent statusIntent = new Intent(Intent.ACTION_POWER_CONNECTED);
+                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                    }
+                });
+            }
+            else if (mPlugType == 0 && mLastPlugType != 0) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent statusIntent = new Intent(Intent.ACTION_POWER_DISCONNECTED);
+                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                    }
+                });
+            }
+
+            if (sendBatteryLow) {
+                mSentLowBatteryBroadcast = true;
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_LOW);
+                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                    }
+                });
+            } else if (mSentLowBatteryBroadcast && mLastBatteryLevel >= mLowBatteryCloseWarningLevel) {
+                mSentLowBatteryBroadcast = false;
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        Intent statusIntent = new Intent(Intent.ACTION_BATTERY_OKAY);
+                        statusIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        mContext.sendBroadcastAsUser(statusIntent, UserHandle.ALL);
+                    }
+                });
+            }
+
+            // Update the battery LED
+            mLed.updateLightsLocked();
+
+            // This needs to be done after sendIntent() so that we get the lastest battery stats.
+            if (logOutlier && dischargeDuration != 0) {
+                logOutlierLocked(dischargeDuration);
+            }
+
+            mLastBatteryStatus = mBatteryProps.batteryStatus;
+            mLastBatteryHealth = mBatteryProps.batteryHealth;
+            mLastBatteryPresent = mBatteryProps.batteryPresent;
+            mLastBatteryLevel = mBatteryProps.batteryLevel;
+            mLastPlugType = mPlugType;
+            mLastBatteryVoltage = mBatteryProps.batteryVoltage;
+            mLastBatteryTemperature = mBatteryProps.batteryTemperature;
+            mLastBatteryLevelCritical = mBatteryLevelCritical;
+            mLastInvalidCharger = mInvalidCharger;
+        }
+    }
+
+    private void sendIntentLocked() {
+        //  Pack up the values and broadcast them to everyone
+        final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+        int icon = getIconLocked(mBatteryProps.batteryLevel);
+
+        intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus);
+        intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth);
+        intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent);
+        intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
+        intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
+        intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
+        intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage);
+        intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature);
+        intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology);
+        intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger);
+
+        if (DEBUG) {
+            Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED.  level:" + mBatteryProps.batteryLevel +
+                    ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus +
+                    ", health:" + mBatteryProps.batteryHealth +  ", present:" + mBatteryProps.batteryPresent +
+                    ", voltage: " + mBatteryProps.batteryVoltage +
+                    ", temperature: " + mBatteryProps.batteryTemperature +
+                    ", technology: " + mBatteryProps.batteryTechnology +
+                    ", AC powered:" + mBatteryProps.chargerAcOnline + ", USB powered:" + mBatteryProps.chargerUsbOnline +
+                    ", Wireless powered:" + mBatteryProps.chargerWirelessOnline +
+                    ", icon:" + icon  + ", invalid charger:" + mInvalidCharger);
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);
+            }
+        });
+    }
+
+    private void logBatteryStatsLocked() {
+        IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME);
+        if (batteryInfoService == null) return;
+
+        DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE);
+        if (db == null || !db.isTagEnabled("BATTERY_DISCHARGE_INFO")) return;
+
+        File dumpFile = null;
+        FileOutputStream dumpStream = null;
+        try {
+            // dump the service to a file
+            dumpFile = new File(DUMPSYS_DATA_PATH + BatteryStats.SERVICE_NAME + ".dump");
+            dumpStream = new FileOutputStream(dumpFile);
+            batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS);
+            FileUtils.sync(dumpStream);
+
+            // add dump file to drop box
+            db.addFile("BATTERY_DISCHARGE_INFO", dumpFile, DropBoxManager.IS_TEXT);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "failed to dump battery service", e);
+        } catch (IOException e) {
+            Slog.e(TAG, "failed to write dumpsys file", e);
+        } finally {
+            // make sure we clean up
+            if (dumpStream != null) {
+                try {
+                    dumpStream.close();
+                } catch (IOException e) {
+                    Slog.e(TAG, "failed to close dumpsys output stream");
+                }
+            }
+            if (dumpFile != null && !dumpFile.delete()) {
+                Slog.e(TAG, "failed to delete temporary dumpsys file: "
+                        + dumpFile.getAbsolutePath());
+            }
+        }
+    }
+
+    private void logOutlierLocked(long duration) {
+        ContentResolver cr = mContext.getContentResolver();
+        String dischargeThresholdString = Settings.Global.getString(cr,
+                Settings.Global.BATTERY_DISCHARGE_THRESHOLD);
+        String durationThresholdString = Settings.Global.getString(cr,
+                Settings.Global.BATTERY_DISCHARGE_DURATION_THRESHOLD);
+
+        if (dischargeThresholdString != null && durationThresholdString != null) {
+            try {
+                long durationThreshold = Long.parseLong(durationThresholdString);
+                int dischargeThreshold = Integer.parseInt(dischargeThresholdString);
+                if (duration <= durationThreshold &&
+                        mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) {
+                    // If the discharge cycle is bad enough we want to know about it.
+                    logBatteryStatsLocked();
+                }
+                if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold +
+                        " discharge threshold: " + dischargeThreshold);
+                if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " +
+                        (mDischargeStartLevel - mBatteryProps.batteryLevel));
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Invalid DischargeThresholds GService string: " +
+                        durationThresholdString + " or " + dischargeThresholdString);
+                return;
+            }
+        }
+    }
+
+    private int getIconLocked(int level) {
+        if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) {
+            return com.android.internal.R.drawable.stat_sys_battery_charge;
+        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) {
+            return com.android.internal.R.drawable.stat_sys_battery;
+        } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING
+                || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) {
+            if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)
+                    && mBatteryProps.batteryLevel >= 100) {
+                return com.android.internal.R.drawable.stat_sys_battery_charge;
+            } else {
+                return com.android.internal.R.drawable.stat_sys_battery;
+            }
+        } else {
+            return com.android.internal.R.drawable.stat_sys_battery_unknown;
+        }
+    }
+
+    @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 Battery service from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mLock) {
+            if (args == null || args.length == 0 || "-a".equals(args[0])) {
+                pw.println("Current Battery Service state:");
+                if (mUpdatesStopped) {
+                    pw.println("  (UPDATES STOPPED -- use 'reset' to restart)");
+                }
+                pw.println("  AC powered: " + mBatteryProps.chargerAcOnline);
+                pw.println("  USB powered: " + mBatteryProps.chargerUsbOnline);
+                pw.println("  Wireless powered: " + mBatteryProps.chargerWirelessOnline);
+                pw.println("  status: " + mBatteryProps.batteryStatus);
+                pw.println("  health: " + mBatteryProps.batteryHealth);
+                pw.println("  present: " + mBatteryProps.batteryPresent);
+                pw.println("  level: " + mBatteryProps.batteryLevel);
+                pw.println("  scale: " + BATTERY_SCALE);
+                pw.println("  voltage: " + mBatteryProps.batteryVoltage);
+
+                if (mBatteryProps.batteryCurrentNow != Integer.MIN_VALUE) {
+                    pw.println("  current now: " + mBatteryProps.batteryCurrentNow);
+                }
+
+                if (mBatteryProps.batteryChargeCounter != Integer.MIN_VALUE) {
+                    pw.println("  charge counter: " + mBatteryProps.batteryChargeCounter);
+                }
+
+                pw.println("  temperature: " + mBatteryProps.batteryTemperature);
+                pw.println("  technology: " + mBatteryProps.batteryTechnology);
+            } else if (args.length == 3 && "set".equals(args[0])) {
+                String key = args[1];
+                String value = args[2];
+                try {
+                    boolean update = true;
+                    if ("ac".equals(key)) {
+                        mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0;
+                    } else if ("usb".equals(key)) {
+                        mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0;
+                    } else if ("wireless".equals(key)) {
+                        mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0;
+                    } else if ("status".equals(key)) {
+                        mBatteryProps.batteryStatus = Integer.parseInt(value);
+                    } else if ("level".equals(key)) {
+                        mBatteryProps.batteryLevel = Integer.parseInt(value);
+                    } else if ("invalid".equals(key)) {
+                        mInvalidCharger = Integer.parseInt(value);
+                    } else {
+                        pw.println("Unknown set option: " + key);
+                        update = false;
+                    }
+                    if (update) {
+                        long ident = Binder.clearCallingIdentity();
+                        try {
+                            mUpdatesStopped = true;
+                            processValuesLocked();
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                    }
+                } catch (NumberFormatException ex) {
+                    pw.println("Bad value: " + value);
+                }
+            } else if (args.length == 1 && "reset".equals(args[0])) {
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    mUpdatesStopped = false;
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } else {
+                pw.println("Dump current battery state, or:");
+                pw.println("  set ac|usb|wireless|status|level|invalid <value>");
+                pw.println("  reset");
+            }
+        }
+    }
+
+    private final UEventObserver mInvalidChargerObserver = new UEventObserver() {
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            final int invalidCharger = "1".equals(event.get("SWITCH_STATE")) ? 1 : 0;
+            synchronized (mLock) {
+                if (mInvalidCharger != invalidCharger) {
+                    mInvalidCharger = invalidCharger;
+                }
+            }
+        }
+    };
+
+    private final class Led {
+        private final Light mBatteryLight;
+
+        private final int mBatteryLowARGB;
+        private final int mBatteryMediumARGB;
+        private final int mBatteryFullARGB;
+        private final int mBatteryLedOn;
+        private final int mBatteryLedOff;
+
+        public Led(Context context, LightsManager lights) {
+            mBatteryLight = lights.getLight(LightsManager.LIGHT_ID_BATTERY);
+
+            mBatteryLowARGB = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryLowARGB);
+            mBatteryMediumARGB = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryMediumARGB);
+            mBatteryFullARGB = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryFullARGB);
+            mBatteryLedOn = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryLedOn);
+            mBatteryLedOff = context.getResources().getInteger(
+                    com.android.internal.R.integer.config_notificationsBatteryLedOff);
+        }
+
+        /**
+         * Synchronize on BatteryService.
+         */
+        public void updateLightsLocked() {
+            final int level = mBatteryProps.batteryLevel;
+            final int status = mBatteryProps.batteryStatus;
+            if (level < mLowBatteryWarningLevel) {
+                if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+                    // Solid red when battery is charging
+                    mBatteryLight.setColor(mBatteryLowARGB);
+                } else {
+                    // Flash red when battery is low and not charging
+                    mBatteryLight.setFlashing(mBatteryLowARGB, Light.LIGHT_FLASH_TIMED,
+                            mBatteryLedOn, mBatteryLedOff);
+                }
+            } else if (status == BatteryManager.BATTERY_STATUS_CHARGING
+                    || status == BatteryManager.BATTERY_STATUS_FULL) {
+                if (status == BatteryManager.BATTERY_STATUS_FULL || level >= 90) {
+                    // Solid green when full or charging and nearly full
+                    mBatteryLight.setColor(mBatteryFullARGB);
+                } else {
+                    // Solid orange when charging and halfway full
+                    mBatteryLight.setColor(mBatteryMediumARGB);
+                }
+            } else {
+                // No lights if not charging and not low
+                mBatteryLight.turnOff();
+            }
+        }
+    }
+
+    private final class BatteryListener extends IBatteryPropertiesListener.Stub {
+        @Override
+        public void batteryPropertiesChanged(BatteryProperties props) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                BatteryService.this.update(props);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+       }
+    }
+}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
new file mode 100644
index 0000000..546324a
--- /dev/null
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -0,0 +1,1262 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetooth;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothCallback;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothManagerCallback;
+import android.bluetooth.IBluetoothStateChangeCallback;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+class BluetoothManagerService extends IBluetoothManager.Stub {
+    private static final String TAG = "BluetoothManagerService";
+    private static final boolean DBG = true;
+
+    private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
+    private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
+    private static final String ACTION_SERVICE_STATE_CHANGED="com.android.bluetooth.btservice.action.STATE_CHANGED";
+    private static final String EXTRA_ACTION="action";
+    private static final String SECURE_SETTINGS_BLUETOOTH_ADDR_VALID="bluetooth_addr_valid";
+    private static final String SECURE_SETTINGS_BLUETOOTH_ADDRESS="bluetooth_address";
+    private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name";
+    private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
+    private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
+    //Maximum msec to wait for service restart
+    private static final int SERVICE_RESTART_TIME_MS = 200;
+    //Maximum msec to wait for restart due to error
+    private static final int ERROR_RESTART_TIME_MS = 3000;
+    //Maximum msec to delay MESSAGE_USER_SWITCHED
+    private static final int USER_SWITCHED_TIME_MS = 200;
+
+    private static final int MESSAGE_ENABLE = 1;
+    private static final int MESSAGE_DISABLE = 2;
+    private static final int MESSAGE_REGISTER_ADAPTER = 20;
+    private static final int MESSAGE_UNREGISTER_ADAPTER = 21;
+    private static final int MESSAGE_REGISTER_STATE_CHANGE_CALLBACK = 30;
+    private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
+    private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
+    private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41;
+    private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42;
+    private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
+    private static final int MESSAGE_TIMEOUT_BIND =100;
+    private static final int MESSAGE_TIMEOUT_UNBIND =101;
+    private static final int MESSAGE_GET_NAME_AND_ADDRESS=200;
+    private static final int MESSAGE_SAVE_NAME_AND_ADDRESS=201;
+    private static final int MESSAGE_USER_SWITCHED = 300;
+    private static final int MAX_SAVE_RETRIES=3;
+    private static final int MAX_ERROR_RESTART_RETRIES=6;
+
+    // Bluetooth persisted setting is off
+    private static final int BLUETOOTH_OFF=0;
+    // Bluetooth persisted setting is on
+    // and Airplane mode won't affect Bluetooth state at start up
+    private static final int BLUETOOTH_ON_BLUETOOTH=1;
+    // Bluetooth persisted setting is on
+    // but Airplane mode will affect Bluetooth state at start up
+    // and Airplane mode will have higher priority.
+    private static final int BLUETOOTH_ON_AIRPLANE=2;
+
+    private static final int SERVICE_IBLUETOOTH = 1;
+    private static final int SERVICE_IBLUETOOTHGATT = 2;
+
+    private final Context mContext;
+
+    // Locks are not provided for mName and mAddress.
+    // They are accessed in handler or broadcast receiver, same thread context.
+    private String mAddress;
+    private String mName;
+    private final ContentResolver mContentResolver;
+    private final RemoteCallbackList<IBluetoothManagerCallback> mCallbacks;
+    private final RemoteCallbackList<IBluetoothStateChangeCallback> mStateChangeCallbacks;
+    private IBluetooth mBluetooth;
+    private IBluetoothGatt mBluetoothGatt;
+    private boolean mBinding;
+    private boolean mUnbinding;
+    // used inside handler thread
+    private boolean mQuietEnable = false;
+    // configuarion from external IBinder call which is used to
+    // synchronize with broadcast receiver.
+    private boolean mQuietEnableExternal;
+    // configuarion from external IBinder call which is used to
+    // synchronize with broadcast receiver.
+    private boolean mEnableExternal;
+    // used inside handler thread
+    private boolean mEnable;
+    private int mState;
+    private final BluetoothHandler mHandler;
+    private int mErrorRecoveryRetryCounter;
+
+    private void registerForAirplaneMode(IntentFilter filter) {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final String airplaneModeRadios = Settings.Global.getString(resolver,
+                Settings.Global.AIRPLANE_MODE_RADIOS);
+        final String toggleableRadios = Settings.Global.getString(resolver,
+                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+        boolean mIsAirplaneSensitive = airplaneModeRadios == null ? true :
+                airplaneModeRadios.contains(Settings.Global.RADIO_BLUETOOTH);
+        if (mIsAirplaneSensitive) {
+            filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        }
+    }
+
+    private final IBluetoothCallback mBluetoothCallback =  new IBluetoothCallback.Stub() {
+        @Override
+        public void onBluetoothStateChange(int prevState, int newState) throws RemoteException  {
+            Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE,prevState,newState);
+            mHandler.sendMessage(msg);
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) {
+                String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME);
+                if (DBG) Log.d(TAG, "Bluetooth Adapter name changed to " + newName);
+                if (newName != null) {
+                    storeNameAndAddress(newName, null);
+                }
+            } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
+                synchronized(mReceiver) {
+                    if (isBluetoothPersistedStateOn()) {
+                        if (isAirplaneModeOn()) {
+                            persistBluetoothSetting(BLUETOOTH_ON_AIRPLANE);
+                        } else {
+                            persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+                        }
+                    }
+                    if (isAirplaneModeOn()) {
+                        // disable without persisting the setting
+                        sendDisableMsg();
+                    } else if (mEnableExternal) {
+                        // enable without persisting the setting
+                        sendEnableMsg(mQuietEnableExternal);
+                    }
+                }
+            } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_USER_SWITCHED,
+                       intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+            } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+                synchronized(mReceiver) {
+                    if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) {
+                        //Enable
+                        if (DBG) Log.d(TAG, "Auto-enabling Bluetooth.");
+                        sendEnableMsg(mQuietEnableExternal);
+                    }
+                }
+
+                if (!isNameAndAddressSet()) {
+                    //Sync the Bluetooth name and address from the Bluetooth Adapter
+                    if (DBG) Log.d(TAG,"Retrieving Bluetooth Adapter name and address...");
+                    getNameAndAddress();
+                }
+            }
+        }
+    };
+
+    BluetoothManagerService(Context context) {
+        mHandler = new BluetoothHandler(IoThread.get().getLooper());
+
+        mContext = context;
+        mBluetooth = null;
+        mBinding = false;
+        mUnbinding = false;
+        mEnable = false;
+        mState = BluetoothAdapter.STATE_OFF;
+        mQuietEnableExternal = false;
+        mEnableExternal = false;
+        mAddress = null;
+        mName = null;
+        mErrorRecoveryRetryCounter = 0;
+        mContentResolver = context.getContentResolver();
+        mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
+        mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+        IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        registerForAirplaneMode(filter);
+        mContext.registerReceiver(mReceiver, filter);
+        loadStoredNameAndAddress();
+        if (isBluetoothPersistedStateOn()) {
+            mEnableExternal = true;
+        }
+    }
+
+    /**
+     *  Returns true if airplane mode is currently on
+     */
+    private final boolean isAirplaneModeOn() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+    }
+
+    /**
+     *  Returns true if the Bluetooth saved state is "on"
+     */
+    private final boolean isBluetoothPersistedStateOn() {
+        return Settings.Global.getInt(mContentResolver,
+                Settings.Global.BLUETOOTH_ON, 0) != BLUETOOTH_OFF;
+    }
+
+    /**
+     *  Returns true if the Bluetooth saved state is BLUETOOTH_ON_BLUETOOTH
+     */
+    private final boolean isBluetoothPersistedStateOnBluetooth() {
+        return Settings.Global.getInt(mContentResolver,
+                Settings.Global.BLUETOOTH_ON, 0) == BLUETOOTH_ON_BLUETOOTH;
+    }
+
+    /**
+     *  Save the Bluetooth on/off state
+     *
+     */
+    private void persistBluetoothSetting(int value) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                               Settings.Global.BLUETOOTH_ON,
+                               value);
+    }
+
+    /**
+     * Returns true if the Bluetooth Adapter's name and address is
+     * locally cached
+     * @return
+     */
+    private boolean isNameAndAddressSet() {
+        return mName !=null && mAddress!= null && mName.length()>0 && mAddress.length()>0;
+    }
+
+    /**
+     * Retrieve the Bluetooth Adapter's name and address and save it in
+     * in the local cache
+     */
+    private void loadStoredNameAndAddress() {
+        if (DBG) Log.d(TAG, "Loading stored name and address");
+        if (mContext.getResources().getBoolean
+            (com.android.internal.R.bool.config_bluetooth_address_validation) &&
+             Settings.Secure.getInt(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 0) == 0) {
+            // if the valid flag is not set, don't load the address and name
+            if (DBG) Log.d(TAG, "invalid bluetooth name and address stored");
+            return;
+        }
+        mName = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME);
+        mAddress = Settings.Secure.getString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS);
+        if (DBG) Log.d(TAG, "Stored bluetooth Name=" + mName + ",Address=" + mAddress);
+    }
+
+    /**
+     * Save the Bluetooth name and address in the persistent store.
+     * Only non-null values will be saved.
+     * @param name
+     * @param address
+     */
+    private void storeNameAndAddress(String name, String address) {
+        if (name != null) {
+            Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_NAME, name);
+            mName = name;
+            if (DBG) Log.d(TAG,"Stored Bluetooth name: " +
+                Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_NAME));
+        }
+
+        if (address != null) {
+            Settings.Secure.putString(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDRESS, address);
+            mAddress=address;
+            if (DBG)  Log.d(TAG,"Stored Bluetoothaddress: " +
+                Settings.Secure.getString(mContentResolver,SECURE_SETTINGS_BLUETOOTH_ADDRESS));
+        }
+
+        if ((name != null) && (address != null)) {
+            Settings.Secure.putInt(mContentResolver, SECURE_SETTINGS_BLUETOOTH_ADDR_VALID, 1);
+        }
+    }
+
+    public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
+        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
+        msg.obj = callback;
+        mHandler.sendMessage(msg);
+        synchronized(mConnection) {
+            return mBluetooth;
+        }
+    }
+
+    public void unregisterAdapter(IBluetoothManagerCallback callback) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_ADAPTER);
+        msg.obj = callback;
+        mHandler.sendMessage(msg);
+    }
+
+    public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK);
+        msg.obj = callback;
+        mHandler.sendMessage(msg);
+    }
+
+    public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+        Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK);
+        msg.obj = callback;
+        mHandler.sendMessage(msg);
+    }
+
+    public boolean isEnabled() {
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+            (!checkIfCallerIsForegroundUser())) {
+            Log.w(TAG,"isEnabled(): not allowed for non-active and non system user");
+            return false;
+        }
+
+        synchronized(mConnection) {
+            try {
+                return (mBluetooth != null && mBluetooth.isEnabled());
+            } catch (RemoteException e) {
+                Log.e(TAG, "isEnabled()", e);
+            }
+        }
+        return false;
+    }
+
+    public void getNameAndAddress() {
+        if (DBG) {
+            Log.d(TAG,"getNameAndAddress(): mBluetooth = " + mBluetooth +
+                  " mBinding = " + mBinding);
+        }
+        Message msg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+        mHandler.sendMessage(msg);
+    }
+    public boolean enableNoAutoConnect()
+    {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH ADMIN permission");
+
+        if (DBG) {
+            Log.d(TAG,"enableNoAutoConnect():  mBluetooth =" + mBluetooth +
+                    " mBinding = " + mBinding);
+        }
+        int callingAppId = UserHandle.getAppId(Binder.getCallingUid());
+
+        if (callingAppId != Process.NFC_UID) {
+            throw new SecurityException("no permission to enable Bluetooth quietly");
+        }
+
+        synchronized(mReceiver) {
+            mQuietEnableExternal = true;
+            mEnableExternal = true;
+            sendEnableMsg(true);
+        }
+        return true;
+
+    }
+    public boolean enable() {
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+            (!checkIfCallerIsForegroundUser())) {
+            Log.w(TAG,"enable(): not allowed for non-active and non system user");
+            return false;
+        }
+
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH ADMIN permission");
+        if (DBG) {
+            Log.d(TAG,"enable():  mBluetooth =" + mBluetooth +
+                    " mBinding = " + mBinding);
+        }
+
+        synchronized(mReceiver) {
+            mQuietEnableExternal = false;
+            mEnableExternal = true;
+            // waive WRITE_SECURE_SETTINGS permission check
+            long callingIdentity = Binder.clearCallingIdentity();
+            persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH);
+            Binder.restoreCallingIdentity(callingIdentity);
+            sendEnableMsg(false);
+        }
+        return true;
+    }
+
+    public boolean disable(boolean persist) {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
+                                                "Need BLUETOOTH ADMIN permissicacheNameAndAddresson");
+
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+            (!checkIfCallerIsForegroundUser())) {
+            Log.w(TAG,"disable(): not allowed for non-active and non system user");
+            return false;
+        }
+
+        if (DBG) {
+            Log.d(TAG,"disable(): mBluetooth = " + mBluetooth +
+                " mBinding = " + mBinding);
+        }
+
+        synchronized(mReceiver) {
+            if (persist) {
+                // waive WRITE_SECURE_SETTINGS permission check
+                long callingIdentity = Binder.clearCallingIdentity();
+                persistBluetoothSetting(BLUETOOTH_OFF);
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+            mEnableExternal = false;
+            sendDisableMsg();
+        }
+        return true;
+    }
+
+    public void unbindAndFinish() {
+        if (DBG) {
+            Log.d(TAG,"unbindAndFinish(): " + mBluetooth +
+                " mBinding = " + mBinding);
+        }
+
+        synchronized (mConnection) {
+            if (mUnbinding) return;
+            mUnbinding = true;
+            if (mBluetooth != null) {
+                if (!mConnection.isGetNameAddressOnly()) {
+                    //Unregister callback object
+                    try {
+                        mBluetooth.unregisterCallback(mBluetoothCallback);
+                    } catch (RemoteException re) {
+                        Log.e(TAG, "Unable to unregister BluetoothCallback",re);
+                    }
+                }
+                if (DBG) Log.d(TAG, "Sending unbind request.");
+                mBluetooth = null;
+                //Unbind
+                mContext.unbindService(mConnection);
+                mUnbinding = false;
+                mBinding = false;
+            } else {
+                mUnbinding=false;
+            }
+        }
+    }
+
+    public IBluetoothGatt getBluetoothGatt() {
+        // sync protection
+        return mBluetoothGatt;
+    }
+
+    private void sendBluetoothStateCallback(boolean isUp) {
+        int n = mStateChangeCallbacks.beginBroadcast();
+        if (DBG) Log.d(TAG,"Broadcasting onBluetoothStateChange("+isUp+") to " + n + " receivers.");
+        for (int i=0; i <n;i++) {
+            try {
+                mStateChangeCallbacks.getBroadcastItem(i).onBluetoothStateChange(isUp);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Unable to call onBluetoothStateChange() on callback #" + i , e);
+            }
+        }
+        mStateChangeCallbacks.finishBroadcast();
+    }
+
+    /**
+     * Inform BluetoothAdapter instances that Adapter service is up
+     */
+    private void sendBluetoothServiceUpCallback() {
+        if (!mConnection.isGetNameAddressOnly()) {
+            if (DBG) Log.d(TAG,"Calling onBluetoothServiceUp callbacks");
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceUp(mBluetooth);
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceUp() on callback #" + i, e);
+                }
+            }
+            mCallbacks.finishBroadcast();
+        }
+    }
+    /**
+     * Inform BluetoothAdapter instances that Adapter service is down
+     */
+    private void sendBluetoothServiceDownCallback() {
+        if (!mConnection.isGetNameAddressOnly()) {
+            if (DBG) Log.d(TAG,"Calling onBluetoothServiceDown callbacks");
+            int n = mCallbacks.beginBroadcast();
+            Log.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers.");
+            for (int i=0; i <n;i++) {
+                try {
+                    mCallbacks.getBroadcastItem(i).onBluetoothServiceDown();
+                }  catch (RemoteException e) {
+                    Log.e(TAG, "Unable to call onBluetoothServiceDown() on callback #" + i, e);
+                }
+            }
+            mCallbacks.finishBroadcast();
+        }
+    }
+    public String getAddress() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+            (!checkIfCallerIsForegroundUser())) {
+            Log.w(TAG,"getAddress(): not allowed for non-active and non system user");
+            return null;
+        }
+
+        synchronized(mConnection) {
+            if (mBluetooth != null) {
+                try {
+                    return mBluetooth.getAddress();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "getAddress(): Unable to retrieve address remotely..Returning cached address",e);
+                }
+            }
+        }
+        // mAddress is accessed from outside.
+        // It is alright without a lock. Here, bluetooth is off, no other thread is
+        // changing mAddress
+        return mAddress;
+    }
+
+    public String getName() {
+        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
+                                                "Need BLUETOOTH permission");
+
+        if ((Binder.getCallingUid() != Process.SYSTEM_UID) &&
+            (!checkIfCallerIsForegroundUser())) {
+            Log.w(TAG,"getName(): not allowed for non-active and non system user");
+            return null;
+        }
+
+        synchronized(mConnection) {
+            if (mBluetooth != null) {
+                try {
+                    return mBluetooth.getName();
+                } catch (RemoteException e) {
+                    Log.e(TAG, "getName(): Unable to retrieve name remotely..Returning cached name",e);
+                }
+            }
+        }
+        // mName is accessed from outside.
+        // It alright without a lock. Here, bluetooth is off, no other thread is
+        // changing mName
+        return mName;
+    }
+
+    private class BluetoothServiceConnection implements ServiceConnection {
+
+        private boolean mGetNameAddressOnly;
+
+        public void setGetNameAddressOnly(boolean getOnly) {
+            mGetNameAddressOnly = getOnly;
+        }
+
+        public boolean isGetNameAddressOnly() {
+            return mGetNameAddressOnly;
+        }
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "BluetoothServiceConnection: " + className.getClassName());
+            Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED);
+            // TBD if (className.getClassName().equals(IBluetooth.class.getName())) {
+            if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+                msg.arg1 = SERVICE_IBLUETOOTH;
+                // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) {
+            } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+                msg.arg1 = SERVICE_IBLUETOOTHGATT;
+            } else {
+                Log.e(TAG, "Unknown service connected: " + className.getClassName());
+                return;
+            }
+            msg.obj = service;
+            mHandler.sendMessage(msg);
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            // Called if we unexpected disconnected.
+            if (DBG) Log.d(TAG, "BluetoothServiceConnection, disconnected: " +
+                           className.getClassName());
+            Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED);
+            if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) {
+                msg.arg1 = SERVICE_IBLUETOOTH;
+            } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) {
+                msg.arg1 = SERVICE_IBLUETOOTHGATT;
+            } else {
+                Log.e(TAG, "Unknown service disconnected: " + className.getClassName());
+                return;
+            }
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private BluetoothServiceConnection mConnection = new BluetoothServiceConnection();
+
+    private class BluetoothHandler extends Handler {
+        public BluetoothHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (DBG) Log.d (TAG, "Message: " + msg.what);
+            switch (msg.what) {
+                case MESSAGE_GET_NAME_AND_ADDRESS: {
+                    if (DBG) Log.d(TAG,"MESSAGE_GET_NAME_AND_ADDRESS");
+                    synchronized(mConnection) {
+                        //Start bind request
+                        if ((mBluetooth == null) && (!mBinding)) {
+                            if (DBG) Log.d(TAG, "Binding to service to get name and address");
+                            mConnection.setGetNameAddressOnly(true);
+                            //Start bind timeout and bind
+                            Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+                            mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
+                            Intent i = new Intent(IBluetooth.class.getName());
+                            if (!doBind(i, mConnection,
+                                    Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+                                mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+                            } else {
+                                mBinding = true;
+                            }
+                        }
+                        else {
+                            Message saveMsg= mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
+                            saveMsg.arg1 = 0;
+                            if (mBluetooth != null) {
+                                mHandler.sendMessage(saveMsg);
+                            } else {
+                                // if enable is also called to bind the service
+                                // wait for MESSAGE_BLUETOOTH_SERVICE_CONNECTED
+                                mHandler.sendMessageDelayed(saveMsg, TIMEOUT_SAVE_MS);
+                            }
+                        }
+                    }
+                    break;
+                }
+                case MESSAGE_SAVE_NAME_AND_ADDRESS: {
+                    boolean unbind = false;
+                    if (DBG) Log.d(TAG,"MESSAGE_SAVE_NAME_AND_ADDRESS");
+                    synchronized(mConnection) {
+                        if (!mEnable && mBluetooth != null) {
+                            try {
+                                mBluetooth.enable();
+                            } catch (RemoteException e) {
+                                Log.e(TAG,"Unable to call enable()",e);
+                            }
+                        }
+                    }
+                    if (mBluetooth != null) waitForOnOff(true, false);
+                    synchronized(mConnection) {
+                        if (mBluetooth != null) {
+                            String name =  null;
+                            String address = null;
+                            try {
+                                name =  mBluetooth.getName();
+                                address = mBluetooth.getAddress();
+                            } catch (RemoteException re) {
+                                Log.e(TAG,"",re);
+                            }
+
+                            if (name != null && address != null) {
+                                storeNameAndAddress(name,address);
+                                if (mConnection.isGetNameAddressOnly()) {
+                                    unbind = true;
+                                }
+                            } else {
+                                if (msg.arg1 < MAX_SAVE_RETRIES) {
+                                    Message retryMsg = mHandler.obtainMessage(MESSAGE_SAVE_NAME_AND_ADDRESS);
+                                    retryMsg.arg1= 1+msg.arg1;
+                                    if (DBG) Log.d(TAG,"Retrying name/address remote retrieval and save.....Retry count =" + retryMsg.arg1);
+                                    mHandler.sendMessageDelayed(retryMsg, TIMEOUT_SAVE_MS);
+                                } else {
+                                    Log.w(TAG,"Maximum name/address remote retrieval retry exceeded");
+                                    if (mConnection.isGetNameAddressOnly()) {
+                                        unbind = true;
+                                    }
+                                }
+                            }
+                            if (!mEnable) {
+                                try {
+                                    mBluetooth.disable();
+                                } catch (RemoteException e) {
+                                    Log.e(TAG,"Unable to call disable()",e);
+                                }
+                            }
+                        } else {
+                            // rebind service by Request GET NAME AND ADDRESS
+                            // if service is unbinded by disable or
+                            // MESSAGE_BLUETOOTH_SERVICE_CONNECTED is not received
+                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+                            mHandler.sendMessage(getMsg);
+                        }
+                    }
+                    if (!mEnable && mBluetooth != null) waitForOnOff(false, true);
+                    if (unbind) {
+                        unbindAndFinish();
+                    }
+                    break;
+                }
+                case MESSAGE_ENABLE:
+                    if (DBG) {
+                        Log.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth);
+                    }
+                    mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+                    mEnable = true;
+                    handleEnable(msg.arg1 == 1);
+                    break;
+
+                case MESSAGE_DISABLE:
+                    mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE);
+                    if (mEnable && mBluetooth != null) {
+                        waitForOnOff(true, false);
+                        mEnable = false;
+                        handleDisable();
+                        waitForOnOff(false, false);
+                    } else {
+                        mEnable = false;
+                        handleDisable();
+                    }
+                    break;
+
+                case MESSAGE_REGISTER_ADAPTER:
+                {
+                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
+                    boolean added = mCallbacks.register(callback);
+                    Log.d(TAG,"Added callback: " +  (callback == null? "null": callback)  +":" +added );
+                }
+                    break;
+                case MESSAGE_UNREGISTER_ADAPTER:
+                {
+                    IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj;
+                    boolean removed = mCallbacks.unregister(callback);
+                    Log.d(TAG,"Removed callback: " +  (callback == null? "null": callback)  +":" + removed);
+                    break;
+                }
+                case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK:
+                {
+                    IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
+                    if (callback != null) {
+                        mStateChangeCallbacks.register(callback);
+                    }
+                    break;
+                }
+                case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK:
+                {
+                    IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj;
+                    if (callback != null) {
+                        mStateChangeCallbacks.unregister(callback);
+                    }
+                    break;
+                }
+                case MESSAGE_BLUETOOTH_SERVICE_CONNECTED:
+                {
+                    if (DBG) Log.d(TAG,"MESSAGE_BLUETOOTH_SERVICE_CONNECTED: " + msg.arg1);
+
+                    IBinder service = (IBinder) msg.obj;
+                    synchronized(mConnection) {
+                        if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+                            mBluetoothGatt = IBluetoothGatt.Stub.asInterface(service);
+                            break;
+                        } // else must be SERVICE_IBLUETOOTH
+
+                        //Remove timeout
+                        mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+
+                        mBinding = false;
+                        mBluetooth = IBluetooth.Stub.asInterface(service);
+
+                        try {
+                            boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver,
+                                Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1);
+                            if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) {
+                                Log.e(TAG,"IBluetooth.configHciSnoopLog return false");
+                            }
+                        } catch (RemoteException e) {
+                            Log.e(TAG,"Unable to call configHciSnoopLog", e);
+                        }
+
+                        if (mConnection.isGetNameAddressOnly()) {
+                            //Request GET NAME AND ADDRESS
+                            Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
+                            mHandler.sendMessage(getMsg);
+                            if (!mEnable) return;
+                        }
+
+                        mConnection.setGetNameAddressOnly(false);
+                        //Register callback object
+                        try {
+                            mBluetooth.registerCallback(mBluetoothCallback);
+                        } catch (RemoteException re) {
+                            Log.e(TAG, "Unable to register BluetoothCallback",re);
+                        }
+                        //Inform BluetoothAdapter instances that service is up
+                        sendBluetoothServiceUpCallback();
+
+                        //Do enable request
+                        try {
+                            if (mQuietEnable == false) {
+                                if(!mBluetooth.enable()) {
+                                    Log.e(TAG,"IBluetooth.enable() returned false");
+                                }
+                            }
+                            else
+                            {
+                                if(!mBluetooth.enableNoAutoConnect()) {
+                                    Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");
+                                }
+                            }
+                        } catch (RemoteException e) {
+                            Log.e(TAG,"Unable to call enable()",e);
+                        }
+                    }
+
+                    if (!mEnable) {
+                        waitForOnOff(true, false);
+                        handleDisable();
+                        waitForOnOff(false, false);
+                    }
+                    break;
+                }
+                case MESSAGE_TIMEOUT_BIND: {
+                    Log.e(TAG, "MESSAGE_TIMEOUT_BIND");
+                    synchronized(mConnection) {
+                        mBinding = false;
+                    }
+                    break;
+                }
+                case MESSAGE_BLUETOOTH_STATE_CHANGE:
+                {
+                    int prevState = msg.arg1;
+                    int newState = msg.arg2;
+                    if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState=" + newState);
+                    mState = newState;
+                    bluetoothStateChangeHandler(prevState, newState);
+                    // handle error state transition case from TURNING_ON to OFF
+                    // unbind and rebind bluetooth service and enable bluetooth
+                    if ((prevState == BluetoothAdapter.STATE_TURNING_ON) &&
+                        (newState == BluetoothAdapter.STATE_OFF) &&
+                        (mBluetooth != null) && mEnable) {
+                        recoverBluetoothServiceFromError();
+                    }
+                    if (newState == BluetoothAdapter.STATE_ON) {
+                        // bluetooth is working, reset the counter
+                        if (mErrorRecoveryRetryCounter != 0) {
+                            Log.w(TAG, "bluetooth is recovered from error");
+                            mErrorRecoveryRetryCounter = 0;
+                        }
+                    }
+                    break;
+                }
+                case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED:
+                {
+                    Log.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: " + msg.arg1);
+                    synchronized(mConnection) {
+                        if (msg.arg1 == SERVICE_IBLUETOOTH) {
+                            // if service is unbinded already, do nothing and return
+                            if (mBluetooth == null) break;
+                            mBluetooth = null;
+                        } else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
+                            mBluetoothGatt = null;
+                            break;
+                        } else {
+                            Log.e(TAG, "Bad msg.arg1: " + msg.arg1);
+                            break;
+                        }
+                    }
+
+                    if (mEnable) {
+                        mEnable = false;
+                        // Send a Bluetooth Restart message
+                        Message restartMsg = mHandler.obtainMessage(
+                            MESSAGE_RESTART_BLUETOOTH_SERVICE);
+                        mHandler.sendMessageDelayed(restartMsg,
+                            SERVICE_RESTART_TIME_MS);
+                    }
+
+                    if (!mConnection.isGetNameAddressOnly()) {
+                        sendBluetoothServiceDownCallback();
+
+                        // Send BT state broadcast to update
+                        // the BT icon correctly
+                        if ((mState == BluetoothAdapter.STATE_TURNING_ON) ||
+                            (mState == BluetoothAdapter.STATE_ON)) {
+                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+                                                        BluetoothAdapter.STATE_TURNING_OFF);
+                            mState = BluetoothAdapter.STATE_TURNING_OFF;
+                        }
+                        if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+                            bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+                                                        BluetoothAdapter.STATE_OFF);
+                        }
+
+                        mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+                        mState = BluetoothAdapter.STATE_OFF;
+                    }
+                    break;
+                }
+                case MESSAGE_RESTART_BLUETOOTH_SERVICE:
+                {
+                    Log.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:"
+                        +" Restart IBluetooth service");
+                    /* Enable without persisting the setting as
+                     it doesnt change when IBluetooth
+                     service restarts */
+                    mEnable = true;
+                    handleEnable(mQuietEnable);
+                    break;
+                }
+
+                case MESSAGE_TIMEOUT_UNBIND:
+                {
+                    Log.e(TAG, "MESSAGE_TIMEOUT_UNBIND");
+                    synchronized(mConnection) {
+                        mUnbinding = false;
+                    }
+                    break;
+                }
+
+                case MESSAGE_USER_SWITCHED:
+                {
+                    if (DBG) {
+                        Log.d(TAG, "MESSAGE_USER_SWITCHED");
+                    }
+                    mHandler.removeMessages(MESSAGE_USER_SWITCHED);
+                    /* disable and enable BT when detect a user switch */
+                    if (mEnable && mBluetooth != null) {
+                        synchronized (mConnection) {
+                            if (mBluetooth != null) {
+                                //Unregister callback object
+                                try {
+                                    mBluetooth.unregisterCallback(mBluetoothCallback);
+                                } catch (RemoteException re) {
+                                    Log.e(TAG, "Unable to unregister",re);
+                                }
+                            }
+                        }
+
+                        if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+                            // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE
+                            bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF);
+                            mState = BluetoothAdapter.STATE_OFF;
+                        }
+                        if (mState == BluetoothAdapter.STATE_OFF) {
+                            bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON);
+                            mState = BluetoothAdapter.STATE_TURNING_ON;
+                        }
+
+                        waitForOnOff(true, false);
+
+                        if (mState == BluetoothAdapter.STATE_TURNING_ON) {
+                            bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
+                        }
+
+                        // disable
+                        handleDisable();
+                        // Pbap service need receive STATE_TURNING_OFF intent to close
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+                                                    BluetoothAdapter.STATE_TURNING_OFF);
+
+                        waitForOnOff(false, true);
+
+                        bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+                                                    BluetoothAdapter.STATE_OFF);
+                        sendBluetoothServiceDownCallback();
+                        synchronized (mConnection) {
+                            if (mBluetooth != null) {
+                                mBluetooth = null;
+                                //Unbind
+                                mContext.unbindService(mConnection);
+                            }
+                        }
+                        SystemClock.sleep(100);
+
+                        mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+                        mState = BluetoothAdapter.STATE_OFF;
+                        // enable
+                        handleEnable(mQuietEnable);
+		    } else if (mBinding || mBluetooth != null) {
+                        Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
+                        userMsg.arg2 = 1 + msg.arg2;
+                        // if user is switched when service is being binding
+                        // delay sending MESSAGE_USER_SWITCHED
+                        mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS);
+                        if (DBG) {
+                            Log.d(TAG, "delay MESSAGE_USER_SWITCHED " + userMsg.arg2);
+                        }
+		    }
+                    break;
+                }
+            }
+        }
+    }
+
+    private void handleEnable(boolean quietMode) {
+        mQuietEnable = quietMode;
+
+        synchronized(mConnection) {
+            if ((mBluetooth == null) && (!mBinding)) {
+                //Start bind timeout and bind
+                Message timeoutMsg=mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND);
+                mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS);
+                mConnection.setGetNameAddressOnly(false);
+                Intent i = new Intent(IBluetooth.class.getName());
+                if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
+                    mHandler.removeMessages(MESSAGE_TIMEOUT_BIND);
+                } else {
+                    mBinding = true;
+                }
+            } else if (mBluetooth != null) {
+                if (mConnection.isGetNameAddressOnly()) {
+                    // if GetNameAddressOnly is set, we can clear this flag,
+                    // so the service won't be unbind
+                    // after name and address are saved
+                    mConnection.setGetNameAddressOnly(false);
+                    //Register callback object
+                    try {
+                        mBluetooth.registerCallback(mBluetoothCallback);
+                    } catch (RemoteException re) {
+                        Log.e(TAG, "Unable to register BluetoothCallback",re);
+                    }
+                    //Inform BluetoothAdapter instances that service is up
+                    sendBluetoothServiceUpCallback();
+                }
+
+                //Enable bluetooth
+                try {
+                    if (!mQuietEnable) {
+                        if(!mBluetooth.enable()) {
+                            Log.e(TAG,"IBluetooth.enable() returned false");
+                        }
+                    }
+                    else {
+                        if(!mBluetooth.enableNoAutoConnect()) {
+                            Log.e(TAG,"IBluetooth.enableNoAutoConnect() returned false");
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG,"Unable to call enable()",e);
+                }
+            }
+        }
+    }
+
+    boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) {
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) {
+            Log.e(TAG, "Fail to bind to: " + intent);
+            return false;
+        }
+        return true;
+    }
+
+    private void handleDisable() {
+        synchronized(mConnection) {
+            // don't need to disable if GetNameAddressOnly is set,
+            // service will be unbinded after Name and Address are saved
+            if ((mBluetooth != null) && (!mConnection.isGetNameAddressOnly())) {
+                if (DBG) Log.d(TAG,"Sending off request.");
+
+                try {
+                    if(!mBluetooth.disable()) {
+                        Log.e(TAG,"IBluetooth.disable() returned false");
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG,"Unable to call disable()",e);
+                }
+            }
+        }
+    }
+
+    private boolean checkIfCallerIsForegroundUser() {
+        int foregroundUser;
+        int callingUser = UserHandle.getCallingUserId();
+        int callingUid = Binder.getCallingUid();
+        long callingIdentity = Binder.clearCallingIdentity();
+        int callingAppId = UserHandle.getAppId(callingUid);
+        boolean valid = false;
+        try {
+            foregroundUser = ActivityManager.getCurrentUser();
+            valid = (callingUser == foregroundUser) ||
+                    callingAppId == Process.NFC_UID;
+            if (DBG) {
+                Log.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid
+                    + " callingUser=" + callingUser
+                    + " foregroundUser=" + foregroundUser);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+        return valid;
+    }
+
+    private void bluetoothStateChangeHandler(int prevState, int newState) {
+        if (prevState != newState) {
+            //Notify all proxy objects first of adapter state change
+            if (newState == BluetoothAdapter.STATE_ON || newState == BluetoothAdapter.STATE_OFF) {
+                boolean isUp = (newState==BluetoothAdapter.STATE_ON);
+                sendBluetoothStateCallback(isUp);
+
+                if (isUp) {
+                    // connect to GattService
+                    if (mContext.getPackageManager().hasSystemFeature(
+                                                     PackageManager.FEATURE_BLUETOOTH_LE)) {
+                        Intent i = new Intent(IBluetoothGatt.class.getName());
+                        doBind(i, mConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT);
+                    }
+                } else {
+                    //If Bluetooth is off, send service down event to proxy objects, and unbind
+                    if (!isUp && canUnbindBluetoothService()) {
+                        sendBluetoothServiceDownCallback();
+                        unbindAndFinish();
+                    }
+                }
+            }
+
+            //Send broadcast message to everyone else
+            Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
+            intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState);
+            intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            if (DBG) Log.d(TAG,"Bluetooth State Change Intent: " + prevState + " -> " + newState);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                    BLUETOOTH_PERM);
+        }
+    }
+
+    /**
+     *  if on is true, wait for state become ON
+     *  if off is true, wait for state become OFF
+     *  if both on and off are false, wait for state not ON
+     */
+    private boolean waitForOnOff(boolean on, boolean off) {
+        int i = 0;
+        while (i < 10) {
+            synchronized(mConnection) {
+                try {
+                    if (mBluetooth == null) break;
+                    if (on) {
+                        if (mBluetooth.getState() == BluetoothAdapter.STATE_ON) return true;
+                    } else if (off) {
+                        if (mBluetooth.getState() == BluetoothAdapter.STATE_OFF) return true;
+                    } else {
+                        if (mBluetooth.getState() != BluetoothAdapter.STATE_ON) return true;
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "getState()", e);
+                    break;
+                }
+            }
+            if (on || off) {
+                SystemClock.sleep(300);
+            } else {
+                SystemClock.sleep(50);
+            }
+            i++;
+        }
+        Log.e(TAG,"waitForOnOff time out");
+        return false;
+    }
+
+    private void sendDisableMsg() {
+        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE));
+    }
+
+    private void sendEnableMsg(boolean quietMode) {
+        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE,
+                             quietMode ? 1 : 0, 0));
+    }
+
+    private boolean canUnbindBluetoothService() {
+        synchronized(mConnection) {
+            //Only unbind with mEnable flag not set
+            //For race condition: disable and enable back-to-back
+            //Avoid unbind right after enable due to callback from disable
+            //Only unbind with Bluetooth at OFF state
+            //Only unbind without any MESSAGE_BLUETOOTH_STATE_CHANGE message
+            try {
+                if (mEnable || (mBluetooth == null)) return false;
+                if (mHandler.hasMessages(MESSAGE_BLUETOOTH_STATE_CHANGE)) return false;
+                return (mBluetooth.getState() == BluetoothAdapter.STATE_OFF);
+            } catch (RemoteException e) {
+                Log.e(TAG, "getState()", e);
+            }
+        }
+        return false;
+    }
+
+    private void recoverBluetoothServiceFromError() {
+        Log.e(TAG,"recoverBluetoothServiceFromError");
+        synchronized (mConnection) {
+            if (mBluetooth != null) {
+                //Unregister callback object
+                try {
+                    mBluetooth.unregisterCallback(mBluetoothCallback);
+                } catch (RemoteException re) {
+                    Log.e(TAG, "Unable to unregister",re);
+                }
+            }
+        }
+
+        SystemClock.sleep(500);
+
+        // disable
+        handleDisable();
+
+        waitForOnOff(false, true);
+
+        sendBluetoothServiceDownCallback();
+        synchronized (mConnection) {
+            if (mBluetooth != null) {
+                mBluetooth = null;
+                //Unbind
+                mContext.unbindService(mConnection);
+            }
+        }
+
+        mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+        mState = BluetoothAdapter.STATE_OFF;
+
+        mEnable = false;
+
+        if (mErrorRecoveryRetryCounter++ < MAX_ERROR_RESTART_RETRIES) {
+            // Send a Bluetooth Restart message to reenable bluetooth
+            Message restartMsg = mHandler.obtainMessage(
+                             MESSAGE_RESTART_BLUETOOTH_SERVICE);
+            mHandler.sendMessageDelayed(restartMsg, ERROR_RESTART_TIME_MS);
+        } else {
+            // todo: notify user to power down and power up phone to make bluetooth work.
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
new file mode 100644
index 0000000..bce85ce
--- /dev/null
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.IPackageManager;
+import android.os.Build;
+import android.os.DropBoxManager;
+import android.os.FileObserver;
+import android.os.FileUtils;
+import android.os.RecoverySystem;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.provider.Downloads;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Performs a number of miscellaneous, non-system-critical actions
+ * after the system has finished booting.
+ */
+public class BootReceiver extends BroadcastReceiver {
+    private static final String TAG = "BootReceiver";
+
+    // Maximum size of a logged event (files get truncated if they're longer).
+    // Give userdebug builds a larger max to capture extra debug, esp. for last_kmsg.
+    private static final int LOG_SIZE =
+        SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
+
+    private static final File TOMBSTONE_DIR = new File("/data/tombstones");
+
+    // The pre-froyo package and class of the system updater, which
+    // ran in the system process.  We need to remove its packages here
+    // in order to clean up after a pre-froyo-to-froyo update.
+    private static final String OLD_UPDATER_PACKAGE =
+        "com.google.android.systemupdater";
+    private static final String OLD_UPDATER_CLASS =
+        "com.google.android.systemupdater.SystemUpdateReceiver";
+
+    // Keep a reference to the observer so the finalizer doesn't disable it.
+    private static FileObserver sTombstoneObserver = null;
+
+    @Override
+    public void onReceive(final Context context, Intent intent) {
+        // Log boot events in the background to avoid blocking the main thread with I/O
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    logBootEvents(context);
+                } catch (Exception e) {
+                    Slog.e(TAG, "Can't log boot events", e);
+                }
+                try {
+                    boolean onlyCore = false;
+                    try {
+                        onlyCore = IPackageManager.Stub.asInterface(ServiceManager.getService(
+                                "package")).isOnlyCoreApps();
+                    } catch (RemoteException e) {
+                    }
+                    if (!onlyCore) {
+                        removeOldUpdatePackages(context);
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Can't remove old update packages", e);
+                }
+
+            }
+        }.start();
+    }
+
+    private void removeOldUpdatePackages(Context context) {
+        Downloads.removeAllDownloadsByPackage(context, OLD_UPDATER_PACKAGE, OLD_UPDATER_CLASS);
+    }
+
+    private void logBootEvents(Context ctx) throws IOException {
+        final DropBoxManager db = (DropBoxManager) ctx.getSystemService(Context.DROPBOX_SERVICE);
+        final SharedPreferences prefs = ctx.getSharedPreferences("log_files", Context.MODE_PRIVATE);
+        final String headers = new StringBuilder(512)
+            .append("Build: ").append(Build.FINGERPRINT).append("\n")
+            .append("Hardware: ").append(Build.BOARD).append("\n")
+            .append("Revision: ")
+            .append(SystemProperties.get("ro.revision", "")).append("\n")
+            .append("Bootloader: ").append(Build.BOOTLOADER).append("\n")
+            .append("Radio: ").append(Build.RADIO).append("\n")
+            .append("Kernel: ")
+            .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n"))
+            .append("\n").toString();
+
+        String recovery = RecoverySystem.handleAftermath();
+        if (recovery != null && db != null) {
+            db.addText("SYSTEM_RECOVERY_LOG", headers + recovery);
+        }
+
+        if (SystemProperties.getLong("ro.runtime.firstboot", 0) == 0) {
+            String now = Long.toString(System.currentTimeMillis());
+            SystemProperties.set("ro.runtime.firstboot", now);
+            if (db != null) db.addText("SYSTEM_BOOT", headers);
+
+            // Negative sizes mean to take the *tail* of the file (see FileUtils.readTextFile())
+            addFileToDropBox(db, prefs, headers, "/proc/last_kmsg",
+                    -LOG_SIZE, "SYSTEM_LAST_KMSG");
+            addFileToDropBox(db, prefs, headers, "/sys/fs/pstore/console-ramoops",
+                    -LOG_SIZE, "SYSTEM_LAST_KMSG");
+            addFileToDropBox(db, prefs, headers, "/cache/recovery/log",
+                    -LOG_SIZE, "SYSTEM_RECOVERY_LOG");
+            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_console",
+                    -LOG_SIZE, "APANIC_CONSOLE");
+            addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads",
+                    -LOG_SIZE, "APANIC_THREADS");
+            addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT");
+            addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK");
+        } else {
+            if (db != null) db.addText("SYSTEM_RESTART", headers);
+        }
+
+        // Scan existing tombstones (in case any new ones appeared)
+        File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
+        for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
+            addFileToDropBox(db, prefs, headers, tombstoneFiles[i].getPath(),
+                    LOG_SIZE, "SYSTEM_TOMBSTONE");
+        }
+
+        // Start watching for new tombstone files; will record them as they occur.
+        // This gets registered with the singleton file observer thread.
+        sTombstoneObserver = new FileObserver(TOMBSTONE_DIR.getPath(), FileObserver.CLOSE_WRITE) {
+            @Override
+            public void onEvent(int event, String path) {
+                try {
+                    String filename = new File(TOMBSTONE_DIR, path).getPath();
+                    addFileToDropBox(db, prefs, headers, filename, LOG_SIZE, "SYSTEM_TOMBSTONE");
+                } catch (IOException e) {
+                    Slog.e(TAG, "Can't log tombstone", e);
+                }
+            }
+        };
+
+        sTombstoneObserver.startWatching();
+    }
+
+    private static void addFileToDropBox(
+            DropBoxManager db, SharedPreferences prefs,
+            String headers, String filename, int maxSize, String tag) throws IOException {
+        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
+
+        File file = new File(filename);
+        long fileTime = file.lastModified();
+        if (fileTime <= 0) return;  // File does not exist
+
+        if (prefs != null) {
+            long lastTime = prefs.getLong(filename, 0);
+            if (lastTime == fileTime) return;  // Already logged this particular file
+            // TODO: move all these SharedPreferences Editor commits
+            // outside this function to the end of logBootEvents
+            prefs.edit().putLong(filename, fileTime).apply();
+        }
+
+        Slog.i(TAG, "Copying " + filename + " to DropBox (" + tag + ")");
+        db.addText(tag, headers + FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"));
+    }
+
+    private static void addAuditErrorsToDropBox(DropBoxManager db,  SharedPreferences prefs,
+            String headers, int maxSize, String tag) throws IOException {
+        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
+        Slog.i(TAG, "Copying audit failures to DropBox");
+
+        File file = new File("/proc/last_kmsg");
+        long fileTime = file.lastModified();
+        if (fileTime <= 0) {
+            file = new File("/sys/fs/pstore/console-ramoops");
+            fileTime = file.lastModified();
+        }
+
+        if (fileTime <= 0) return;  // File does not exist
+
+        if (prefs != null) {
+            long lastTime = prefs.getLong(tag, 0);
+            if (lastTime == fileTime) return;  // Already logged this particular file
+            // TODO: move all these SharedPreferences Editor commits
+            // outside this function to the end of logBootEvents
+            prefs.edit().putLong(tag, fileTime).apply();
+        }
+
+        String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
+        StringBuilder sb = new StringBuilder();
+        for (String line : log.split("\n")) {
+            if (line.contains("audit")) {
+                sb.append(line + "\n");
+            }
+        }
+        Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox");
+        db.addText(tag, headers + sb.toString());
+    }
+
+    private static void addFsckErrorsToDropBox(DropBoxManager db,  SharedPreferences prefs,
+            String headers, int maxSize, String tag) throws IOException {
+        boolean upload_needed = false;
+        if (db == null || !db.isTagEnabled(tag)) return;  // Logging disabled
+        Slog.i(TAG, "Checking for fsck errors");
+
+        File file = new File("/dev/fscklogs/log");
+        long fileTime = file.lastModified();
+        if (fileTime <= 0) return;  // File does not exist
+
+        String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n");
+        StringBuilder sb = new StringBuilder();
+        for (String line : log.split("\n")) {
+            if (line.contains("FILE SYSTEM WAS MODIFIED")) {
+                upload_needed = true;
+                break;
+            }
+        }
+
+        if (upload_needed) {
+            addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag);
+        }
+
+        // Remove the file so we don't re-upload if the runtime restarts.
+        file.delete();
+    }
+}
diff --git a/services/core/java/com/android/server/BrickReceiver.java b/services/core/java/com/android/server/BrickReceiver.java
new file mode 100644
index 0000000..cff3805
--- /dev/null
+++ b/services/core/java/com/android/server/BrickReceiver.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.BroadcastReceiver;
+import android.os.SystemService;
+import android.util.Slog;
+
+public class BrickReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Slog.w("BrickReceiver", "!!! BRICKING DEVICE !!!");
+        SystemService.start("brick");
+    }
+}
diff --git a/services/core/java/com/android/server/CertBlacklister.java b/services/core/java/com/android/server/CertBlacklister.java
new file mode 100644
index 0000000..8b167d7
--- /dev/null
+++ b/services/core/java/com/android/server/CertBlacklister.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.provider.Settings;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+
+/**
+ * <p>CertBlacklister provides a simple mechanism for updating the platform blacklists for SSL
+ * certificate public keys and serial numbers.
+ */
+public class CertBlacklister extends Binder {
+
+    private static final String TAG = "CertBlacklister";
+
+    private static final String BLACKLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+    public static final String PUBKEY_PATH = BLACKLIST_ROOT + "pubkey_blacklist.txt";
+    public static final String SERIAL_PATH = BLACKLIST_ROOT + "serial_blacklist.txt";
+
+    public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist";
+    public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist";
+
+    private static class BlacklistObserver extends ContentObserver {
+
+        private final String mKey;
+        private final String mName;
+        private final String mPath;
+        private final File mTmpDir;
+        private final ContentResolver mContentResolver;
+
+        public BlacklistObserver(String key, String name, String path, ContentResolver cr) {
+            super(null);
+            mKey = key;
+            mName = name;
+            mPath = path;
+            mTmpDir = new File(mPath).getParentFile();
+            mContentResolver = cr;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            writeBlacklist();
+        }
+
+        public String getValue() {
+            return Settings.Secure.getString(mContentResolver, mKey);
+        }
+
+        private void writeBlacklist() {
+            new Thread("BlacklistUpdater") {
+                public void run() {
+                    synchronized(mTmpDir) {
+                        String blacklist = getValue();
+                        if (blacklist != null) {
+                            Slog.i(TAG, "Certificate blacklist changed, updating...");
+                            FileOutputStream out = null;
+                            try {
+                                // create a temporary file
+                                File tmp = File.createTempFile("journal", "", mTmpDir);
+                                // mark it -rw-r--r--
+                                tmp.setReadable(true, false);
+                                // write to it
+                                out = new FileOutputStream(tmp);
+                                out.write(blacklist.getBytes());
+                                // sync to disk
+                                FileUtils.sync(out);
+                                // atomic rename
+                                tmp.renameTo(new File(mPath));
+                                Slog.i(TAG, "Certificate blacklist updated");
+                            } catch (IOException e) {
+                                Slog.e(TAG, "Failed to write blacklist", e);
+                            } finally {
+                                IoUtils.closeQuietly(out);
+                            }
+                        }
+                    }
+                }
+            }.start();
+        }
+    }
+
+    public CertBlacklister(Context context) {
+        registerObservers(context.getContentResolver());
+    }
+
+    private BlacklistObserver buildPubkeyObserver(ContentResolver cr) {
+        return new BlacklistObserver(PUBKEY_BLACKLIST_KEY,
+                    "pubkey",
+                    PUBKEY_PATH,
+                    cr);
+    }
+
+    private BlacklistObserver buildSerialObserver(ContentResolver cr) {
+        return new BlacklistObserver(SERIAL_BLACKLIST_KEY,
+                    "serial",
+                    SERIAL_PATH,
+                    cr);
+    }
+
+    private void registerObservers(ContentResolver cr) {
+        // set up the public key blacklist observer
+        cr.registerContentObserver(
+            Settings.Secure.getUriFor(PUBKEY_BLACKLIST_KEY),
+            true,
+            buildPubkeyObserver(cr)
+        );
+
+        // set up the serial number blacklist observer
+        cr.registerContentObserver(
+            Settings.Secure.getUriFor(SERIAL_BLACKLIST_KEY),
+            true,
+            buildSerialObserver(cr)
+        );
+    }
+}
diff --git a/services/core/java/com/android/server/CommonTimeManagementService.java b/services/core/java/com/android/server/CommonTimeManagementService.java
new file mode 100644
index 0000000..710fb9d
--- /dev/null
+++ b/services/core/java/com/android/server/CommonTimeManagementService.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
+import android.net.NetworkInfo;
+import android.os.Binder;
+import android.os.CommonTimeConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.server.net.BaseNetworkObserver;
+
+/**
+ * @hide
+ * <p>CommonTimeManagementService manages the configuration of the native Common Time service,
+ * reconfiguring the native service as appropriate in response to changes in network configuration.
+ */
+class CommonTimeManagementService extends Binder {
+    /*
+     * Constants and globals.
+     */
+    private static final String TAG = CommonTimeManagementService.class.getSimpleName();
+    private static final int NATIVE_SERVICE_RECONNECT_TIMEOUT = 5000;
+    private static final String AUTO_DISABLE_PROP = "ro.common_time.auto_disable";
+    private static final String ALLOW_WIFI_PROP = "ro.common_time.allow_wifi";
+    private static final String SERVER_PRIO_PROP = "ro.common_time.server_prio";
+    private static final String NO_INTERFACE_TIMEOUT_PROP = "ro.common_time.no_iface_timeout";
+    private static final boolean AUTO_DISABLE;
+    private static final boolean ALLOW_WIFI;
+    private static final byte BASE_SERVER_PRIO;
+    private static final int NO_INTERFACE_TIMEOUT;
+    private static final InterfaceScoreRule[] IFACE_SCORE_RULES;
+
+    static {
+        int tmp;
+        AUTO_DISABLE         = (0 != SystemProperties.getInt(AUTO_DISABLE_PROP, 1));
+        ALLOW_WIFI           = (0 != SystemProperties.getInt(ALLOW_WIFI_PROP, 0));
+        tmp                  = SystemProperties.getInt(SERVER_PRIO_PROP, 1);
+        NO_INTERFACE_TIMEOUT = SystemProperties.getInt(NO_INTERFACE_TIMEOUT_PROP, 60000);
+
+        if (tmp < 1)
+            BASE_SERVER_PRIO = 1;
+        else
+        if (tmp > 30)
+            BASE_SERVER_PRIO = 30;
+        else
+            BASE_SERVER_PRIO = (byte)tmp;
+
+        if (ALLOW_WIFI) {
+            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+                new InterfaceScoreRule("wlan", (byte)1),
+                new InterfaceScoreRule("eth", (byte)2),
+            };
+        } else {
+            IFACE_SCORE_RULES = new InterfaceScoreRule[] {
+                new InterfaceScoreRule("eth", (byte)2),
+            };
+        }
+    };
+
+    /*
+     * Internal state
+     */
+    private final Context mContext;
+    private INetworkManagementService mNetMgr;
+    private CommonTimeConfig mCTConfig;
+    private String mCurIface;
+    private Handler mReconnectHandler = new Handler();
+    private Handler mNoInterfaceHandler = new Handler();
+    private Object mLock = new Object();
+    private boolean mDetectedAtStartup = false;
+    private byte mEffectivePrio = BASE_SERVER_PRIO;
+
+    /*
+     * Callback handler implementations.
+     */
+    private INetworkManagementEventObserver mIfaceObserver = new BaseNetworkObserver() {
+        public void interfaceStatusChanged(String iface, boolean up) {
+            reevaluateServiceState();
+        }
+        public void interfaceLinkStateChanged(String iface, boolean up) {
+            reevaluateServiceState();
+        }
+        public void interfaceAdded(String iface) {
+            reevaluateServiceState();
+        }
+        public void interfaceRemoved(String iface) {
+            reevaluateServiceState();
+        }
+    };
+
+    private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reevaluateServiceState();
+        }
+    };
+
+    private CommonTimeConfig.OnServerDiedListener mCTServerDiedListener =
+        new CommonTimeConfig.OnServerDiedListener() {
+            public void onServerDied() {
+                scheduleTimeConfigReconnect();
+            }
+        };
+
+    private Runnable mReconnectRunnable = new Runnable() {
+        public void run() { connectToTimeConfig(); }
+    };
+
+    private Runnable mNoInterfaceRunnable = new Runnable() {
+        public void run() { handleNoInterfaceTimeout(); }
+    };
+
+    /*
+     * Public interface (constructor, systemReady and dump)
+     */
+    public CommonTimeManagementService(Context context) {
+        mContext = context;
+    }
+
+    void systemRunning() {
+        if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) {
+            Log.i(TAG, "No common time service detected on this platform.  " +
+                       "Common time services will be unavailable.");
+            return;
+        }
+
+        mDetectedAtStartup = true;
+
+        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+        mNetMgr = INetworkManagementService.Stub.asInterface(b);
+
+        // Network manager is running along-side us, so we should never receiver a remote exception
+        // while trying to register this observer.
+        try {
+            mNetMgr.registerObserver(mIfaceObserver);
+        }
+        catch (RemoteException e) { }
+
+        // Register with the connectivity manager for connectivity changed intents.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnectivityMangerObserver, filter);
+
+        // Connect to the common time config service and apply the initial configuration.
+        connectToTimeConfig();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println(String.format(
+                        "Permission Denial: can't dump CommonTimeManagement service from from " +
+                        "pid=%d, uid=%d", Binder.getCallingPid(), Binder.getCallingUid()));
+            return;
+        }
+
+        if (!mDetectedAtStartup) {
+            pw.println("Native Common Time service was not detected at startup.  " +
+                       "Service is unavailable");
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("Current Common Time Management Service Config:");
+            pw.println(String.format("  Native service     : %s",
+                                     (null == mCTConfig) ? "reconnecting"
+                                                         : "alive"));
+            pw.println(String.format("  Bound interface    : %s",
+                                     (null == mCurIface ? "unbound" : mCurIface)));
+            pw.println(String.format("  Allow WiFi         : %s", ALLOW_WIFI ? "yes" : "no"));
+            pw.println(String.format("  Allow Auto Disable : %s", AUTO_DISABLE ? "yes" : "no"));
+            pw.println(String.format("  Server Priority    : %d", mEffectivePrio));
+            pw.println(String.format("  No iface timeout   : %d", NO_INTERFACE_TIMEOUT));
+        }
+    }
+
+    /*
+     * Inner helper classes
+     */
+    private static class InterfaceScoreRule {
+        public final String mPrefix;
+        public final byte mScore;
+        public InterfaceScoreRule(String prefix, byte score) {
+            mPrefix = prefix;
+            mScore = score;
+        }
+    };
+
+    /*
+     * Internal implementation
+     */
+    private void cleanupTimeConfig() {
+        mReconnectHandler.removeCallbacks(mReconnectRunnable);
+        mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+        if (null != mCTConfig) {
+            mCTConfig.release();
+            mCTConfig = null;
+        }
+    }
+
+    private void connectToTimeConfig() {
+        // Get access to the common time service configuration interface.  If we catch a remote
+        // exception in the process (service crashed or no running for w/e reason), schedule an
+        // attempt to reconnect in the future.
+        cleanupTimeConfig();
+        try {
+            synchronized (mLock) {
+                mCTConfig = new CommonTimeConfig();
+                mCTConfig.setServerDiedListener(mCTServerDiedListener);
+                mCurIface = mCTConfig.getInterfaceBinding();
+                mCTConfig.setAutoDisable(AUTO_DISABLE);
+                mCTConfig.setMasterElectionPriority(mEffectivePrio);
+            }
+
+            if (NO_INTERFACE_TIMEOUT >= 0)
+                mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+
+            reevaluateServiceState();
+        }
+        catch (RemoteException e) {
+            scheduleTimeConfigReconnect();
+        }
+    }
+
+    private void scheduleTimeConfigReconnect() {
+        cleanupTimeConfig();
+        Log.w(TAG, String.format("Native service died, will reconnect in %d mSec",
+                                 NATIVE_SERVICE_RECONNECT_TIMEOUT));
+        mReconnectHandler.postDelayed(mReconnectRunnable,
+                                      NATIVE_SERVICE_RECONNECT_TIMEOUT);
+    }
+
+    private void handleNoInterfaceTimeout() {
+        if (null != mCTConfig) {
+            Log.i(TAG, "Timeout waiting for interface to come up.  " +
+                       "Forcing networkless master mode.");
+            if (CommonTimeConfig.ERROR_DEAD_OBJECT == mCTConfig.forceNetworklessMasterMode())
+                scheduleTimeConfigReconnect();
+        }
+    }
+
+    private void reevaluateServiceState() {
+        String bindIface = null;
+        byte bestScore = -1;
+        try {
+            // Check to see if this interface is suitable to use for time synchronization.
+            //
+            // TODO : This selection algorithm needs to be enhanced for use with mobile devices.  In
+            // particular, the choice of whether to a wireless interface or not should not be an all
+            // or nothing thing controlled by properties.  It would probably be better if the
+            // platform had some concept of public wireless networks vs. home or friendly wireless
+            // networks (something a user would configure in settings or when a new interface is
+            // added).  Then this algorithm could pick only wireless interfaces which were flagged
+            // as friendly, and be dormant when on public wireless networks.
+            //
+            // Another issue which needs to be dealt with is the use of driver supplied interface
+            // name to determine the network type.  The fact that the wireless interface on a device
+            // is named "wlan0" is just a matter of convention; its not a 100% rule.  For example,
+            // there are devices out there where the wireless is name "tiwlan0", not "wlan0".  The
+            // internal network management interfaces in Android have all of the information needed
+            // to make a proper classification, there is just no way (currently) to fetch an
+            // interface's type (available from the ConnectionManager) as well as its address
+            // (available from either the java.net interfaces or from the NetworkManagment service).
+            // Both can enumerate interfaces, but that is no way to correlate their results (no
+            // common shared key; although using the interface name in the connection manager would
+            // be a good start).  Until this gets resolved, we resort to substring searching for
+            // tags like wlan and eth.
+            //
+            String ifaceList[] = mNetMgr.listInterfaces();
+            if (null != ifaceList) {
+                for (String iface : ifaceList) {
+
+                    byte thisScore = -1;
+                    for (InterfaceScoreRule r : IFACE_SCORE_RULES) {
+                        if (iface.contains(r.mPrefix)) {
+                            thisScore = r.mScore;
+                            break;
+                        }
+                    }
+
+                    if (thisScore <= bestScore)
+                        continue;
+
+                    InterfaceConfiguration config = mNetMgr.getInterfaceConfig(iface);
+                    if (null == config)
+                        continue;
+
+                    if (config.isActive()) {
+                        bindIface = iface;
+                        bestScore = thisScore;
+                    }
+                }
+            }
+        }
+        catch (RemoteException e) {
+            // Bad news; we should not be getting remote exceptions from the connectivity manager
+            // since it is running in SystemServer along side of us.  It probably does not matter
+            // what we do here, but go ahead and unbind the common time service in this case, just
+            // so we have some defined behavior.
+            bindIface = null;
+        }
+
+        boolean doRebind = true;
+        synchronized (mLock) {
+            if ((null != bindIface) && (null == mCurIface)) {
+                Log.e(TAG, String.format("Binding common time service to %s.", bindIface));
+                mCurIface = bindIface;
+            } else
+            if ((null == bindIface) && (null != mCurIface)) {
+                Log.e(TAG, "Unbinding common time service.");
+                mCurIface = null;
+            } else
+            if ((null != bindIface) && (null != mCurIface) && !bindIface.equals(mCurIface)) {
+                Log.e(TAG, String.format("Switching common time service binding from %s to %s.",
+                                         mCurIface, bindIface));
+                mCurIface = bindIface;
+            } else {
+                doRebind = false;
+            }
+        }
+
+        if (doRebind && (null != mCTConfig)) {
+            byte newPrio = (bestScore > 0)
+                         ? (byte)(bestScore * BASE_SERVER_PRIO)
+                         : BASE_SERVER_PRIO;
+            if (newPrio != mEffectivePrio) {
+                mEffectivePrio = newPrio;
+                mCTConfig.setMasterElectionPriority(mEffectivePrio);
+            }
+
+            int res = mCTConfig.setNetworkBinding(mCurIface);
+            if (res != CommonTimeConfig.SUCCESS)
+                scheduleTimeConfigReconnect();
+
+            else if (NO_INTERFACE_TIMEOUT >= 0) {
+                mNoInterfaceHandler.removeCallbacks(mNoInterfaceRunnable);
+                if (null == mCurIface)
+                    mNoInterfaceHandler.postDelayed(mNoInterfaceRunnable, NO_INTERFACE_TIMEOUT);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
new file mode 100644
index 0000000..d42ae3a
--- /dev/null
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -0,0 +1,4930 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_DUMMY;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothTetheringDataTracker;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
+import android.net.ConnectivityManager;
+import android.net.DummyDataStateTracker;
+import android.net.EthernetDataTracker;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.LinkProperties.CompareResult;
+import android.net.LinkQualityInfo;
+import android.net.MobileDataStateTracker;
+import android.net.NetworkConfig;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.Proxy;
+import android.net.ProxyProperties;
+import android.net.RouteInfo;
+import android.net.SamplingDataTracker;
+import android.net.Uri;
+import android.net.wifi.WifiStateTracker;
+import android.net.wimax.WimaxManagerConstants;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.PacManager;
+import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.net.LockdownVpnTracker;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import dalvik.system.DexClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
+/**
+ * @hide
+ */
+public class ConnectivityService extends IConnectivityManager.Stub {
+    private static final String TAG = "ConnectivityService";
+
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    private static final boolean LOGD_RULES = false;
+
+    // TODO: create better separation between radio types and network types
+
+    // how long to wait before switching back to a radio's default network
+    private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000;
+    // system property that can override the above value
+    private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
+            "android.telephony.apn-restore";
+
+    // Default value if FAIL_FAST_TIME_MS is not set
+    private static final int DEFAULT_FAIL_FAST_TIME_MS = 1 * 60 * 1000;
+    // system property that can override DEFAULT_FAIL_FAST_TIME_MS
+    private static final String FAIL_FAST_TIME_MS =
+            "persist.radio.fail_fast_time_ms";
+
+    private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED =
+            "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED";
+
+    private static final int SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE = 0;
+
+    private PendingIntent mSampleIntervalElapsedIntent;
+
+    // Set network sampling interval at 12 minutes, this way, even if the timers get
+    // aggregated, it will fire at around 15 minutes, which should allow us to
+    // aggregate this timer with other timers (specially the socket keep alive timers)
+    private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60);
+
+    // start network sampling a minute after booting ...
+    private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60);
+
+    AlarmManager mAlarmManager;
+
+    // used in recursive route setting to add gateways for the host for which
+    // a host route was requested.
+    private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
+
+    private Tethering mTethering;
+
+    private KeyStore mKeyStore;
+
+    @GuardedBy("mVpns")
+    private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+    private VpnCallback mVpnCallback = new VpnCallback();
+
+    private boolean mLockdownEnabled;
+    private LockdownVpnTracker mLockdownTracker;
+
+    private Nat464Xlat mClat;
+
+    /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
+    private Object mRulesLock = new Object();
+    /** Currently active network rules by UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
+    /** Set of ifaces that are costly. */
+    private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+
+    /**
+     * Sometimes we want to refer to the individual network state
+     * trackers separately, and sometimes we just want to treat them
+     * abstractly.
+     */
+    private NetworkStateTracker mNetTrackers[];
+
+    /* Handles captive portal check on a network */
+    private CaptivePortalTracker mCaptivePortalTracker;
+
+    /**
+     * The link properties that define the current links
+     */
+    private LinkProperties mCurrentLinkProperties[];
+
+    /**
+     * A per Net list of the PID's that requested access to the net
+     * used both as a refcount and for per-PID DNS selection
+     */
+    private List<Integer> mNetRequestersPids[];
+
+    // priority order of the nettrackers
+    // (excluding dynamically set mNetworkPreference)
+    // TODO - move mNetworkTypePreference into this
+    private int[] mPriorityList;
+
+    private Context mContext;
+    private int mNetworkPreference;
+    private int mActiveDefaultNetwork = -1;
+    // 0 is full bad, 100 is full good
+    private int mDefaultInetCondition = 0;
+    private int mDefaultInetConditionPublished = 0;
+    private boolean mInetConditionChangeInFlight = false;
+    private int mDefaultConnectionSequence = 0;
+
+    private Object mDnsLock = new Object();
+    private int mNumDnsEntries;
+
+    private boolean mTestMode;
+    private static ConnectivityService sServiceInstance;
+
+    private INetworkManagementService mNetd;
+    private INetworkPolicyManager mPolicyManager;
+
+    private static final int ENABLED  = 1;
+    private static final int DISABLED = 0;
+
+    private static final boolean ADD = true;
+    private static final boolean REMOVE = false;
+
+    private static final boolean TO_DEFAULT_TABLE = true;
+    private static final boolean TO_SECONDARY_TABLE = false;
+
+    private static final boolean EXEMPT = true;
+    private static final boolean UNEXEMPT = false;
+
+    /**
+     * used internally as a delayed event to make us switch back to the
+     * default network
+     */
+    private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1;
+
+    /**
+     * used internally to change our mobile data enabled flag
+     */
+    private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
+
+    /**
+     * used internally to change our network preference setting
+     * arg1 = networkType to prefer
+     */
+    private static final int EVENT_SET_NETWORK_PREFERENCE = 3;
+
+    /**
+     * used internally to synchronize inet condition reports
+     * arg1 = networkType
+     * arg2 = condition (0 bad, 100 good)
+     */
+    private static final int EVENT_INET_CONDITION_CHANGE = 4;
+
+    /**
+     * used internally to mark the end of inet condition hold periods
+     * arg1 = networkType
+     */
+    private static final int EVENT_INET_CONDITION_HOLD_END = 5;
+
+    /**
+     * used internally to set enable/disable cellular data
+     * arg1 = ENBALED or DISABLED
+     */
+    private static final int EVENT_SET_MOBILE_DATA = 7;
+
+    /**
+     * used internally to clear a wakelock when transitioning
+     * from one net to another
+     */
+    private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
+
+    /**
+     * used internally to reload global proxy settings
+     */
+    private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
+
+    /**
+     * used internally to set external dependency met/unmet
+     * arg1 = ENABLED (met) or DISABLED (unmet)
+     * arg2 = NetworkType
+     */
+    private static final int EVENT_SET_DEPENDENCY_MET = 10;
+
+    /**
+     * used internally to send a sticky broadcast delayed.
+     */
+    private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
+
+    /**
+     * Used internally to
+     * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
+     */
+    private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
+
+    private static final int EVENT_VPN_STATE_CHANGED = 13;
+
+    /**
+     * Used internally to disable fail fast of mobile data
+     */
+    private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
+
+    /**
+     * user internally to indicate that data sampling interval is up
+     */
+    private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
+
+    /**
+     * PAC manager has received new port.
+     */
+    private static final int EVENT_PROXY_HAS_CHANGED = 16;
+
+    /** Handler used for internal events. */
+    private InternalHandler mHandler;
+    /** Handler used for incoming {@link NetworkStateTracker} events. */
+    private NetworkStateTrackerHandler mTrackerHandler;
+
+    // list of DeathRecipients used to make sure features are turned off when
+    // a process dies
+    private List<FeatureUser> mFeatureUsers;
+
+    private boolean mSystemReady;
+    private Intent mInitialBroadcast;
+
+    private PowerManager.WakeLock mNetTransitionWakeLock;
+    private String mNetTransitionWakeLockCausedBy = "";
+    private int mNetTransitionWakeLockSerialNumber;
+    private int mNetTransitionWakeLockTimeout;
+
+    private InetAddress mDefaultDns;
+
+    // Lock for protecting access to mAddedRoutes and mExemptAddresses
+    private final Object mRoutesLock = new Object();
+
+    // this collection is used to refcount the added routes - if there are none left
+    // it's time to remove the route from the route table
+    @GuardedBy("mRoutesLock")
+    private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
+
+    // this collection corresponds to the entries of mAddedRoutes that have routing exemptions
+    // used to handle cleanup of exempt rules
+    @GuardedBy("mRoutesLock")
+    private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
+
+    // used in DBG mode to track inet condition reports
+    private static final int INET_CONDITION_LOG_MAX_SIZE = 15;
+    private ArrayList mInetLog;
+
+    // track the current default http proxy - tell the world if we get a new one (real change)
+    private ProxyProperties mDefaultProxy = null;
+    private Object mProxyLock = new Object();
+    private boolean mDefaultProxyDisabled = false;
+
+    // track the global proxy.
+    private ProxyProperties mGlobalProxy = null;
+
+    private PacManager mPacManager = null;
+
+    private SettingsObserver mSettingsObserver;
+
+    NetworkConfig[] mNetConfigs;
+    int mNetworksDefined;
+
+    private static class RadioAttributes {
+        public int mSimultaneity;
+        public int mType;
+        public RadioAttributes(String init) {
+            String fragments[] = init.split(",");
+            mType = Integer.parseInt(fragments[0]);
+            mSimultaneity = Integer.parseInt(fragments[1]);
+        }
+    }
+    RadioAttributes[] mRadioAttributes;
+
+    // the set of network types that can only be enabled by system/sig apps
+    List mProtectedNetworks;
+
+    private DataConnectionStats mDataConnectionStats;
+
+    private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+    TelephonyManager mTelephonyManager;
+
+    public ConnectivityService(Context context, INetworkManagementService netd,
+            INetworkStatsService statsService, INetworkPolicyManager policyManager) {
+        // Currently, omitting a NetworkFactory will create one internally
+        // TODO: create here when we have cleaner WiMAX support
+        this(context, netd, statsService, policyManager, null);
+    }
+
+    public ConnectivityService(Context context, INetworkManagementService netManager,
+            INetworkStatsService statsService, INetworkPolicyManager policyManager,
+            NetworkFactory netFactory) {
+        if (DBG) log("ConnectivityService starting up");
+
+        HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+        handlerThread.start();
+        mHandler = new InternalHandler(handlerThread.getLooper());
+        mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());
+
+        if (netFactory == null) {
+            netFactory = new DefaultNetworkFactory(context, mTrackerHandler);
+        }
+
+        // setup our unique device name
+        if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) {
+            String id = Settings.Secure.getString(context.getContentResolver(),
+                    Settings.Secure.ANDROID_ID);
+            if (id != null && id.length() > 0) {
+                String name = new String("android-").concat(id);
+                SystemProperties.set("net.hostname", name);
+            }
+        }
+
+        // read our default dns server ip
+        String dns = Settings.Global.getString(context.getContentResolver(),
+                Settings.Global.DEFAULT_DNS_SERVER);
+        if (dns == null || dns.length() == 0) {
+            dns = context.getResources().getString(
+                    com.android.internal.R.string.config_default_dns_server);
+        }
+        try {
+            mDefaultDns = NetworkUtils.numericToInetAddress(dns);
+        } catch (IllegalArgumentException e) {
+            loge("Error setting defaultDns using " + dns);
+        }
+
+        mContext = checkNotNull(context, "missing Context");
+        mNetd = checkNotNull(netManager, "missing INetworkManagementService");
+        mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+        mKeyStore = KeyStore.getInstance();
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+        try {
+            mPolicyManager.registerListener(mPolicyListener);
+        } catch (RemoteException e) {
+            // ouch, no rules updates means some processes may never get network
+            loge("unable to register INetworkPolicyListener" + e.toString());
+        }
+
+        final PowerManager powerManager = (PowerManager) context.getSystemService(
+                Context.POWER_SERVICE);
+        mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mNetTransitionWakeLockTimeout = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_networkTransitionTimeout);
+
+        mNetTrackers = new NetworkStateTracker[
+                ConnectivityManager.MAX_NETWORK_TYPE+1];
+        mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
+
+        mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
+        mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
+
+        // Load device network attributes from resources
+        String[] raStrings = context.getResources().getStringArray(
+                com.android.internal.R.array.radioAttributes);
+        for (String raString : raStrings) {
+            RadioAttributes r = new RadioAttributes(raString);
+            if (VDBG) log("raString=" + raString + " r=" + r);
+            if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) {
+                loge("Error in radioAttributes - ignoring attempt to define type " + r.mType);
+                continue;
+            }
+            if (mRadioAttributes[r.mType] != null) {
+                loge("Error in radioAttributes - ignoring attempt to redefine type " +
+                        r.mType);
+                continue;
+            }
+            mRadioAttributes[r.mType] = r;
+        }
+
+        // TODO: What is the "correct" way to do determine if this is a wifi only device?
+        boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+        log("wifiOnly=" + wifiOnly);
+        String[] naStrings = context.getResources().getStringArray(
+                com.android.internal.R.array.networkAttributes);
+        for (String naString : naStrings) {
+            try {
+                NetworkConfig n = new NetworkConfig(naString);
+                if (VDBG) log("naString=" + naString + " config=" + n);
+                if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
+                    loge("Error in networkAttributes - ignoring attempt to define type " +
+                            n.type);
+                    continue;
+                }
+                if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
+                    log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
+                            n.type);
+                    continue;
+                }
+                if (mNetConfigs[n.type] != null) {
+                    loge("Error in networkAttributes - ignoring attempt to redefine type " +
+                            n.type);
+                    continue;
+                }
+                if (mRadioAttributes[n.radio] == null) {
+                    loge("Error in networkAttributes - ignoring attempt to use undefined " +
+                            "radio " + n.radio + " in network type " + n.type);
+                    continue;
+                }
+                mNetConfigs[n.type] = n;
+                mNetworksDefined++;
+            } catch(Exception e) {
+                // ignore it - leave the entry null
+            }
+        }
+        if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
+
+        mProtectedNetworks = new ArrayList<Integer>();
+        int[] protectedNetworks = context.getResources().getIntArray(
+                com.android.internal.R.array.config_protectedNetworks);
+        for (int p : protectedNetworks) {
+            if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+                mProtectedNetworks.add(p);
+            } else {
+                if (DBG) loge("Ignoring protectedNetwork " + p);
+            }
+        }
+
+        // high priority first
+        mPriorityList = new int[mNetworksDefined];
+        {
+            int insertionPoint = mNetworksDefined-1;
+            int currentLowest = 0;
+            int nextLowest = 0;
+            while (insertionPoint > -1) {
+                for (NetworkConfig na : mNetConfigs) {
+                    if (na == null) continue;
+                    if (na.priority < currentLowest) continue;
+                    if (na.priority > currentLowest) {
+                        if (na.priority < nextLowest || nextLowest == 0) {
+                            nextLowest = na.priority;
+                        }
+                        continue;
+                    }
+                    mPriorityList[insertionPoint--] = na.type;
+                }
+                currentLowest = nextLowest;
+                nextLowest = 0;
+            }
+        }
+
+        // Update mNetworkPreference according to user mannually first then overlay config.xml
+        mNetworkPreference = getPersistedNetworkPreference();
+        if (mNetworkPreference == -1) {
+            for (int n : mPriorityList) {
+                if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) {
+                    mNetworkPreference = n;
+                    break;
+                }
+            }
+            if (mNetworkPreference == -1) {
+                throw new IllegalStateException(
+                        "You should set at least one default Network in config.xml!");
+            }
+        }
+
+        mNetRequestersPids =
+                (List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+        for (int i : mPriorityList) {
+            mNetRequestersPids[i] = new ArrayList<Integer>();
+        }
+
+        mFeatureUsers = new ArrayList<FeatureUser>();
+
+        mTestMode = SystemProperties.get("cm.test.mode").equals("true")
+                && SystemProperties.get("ro.build.type").equals("eng");
+
+        // Create and start trackers for hard-coded networks
+        for (int targetNetworkType : mPriorityList) {
+            final NetworkConfig config = mNetConfigs[targetNetworkType];
+            final NetworkStateTracker tracker;
+            try {
+                tracker = netFactory.createTracker(targetNetworkType, config);
+                mNetTrackers[targetNetworkType] = tracker;
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType)
+                        + " tracker: " + e);
+                continue;
+            }
+
+            tracker.startMonitoring(context, mTrackerHandler);
+            if (config.isDefault()) {
+                tracker.reconnect();
+            }
+        }
+
+        mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
+
+        //set up the listener for user state for creating user VPNs
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_STARTING);
+        intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+        mContext.registerReceiverAsUser(
+                mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+        mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
+
+        try {
+            mNetd.registerObserver(mTethering);
+            mNetd.registerObserver(mDataActivityObserver);
+            mNetd.registerObserver(mClat);
+        } catch (RemoteException e) {
+            loge("Error registering observer :" + e);
+        }
+
+        if (DBG) {
+            mInetLog = new ArrayList();
+        }
+
+        mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
+        mSettingsObserver.observe(mContext);
+
+        mDataConnectionStats = new DataConnectionStats(mContext);
+        mDataConnectionStats.startMonitoring();
+
+        // start network sampling ..
+        Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null);
+        mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext,
+                SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE, intent, 0);
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) {
+                            mHandler.sendMessage(mHandler.obtainMessage
+                                    (EVENT_SAMPLE_INTERVAL_ELAPSED));
+                        }
+                    }
+                },
+                new IntentFilter(filter));
+
+        mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+
+        filter = new IntentFilter();
+        filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+        mContext.registerReceiver(mProvisioningReceiver, filter);
+    }
+
+    /**
+     * Factory that creates {@link NetworkStateTracker} instances using given
+     * {@link NetworkConfig}.
+     */
+    public interface NetworkFactory {
+        public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
+    }
+
+    private static class DefaultNetworkFactory implements NetworkFactory {
+        private final Context mContext;
+        private final Handler mTrackerHandler;
+
+        public DefaultNetworkFactory(Context context, Handler trackerHandler) {
+            mContext = context;
+            mTrackerHandler = trackerHandler;
+        }
+
+        @Override
+        public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
+            switch (config.radio) {
+                case TYPE_WIFI:
+                    return new WifiStateTracker(targetNetworkType, config.name);
+                case TYPE_MOBILE:
+                    return new MobileDataStateTracker(targetNetworkType, config.name);
+                case TYPE_DUMMY:
+                    return new DummyDataStateTracker(targetNetworkType, config.name);
+                case TYPE_BLUETOOTH:
+                    return BluetoothTetheringDataTracker.getInstance();
+                case TYPE_WIMAX:
+                    return makeWimaxStateTracker(mContext, mTrackerHandler);
+                case TYPE_ETHERNET:
+                    return EthernetDataTracker.getInstance();
+                default:
+                    throw new IllegalArgumentException(
+                            "Trying to create a NetworkStateTracker for an unknown radio type: "
+                            + config.radio);
+            }
+        }
+    }
+
+    /**
+     * Loads external WiMAX library and registers as system service, returning a
+     * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for
+     * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}.
+     */
+    private static NetworkStateTracker makeWimaxStateTracker(
+            Context context, Handler trackerHandler) {
+        // Initialize Wimax
+        DexClassLoader wimaxClassLoader;
+        Class wimaxStateTrackerClass = null;
+        Class wimaxServiceClass = null;
+        Class wimaxManagerClass;
+        String wimaxJarLocation;
+        String wimaxLibLocation;
+        String wimaxManagerClassName;
+        String wimaxServiceClassName;
+        String wimaxStateTrackerClassName;
+
+        NetworkStateTracker wimaxStateTracker = null;
+
+        boolean isWimaxEnabled = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_wimaxEnabled);
+
+        if (isWimaxEnabled) {
+            try {
+                wimaxJarLocation = context.getResources().getString(
+                        com.android.internal.R.string.config_wimaxServiceJarLocation);
+                wimaxLibLocation = context.getResources().getString(
+                        com.android.internal.R.string.config_wimaxNativeLibLocation);
+                wimaxManagerClassName = context.getResources().getString(
+                        com.android.internal.R.string.config_wimaxManagerClassname);
+                wimaxServiceClassName = context.getResources().getString(
+                        com.android.internal.R.string.config_wimaxServiceClassname);
+                wimaxStateTrackerClassName = context.getResources().getString(
+                        com.android.internal.R.string.config_wimaxStateTrackerClassname);
+
+                if (DBG) log("wimaxJarLocation: " + wimaxJarLocation);
+                wimaxClassLoader =  new DexClassLoader(wimaxJarLocation,
+                        new ContextWrapper(context).getCacheDir().getAbsolutePath(),
+                        wimaxLibLocation, ClassLoader.getSystemClassLoader());
+
+                try {
+                    wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName);
+                    wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName);
+                    wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName);
+                } catch (ClassNotFoundException ex) {
+                    loge("Exception finding Wimax classes: " + ex.toString());
+                    return null;
+                }
+            } catch(Resources.NotFoundException ex) {
+                loge("Wimax Resources does not exist!!! ");
+                return null;
+            }
+
+            try {
+                if (DBG) log("Starting Wimax Service... ");
+
+                Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
+                        (new Class[] {Context.class, Handler.class});
+                wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
+                        context, trackerHandler);
+
+                Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
+                        (new Class[] {Context.class, wimaxStateTrackerClass});
+                wmxSrvConst.setAccessible(true);
+                IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
+                wmxSrvConst.setAccessible(false);
+
+                ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
+
+            } catch(Exception ex) {
+                loge("Exception creating Wimax classes: " + ex.toString());
+                return null;
+            }
+        } else {
+            loge("Wimax is not enabled or not added to the network attributes!!! ");
+            return null;
+        }
+
+        return wimaxStateTracker;
+    }
+
+    /**
+     * Sets the preferred network.
+     * @param preference the new preference
+     */
+    public void setNetworkPreference(int preference) {
+        enforceChangePermission();
+
+        mHandler.sendMessage(
+                mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
+    }
+
+    public int getNetworkPreference() {
+        enforceAccessPermission();
+        int preference;
+        synchronized(this) {
+            preference = mNetworkPreference;
+        }
+        return preference;
+    }
+
+    private void handleSetNetworkPreference(int preference) {
+        if (ConnectivityManager.isNetworkTypeValid(preference) &&
+                mNetConfigs[preference] != null &&
+                mNetConfigs[preference].isDefault()) {
+            if (mNetworkPreference != preference) {
+                final ContentResolver cr = mContext.getContentResolver();
+                Settings.Global.putInt(cr, Settings.Global.NETWORK_PREFERENCE, preference);
+                synchronized(this) {
+                    mNetworkPreference = preference;
+                }
+                enforcePreference();
+            }
+        }
+    }
+
+    private int getConnectivityChangeDelay() {
+        final ContentResolver cr = mContext.getContentResolver();
+
+        /** Check system properties for the default value then use secure settings value, if any. */
+        int defaultDelay = SystemProperties.getInt(
+                "conn." + Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+                ConnectivityManager.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
+        return Settings.Global.getInt(cr, Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+                defaultDelay);
+    }
+
+    private int getPersistedNetworkPreference() {
+        final ContentResolver cr = mContext.getContentResolver();
+
+        final int networkPrefSetting = Settings.Global
+                .getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
+
+        return networkPrefSetting;
+    }
+
+    /**
+     * Make the state of network connectivity conform to the preference settings
+     * In this method, we only tear down a non-preferred network. Establishing
+     * a connection to the preferred network is taken care of when we handle
+     * the disconnect event from the non-preferred network
+     * (see {@link #handleDisconnect(NetworkInfo)}).
+     */
+    private void enforcePreference() {
+        if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected())
+            return;
+
+        if (!mNetTrackers[mNetworkPreference].isAvailable())
+            return;
+
+        for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) {
+            if (t != mNetworkPreference && mNetTrackers[t] != null &&
+                    mNetTrackers[t].getNetworkInfo().isConnected()) {
+                if (DBG) {
+                    log("tearing down " + mNetTrackers[t].getNetworkInfo() +
+                            " in enforcePreference");
+                }
+                teardown(mNetTrackers[t]);
+            }
+        }
+    }
+
+    private boolean teardown(NetworkStateTracker netTracker) {
+        if (netTracker.teardown()) {
+            netTracker.setTeardownRequested(true);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Check if UID should be blocked from using the network represented by the
+     * given {@link NetworkStateTracker}.
+     */
+    private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
+        final String iface = tracker.getLinkProperties().getInterfaceName();
+
+        final boolean networkCostly;
+        final int uidRules;
+        synchronized (mRulesLock) {
+            networkCostly = mMeteredIfaces.contains(iface);
+            uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+        }
+
+        if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+            return true;
+        }
+
+        // no restrictive rules; network is visible
+        return false;
+    }
+
+    /**
+     * Return a filtered {@link NetworkInfo}, potentially marked
+     * {@link DetailedState#BLOCKED} based on
+     * {@link #isNetworkBlocked(NetworkStateTracker, int)}.
+     */
+    private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
+        NetworkInfo info = tracker.getNetworkInfo();
+        if (isNetworkBlocked(tracker, uid)) {
+            // network is blocked; clone and override state
+            info = new NetworkInfo(info);
+            info.setDetailedState(DetailedState.BLOCKED, null, null);
+        }
+        if (mLockdownTracker != null) {
+            info = mLockdownTracker.augmentNetworkInfo(info);
+        }
+        return info;
+    }
+
+    /**
+     * Return NetworkInfo for the active (i.e., connected) network interface.
+     * It is assumed that at most one network is active at a time. If more
+     * than one is active, it is indeterminate which will be returned.
+     * @return the info for the active network, or {@code null} if none is
+     * active
+     */
+    @Override
+    public NetworkInfo getActiveNetworkInfo() {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
+    }
+
+    /**
+     * Find the first Provisioning network.
+     *
+     * @return NetworkInfo or null if none.
+     */
+    private NetworkInfo getProvisioningNetworkInfo() {
+        enforceAccessPermission();
+
+        // Find the first Provisioning Network
+        NetworkInfo provNi = null;
+        for (NetworkInfo ni : getAllNetworkInfo()) {
+            if (ni.isConnectedToProvisioningNetwork()) {
+                provNi = ni;
+                break;
+            }
+        }
+        if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
+        return provNi;
+    }
+
+    /**
+     * Find the first Provisioning network or the ActiveDefaultNetwork
+     * if there is no Provisioning network
+     *
+     * @return NetworkInfo or null if none.
+     */
+    @Override
+    public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+        enforceAccessPermission();
+
+        NetworkInfo provNi = getProvisioningNetworkInfo();
+        if (provNi == null) {
+            final int uid = Binder.getCallingUid();
+            provNi = getNetworkInfo(mActiveDefaultNetwork, uid);
+        }
+        if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
+        return provNi;
+    }
+
+    public NetworkInfo getActiveNetworkInfoUnfiltered() {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+            final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
+            if (tracker != null) {
+                return tracker.getNetworkInfo();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+        enforceConnectivityInternalPermission();
+        return getNetworkInfo(mActiveDefaultNetwork, uid);
+    }
+
+    @Override
+    public NetworkInfo getNetworkInfo(int networkType) {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        return getNetworkInfo(networkType, uid);
+    }
+
+    private NetworkInfo getNetworkInfo(int networkType, int uid) {
+        NetworkInfo info = null;
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                info = getFilteredNetworkInfo(tracker, uid);
+            }
+        }
+        return info;
+    }
+
+    @Override
+    public NetworkInfo[] getAllNetworkInfo() {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        final ArrayList<NetworkInfo> result = Lists.newArrayList();
+        synchronized (mRulesLock) {
+            for (NetworkStateTracker tracker : mNetTrackers) {
+                if (tracker != null) {
+                    result.add(getFilteredNetworkInfo(tracker, uid));
+                }
+            }
+        }
+        return result.toArray(new NetworkInfo[result.size()]);
+    }
+
+    @Override
+    public boolean isNetworkSupported(int networkType) {
+        enforceAccessPermission();
+        return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
+    }
+
+    /**
+     * Return LinkProperties for the active (i.e., connected) default
+     * network interface.  It is assumed that at most one default network
+     * is active at a time. If more than one is active, it is indeterminate
+     * which will be returned.
+     * @return the ip properties for the active network, or {@code null} if
+     * none is active
+     */
+    @Override
+    public LinkProperties getActiveLinkProperties() {
+        return getLinkProperties(mActiveDefaultNetwork);
+    }
+
+    @Override
+    public LinkProperties getLinkProperties(int networkType) {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                return tracker.getLinkProperties();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkState[] getAllNetworkState() {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        final ArrayList<NetworkState> result = Lists.newArrayList();
+        synchronized (mRulesLock) {
+            for (NetworkStateTracker tracker : mNetTrackers) {
+                if (tracker != null) {
+                    final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
+                    result.add(new NetworkState(
+                            info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
+                }
+            }
+        }
+        return result.toArray(new NetworkState[result.size()]);
+    }
+
+    private NetworkState getNetworkStateUnchecked(int networkType) {
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
+                        tracker.getLinkCapabilities());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+        enforceAccessPermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+            if (state != null) {
+                try {
+                    return mPolicyManager.getNetworkQuotaInfo(state);
+                } catch (RemoteException e) {
+                }
+            }
+            return null;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public boolean isActiveNetworkMetered() {
+        enforceAccessPermission();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return isNetworkMeteredUnchecked(mActiveDefaultNetwork);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private boolean isNetworkMeteredUnchecked(int networkType) {
+        final NetworkState state = getNetworkStateUnchecked(networkType);
+        if (state != null) {
+            try {
+                return mPolicyManager.isNetworkMetered(state);
+            } catch (RemoteException e) {
+            }
+        }
+        return false;
+    }
+
+    public boolean setRadios(boolean turnOn) {
+        boolean result = true;
+        enforceChangePermission();
+        for (NetworkStateTracker t : mNetTrackers) {
+            if (t != null) result = t.setRadio(turnOn) && result;
+        }
+        return result;
+    }
+
+    public boolean setRadio(int netType, boolean turnOn) {
+        enforceChangePermission();
+        if (!ConnectivityManager.isNetworkTypeValid(netType)) {
+            return false;
+        }
+        NetworkStateTracker tracker = mNetTrackers[netType];
+        return tracker != null && tracker.setRadio(turnOn);
+    }
+
+    private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
+        @Override
+        public void interfaceClassDataActivityChanged(String label, boolean active) {
+            int deviceType = Integer.parseInt(label);
+            sendDataActivityBroadcast(deviceType, active);
+        }
+    };
+
+    /**
+     * Used to notice when the calling process dies so we can self-expire
+     *
+     * Also used to know if the process has cleaned up after itself when
+     * our auto-expire timer goes off.  The timer has a link to an object.
+     *
+     */
+    private class FeatureUser implements IBinder.DeathRecipient {
+        int mNetworkType;
+        String mFeature;
+        IBinder mBinder;
+        int mPid;
+        int mUid;
+        long mCreateTime;
+
+        FeatureUser(int type, String feature, IBinder binder) {
+            super();
+            mNetworkType = type;
+            mFeature = feature;
+            mBinder = binder;
+            mPid = getCallingPid();
+            mUid = getCallingUid();
+            mCreateTime = System.currentTimeMillis();
+
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
+        }
+
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+
+        public void binderDied() {
+            log("ConnectivityService FeatureUser binderDied(" +
+                    mNetworkType + ", " + mFeature + ", " + mBinder + "), created " +
+                    (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+            stopUsingNetworkFeature(this, false);
+        }
+
+        public void expire() {
+            if (VDBG) {
+                log("ConnectivityService FeatureUser expire(" +
+                        mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
+                        (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+            }
+            stopUsingNetworkFeature(this, false);
+        }
+
+        public boolean isSameUser(FeatureUser u) {
+            if (u == null) return false;
+
+            return isSameUser(u.mPid, u.mUid, u.mNetworkType, u.mFeature);
+        }
+
+        public boolean isSameUser(int pid, int uid, int networkType, String feature) {
+            if ((mPid == pid) && (mUid == uid) && (mNetworkType == networkType) &&
+                TextUtils.equals(mFeature, feature)) {
+                return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            return "FeatureUser("+mNetworkType+","+mFeature+","+mPid+","+mUid+"), created " +
+                    (System.currentTimeMillis() - mCreateTime) + " mSec ago";
+        }
+    }
+
+    // javadoc from interface
+    public int startUsingNetworkFeature(int networkType, String feature,
+            IBinder binder) {
+        long startTime = 0;
+        if (DBG) {
+            startTime = SystemClock.elapsedRealtime();
+        }
+        if (VDBG) {
+            log("startUsingNetworkFeature for net " + networkType + ": " + feature + ", uid="
+                    + Binder.getCallingUid());
+        }
+        enforceChangePermission();
+        try {
+            if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
+                    mNetConfigs[networkType] == null) {
+                return PhoneConstants.APN_REQUEST_FAILED;
+            }
+
+            FeatureUser f = new FeatureUser(networkType, feature, binder);
+
+            // TODO - move this into individual networktrackers
+            int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+            if (mLockdownEnabled) {
+                // Since carrier APNs usually aren't available from VPN
+                // endpoint, mark them as unavailable.
+                return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+            }
+
+            if (mProtectedNetworks.contains(usedNetworkType)) {
+                enforceConnectivityInternalPermission();
+            }
+
+            // if UID is restricted, don't allow them to bring up metered APNs
+            final boolean networkMetered = isNetworkMeteredUnchecked(usedNetworkType);
+            final int uidRules;
+            synchronized (mRulesLock) {
+                uidRules = mUidRules.get(Binder.getCallingUid(), RULE_ALLOW_ALL);
+            }
+            if (networkMetered && (uidRules & RULE_REJECT_METERED) != 0) {
+                return PhoneConstants.APN_REQUEST_FAILED;
+            }
+
+            NetworkStateTracker network = mNetTrackers[usedNetworkType];
+            if (network != null) {
+                Integer currentPid = new Integer(getCallingPid());
+                if (usedNetworkType != networkType) {
+                    NetworkInfo ni = network.getNetworkInfo();
+
+                    if (ni.isAvailable() == false) {
+                        if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+                            if (DBG) log("special network not available ni=" + ni.getTypeName());
+                            return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+                        } else {
+                            // else make the attempt anyway - probably giving REQUEST_STARTED below
+                            if (DBG) {
+                                log("special network not available, but try anyway ni=" +
+                                        ni.getTypeName());
+                            }
+                        }
+                    }
+
+                    int restoreTimer = getRestoreDefaultNetworkDelay(usedNetworkType);
+
+                    synchronized(this) {
+                        boolean addToList = true;
+                        if (restoreTimer < 0) {
+                            // In case there is no timer is specified for the feature,
+                            // make sure we don't add duplicate entry with the same request.
+                            for (FeatureUser u : mFeatureUsers) {
+                                if (u.isSameUser(f)) {
+                                    // Duplicate user is found. Do not add.
+                                    addToList = false;
+                                    break;
+                                }
+                            }
+                        }
+
+                        if (addToList) mFeatureUsers.add(f);
+                        if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+                            // this gets used for per-pid dns when connected
+                            mNetRequestersPids[usedNetworkType].add(currentPid);
+                        }
+                    }
+
+                    if (restoreTimer >= 0) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                EVENT_RESTORE_DEFAULT_NETWORK, f), restoreTimer);
+                    }
+
+                    if ((ni.isConnectedOrConnecting() == true) &&
+                            !network.isTeardownRequested()) {
+                        if (ni.isConnected() == true) {
+                            final long token = Binder.clearCallingIdentity();
+                            try {
+                                // add the pid-specific dns
+                                handleDnsConfigurationChange(usedNetworkType);
+                                if (VDBG) log("special network already active");
+                            } finally {
+                                Binder.restoreCallingIdentity(token);
+                            }
+                            return PhoneConstants.APN_ALREADY_ACTIVE;
+                        }
+                        if (VDBG) log("special network already connecting");
+                        return PhoneConstants.APN_REQUEST_STARTED;
+                    }
+
+                    // check if the radio in play can make another contact
+                    // assume if cannot for now
+
+                    if (DBG) {
+                        log("startUsingNetworkFeature reconnecting to " + networkType + ": " +
+                                feature);
+                    }
+                    if (network.reconnect()) {
+                        if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_STARTED");
+                        return PhoneConstants.APN_REQUEST_STARTED;
+                    } else {
+                        if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_FAILED");
+                        return PhoneConstants.APN_REQUEST_FAILED;
+                    }
+                } else {
+                    // need to remember this unsupported request so we respond appropriately on stop
+                    synchronized(this) {
+                        mFeatureUsers.add(f);
+                        if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+                            // this gets used for per-pid dns when connected
+                            mNetRequestersPids[usedNetworkType].add(currentPid);
+                        }
+                    }
+                    if (DBG) log("startUsingNetworkFeature X: return -1 unsupported feature.");
+                    return -1;
+                }
+            }
+            if (DBG) log("startUsingNetworkFeature X: return APN_TYPE_NOT_AVAILABLE");
+            return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+         } finally {
+            if (DBG) {
+                final long execTime = SystemClock.elapsedRealtime() - startTime;
+                if (execTime > 250) {
+                    loge("startUsingNetworkFeature took too long: " + execTime + "ms");
+                } else {
+                    if (VDBG) log("startUsingNetworkFeature took " + execTime + "ms");
+                }
+            }
+         }
+    }
+
+    // javadoc from interface
+    public int stopUsingNetworkFeature(int networkType, String feature) {
+        enforceChangePermission();
+
+        int pid = getCallingPid();
+        int uid = getCallingUid();
+
+        FeatureUser u = null;
+        boolean found = false;
+
+        synchronized(this) {
+            for (FeatureUser x : mFeatureUsers) {
+                if (x.isSameUser(pid, uid, networkType, feature)) {
+                    u = x;
+                    found = true;
+                    break;
+                }
+            }
+        }
+        if (found && u != null) {
+            if (VDBG) log("stopUsingNetworkFeature: X");
+            // stop regardless of how many other time this proc had called start
+            return stopUsingNetworkFeature(u, true);
+        } else {
+            // none found!
+            if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring");
+            return 1;
+        }
+    }
+
+    private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) {
+        int networkType = u.mNetworkType;
+        String feature = u.mFeature;
+        int pid = u.mPid;
+        int uid = u.mUid;
+
+        NetworkStateTracker tracker = null;
+        boolean callTeardown = false;  // used to carry our decision outside of sync block
+
+        if (VDBG) {
+            log("stopUsingNetworkFeature: net " + networkType + ": " + feature);
+        }
+
+        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+            if (DBG) {
+                log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+                        ", net is invalid");
+            }
+            return -1;
+        }
+
+        // need to link the mFeatureUsers list with the mNetRequestersPids state in this
+        // sync block
+        synchronized(this) {
+            // check if this process still has an outstanding start request
+            if (!mFeatureUsers.contains(u)) {
+                if (VDBG) {
+                    log("stopUsingNetworkFeature: this process has no outstanding requests" +
+                        ", ignoring");
+                }
+                return 1;
+            }
+            u.unlinkDeathRecipient();
+            mFeatureUsers.remove(mFeatureUsers.indexOf(u));
+            // If we care about duplicate requests, check for that here.
+            //
+            // This is done to support the extension of a request - the app
+            // can request we start the network feature again and renew the
+            // auto-shutoff delay.  Normal "stop" calls from the app though
+            // do not pay attention to duplicate requests - in effect the
+            // API does not refcount and a single stop will counter multiple starts.
+            if (ignoreDups == false) {
+                for (FeatureUser x : mFeatureUsers) {
+                    if (x.isSameUser(u)) {
+                        if (VDBG) log("stopUsingNetworkFeature: dup is found, ignoring");
+                        return 1;
+                    }
+                }
+            }
+
+            // TODO - move to individual network trackers
+            int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+            tracker =  mNetTrackers[usedNetworkType];
+            if (tracker == null) {
+                if (DBG) {
+                    log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+                            " no known tracker for used net type " + usedNetworkType);
+                }
+                return -1;
+            }
+            if (usedNetworkType != networkType) {
+                Integer currentPid = new Integer(pid);
+                mNetRequestersPids[usedNetworkType].remove(currentPid);
+
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    reassessPidDns(pid, true);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+                flushVmDnsCache();
+                if (mNetRequestersPids[usedNetworkType].size() != 0) {
+                    if (VDBG) {
+                        log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+                                " others still using it");
+                    }
+                    return 1;
+                }
+                callTeardown = true;
+            } else {
+                if (DBG) {
+                    log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+                            " not a known feature - dropping");
+                }
+            }
+        }
+
+        if (callTeardown) {
+            if (DBG) {
+                log("stopUsingNetworkFeature: teardown net " + networkType + ": " + feature);
+            }
+            tracker.teardown();
+            return 1;
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * @deprecated use requestRouteToHostAddress instead
+     *
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via the specified network interface.
+     * @param networkType the type of the network over which traffic to the
+     * specified host is to be routed
+     * @param hostAddress the IP address of the host to which the route is
+     * desired
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public boolean requestRouteToHost(int networkType, int hostAddress) {
+        InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress);
+
+        if (inetAddress == null) {
+            return false;
+        }
+
+        return requestRouteToHostAddress(networkType, inetAddress.getAddress());
+    }
+
+    /**
+     * Ensure that a network route exists to deliver traffic to the specified
+     * host via the specified network interface.
+     * @param networkType the type of the network over which traffic to the
+     * specified host is to be routed
+     * @param hostAddress the IP address of the host to which the route is
+     * desired
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public boolean requestRouteToHostAddress(int networkType, byte[] hostAddress) {
+        enforceChangePermission();
+        if (mProtectedNetworks.contains(networkType)) {
+            enforceConnectivityInternalPermission();
+        }
+
+        if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+            if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
+            return false;
+        }
+        NetworkStateTracker tracker = mNetTrackers[networkType];
+        DetailedState netState = DetailedState.DISCONNECTED;
+        if (tracker != null) {
+            netState = tracker.getNetworkInfo().getDetailedState();
+        }
+
+        if ((netState != DetailedState.CONNECTED &&
+                netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
+                tracker.isTeardownRequested()) {
+            if (VDBG) {
+                log("requestRouteToHostAddress on down network "
+                        + "(" + networkType + ") - dropped"
+                        + " tracker=" + tracker
+                        + " netState=" + netState
+                        + " isTeardownRequested="
+                            + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
+            }
+            return false;
+        }
+        final long token = Binder.clearCallingIdentity();
+        try {
+            InetAddress addr = InetAddress.getByAddress(hostAddress);
+            LinkProperties lp = tracker.getLinkProperties();
+            boolean ok = addRouteToAddress(lp, addr, EXEMPT);
+            if (DBG) log("requestRouteToHostAddress ok=" + ok);
+            return ok;
+        } catch (UnknownHostException e) {
+            if (DBG) log("requestRouteToHostAddress got " + e.toString());
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        if (DBG) log("requestRouteToHostAddress X bottom return false");
+        return false;
+    }
+
+    private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
+            boolean exempt) {
+        return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt);
+    }
+
+    private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
+        return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT);
+    }
+
+    private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) {
+        return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt);
+    }
+
+    private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
+        return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT);
+    }
+
+    private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
+            boolean toDefaultTable, boolean exempt) {
+        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
+        if (bestRoute == null) {
+            bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
+        } else {
+            String iface = bestRoute.getInterface();
+            if (bestRoute.getGateway().equals(addr)) {
+                // if there is no better route, add the implied hostroute for our gateway
+                bestRoute = RouteInfo.makeHostRoute(addr, iface);
+            } else {
+                // if we will connect to this through another route, add a direct route
+                // to it's gateway
+                bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
+            }
+        }
+        return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt);
+    }
+
+    private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+            boolean toDefaultTable, boolean exempt) {
+        if ((lp == null) || (r == null)) {
+            if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
+            return false;
+        }
+
+        if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
+            loge("Error modifying route - too much recursion");
+            return false;
+        }
+
+        String ifaceName = r.getInterface();
+        if(ifaceName == null) {
+            loge("Error modifying route - no interface name");
+            return false;
+        }
+        if (r.hasGateway()) {
+            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
+            if (bestRoute != null) {
+                if (bestRoute.getGateway().equals(r.getGateway())) {
+                    // if there is no better route, add the implied hostroute for our gateway
+                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(), ifaceName);
+                } else {
+                    // if we will connect to our gateway through another route, add a direct
+                    // route to it's gateway
+                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+                                                        bestRoute.getGateway(),
+                                                        ifaceName);
+                }
+                modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt);
+            }
+        }
+        if (doAdd) {
+            if (VDBG) log("Adding " + r + " for interface " + ifaceName);
+            try {
+                if (toDefaultTable) {
+                    synchronized (mRoutesLock) {
+                        // only track default table - only one apps can effect
+                        mAddedRoutes.add(r);
+                        mNetd.addRoute(ifaceName, r);
+                        if (exempt) {
+                            LinkAddress dest = r.getDestination();
+                            if (!mExemptAddresses.contains(dest)) {
+                                mNetd.setHostExemption(dest);
+                                mExemptAddresses.add(dest);
+                            }
+                        }
+                    }
+                } else {
+                    mNetd.addSecondaryRoute(ifaceName, r);
+                }
+            } catch (Exception e) {
+                // never crash - catch them all
+                if (DBG) loge("Exception trying to add a route: " + e);
+                return false;
+            }
+        } else {
+            // if we remove this one and there are no more like it, then refcount==0 and
+            // we can remove it from the table
+            if (toDefaultTable) {
+                synchronized (mRoutesLock) {
+                    mAddedRoutes.remove(r);
+                    if (mAddedRoutes.contains(r) == false) {
+                        if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                        try {
+                            mNetd.removeRoute(ifaceName, r);
+                            LinkAddress dest = r.getDestination();
+                            if (mExemptAddresses.contains(dest)) {
+                                mNetd.clearHostExemption(dest);
+                                mExemptAddresses.remove(dest);
+                            }
+                        } catch (Exception e) {
+                            // never crash - catch them all
+                            if (VDBG) loge("Exception trying to remove a route: " + e);
+                            return false;
+                        }
+                    } else {
+                        if (VDBG) log("not removing " + r + " as it's still in use");
+                    }
+                }
+            } else {
+                if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                try {
+                    mNetd.removeSecondaryRoute(ifaceName, r);
+                } catch (Exception e) {
+                    // never crash - catch them all
+                    if (VDBG) loge("Exception trying to remove a route: " + e);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * @see ConnectivityManager#getMobileDataEnabled()
+     */
+    public boolean getMobileDataEnabled() {
+        // TODO: This detail should probably be in DataConnectionTracker's
+        //       which is where we store the value and maybe make this
+        //       asynchronous.
+        enforceAccessPermission();
+        boolean retVal = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.MOBILE_DATA, 1) == 1;
+        if (VDBG) log("getMobileDataEnabled returning " + retVal);
+        return retVal;
+    }
+
+    public void setDataDependency(int networkType, boolean met) {
+        enforceConnectivityInternalPermission();
+
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_DEPENDENCY_MET,
+                (met ? ENABLED : DISABLED), networkType));
+    }
+
+    private void handleSetDependencyMet(int networkType, boolean met) {
+        if (mNetTrackers[networkType] != null) {
+            if (DBG) {
+                log("handleSetDependencyMet(" + networkType + ", " + met + ")");
+            }
+            mNetTrackers[networkType].setDependencyMet(met);
+        }
+    }
+
+    private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+        @Override
+        public void onUidRulesChanged(int uid, int uidRules) {
+            // caller is NPMS, since we only register with them
+            if (LOGD_RULES) {
+                log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+            }
+
+            synchronized (mRulesLock) {
+                // skip update when we've already applied rules
+                final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+                if (oldRules == uidRules) return;
+
+                mUidRules.put(uid, uidRules);
+            }
+
+            // TODO: notify UID when it has requested targeted updates
+        }
+
+        @Override
+        public void onMeteredIfacesChanged(String[] meteredIfaces) {
+            // caller is NPMS, since we only register with them
+            if (LOGD_RULES) {
+                log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
+            }
+
+            synchronized (mRulesLock) {
+                mMeteredIfaces.clear();
+                for (String iface : meteredIfaces) {
+                    mMeteredIfaces.add(iface);
+                }
+            }
+        }
+
+        @Override
+        public void onRestrictBackgroundChanged(boolean restrictBackground) {
+            // caller is NPMS, since we only register with them
+            if (LOGD_RULES) {
+                log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
+            }
+
+            // kick off connectivity change broadcast for active network, since
+            // global background policy change is radical.
+            final int networkType = mActiveDefaultNetwork;
+            if (isNetworkTypeValid(networkType)) {
+                final NetworkStateTracker tracker = mNetTrackers[networkType];
+                if (tracker != null) {
+                    final NetworkInfo info = tracker.getNetworkInfo();
+                    if (info != null && info.isConnected()) {
+                        sendConnectedBroadcast(info);
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * @see ConnectivityManager#setMobileDataEnabled(boolean)
+     */
+    public void setMobileDataEnabled(boolean enabled) {
+        enforceChangePermission();
+        if (DBG) log("setMobileDataEnabled(" + enabled + ")");
+
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA,
+                (enabled ? ENABLED : DISABLED), 0));
+    }
+
+    private void handleSetMobileData(boolean enabled) {
+        if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) {
+            if (VDBG) {
+                log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
+            }
+            mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
+        }
+        if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) {
+            if (VDBG) {
+                log(mNetTrackers[ConnectivityManager.TYPE_WIMAX].toString() + enabled);
+            }
+            mNetTrackers[ConnectivityManager.TYPE_WIMAX].setUserDataEnable(enabled);
+        }
+    }
+
+    @Override
+    public void setPolicyDataEnable(int networkType, boolean enabled) {
+        // only someone like NPMS should only be calling us
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        mHandler.sendMessage(mHandler.obtainMessage(
+                EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
+    }
+
+    private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
+        if (isNetworkTypeValid(networkType)) {
+            final NetworkStateTracker tracker = mNetTrackers[networkType];
+            if (tracker != null) {
+                tracker.setPolicyDataEnable(enabled);
+            }
+        }
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE,
+                "ConnectivityService");
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_NETWORK_STATE,
+                "ConnectivityService");
+    }
+
+    // TODO Make this a special check when it goes public
+    private void enforceTetherChangePermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_NETWORK_STATE,
+                "ConnectivityService");
+    }
+
+    private void enforceTetherAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_NETWORK_STATE,
+                "ConnectivityService");
+    }
+
+    private void enforceConnectivityInternalPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                "ConnectivityService");
+    }
+
+    private void enforceMarkNetworkSocketPermission() {
+        //Media server special case
+        if (Binder.getCallingUid() == Process.MEDIA_UID) {
+            return;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.MARK_NETWORK_SOCKET,
+                "ConnectivityService");
+    }
+
+    /**
+     * Handle a {@code DISCONNECTED} event. If this pertains to the non-active
+     * network, we ignore it. If it is for the active network, we send out a
+     * broadcast. But first, we check whether it might be possible to connect
+     * to a different network.
+     * @param info the {@code NetworkInfo} for the network
+     */
+    private void handleDisconnect(NetworkInfo info) {
+
+        int prevNetType = info.getType();
+
+        mNetTrackers[prevNetType].setTeardownRequested(false);
+
+        // Remove idletimer previously setup in {@code handleConnect}
+        removeDataActivityTracking(prevNetType);
+
+        /*
+         * If the disconnected network is not the active one, then don't report
+         * this as a loss of connectivity. What probably happened is that we're
+         * getting the disconnect for a network that we explicitly disabled
+         * in accordance with network preference policies.
+         */
+        if (!mNetConfigs[prevNetType].isDefault()) {
+            List<Integer> pids = mNetRequestersPids[prevNetType];
+            for (Integer pid : pids) {
+                // will remove them because the net's no longer connected
+                // need to do this now as only now do we know the pids and
+                // can properly null things that are no longer referenced.
+                reassessPidDns(pid.intValue(), false);
+            }
+        }
+
+        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+        if (info.isFailover()) {
+            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+            info.setFailover(false);
+        }
+        if (info.getReason() != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+        }
+        if (info.getExtraInfo() != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+                    info.getExtraInfo());
+        }
+
+        if (mNetConfigs[prevNetType].isDefault()) {
+            tryFailover(prevNetType);
+            if (mActiveDefaultNetwork != -1) {
+                NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+                intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+            } else {
+                mDefaultInetConditionPublished = 0; // we're not connected anymore
+                intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+            }
+        }
+        intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+
+        // Reset interface if no other connections are using the same interface
+        boolean doReset = true;
+        LinkProperties linkProperties = mNetTrackers[prevNetType].getLinkProperties();
+        if (linkProperties != null) {
+            String oldIface = linkProperties.getInterfaceName();
+            if (TextUtils.isEmpty(oldIface) == false) {
+                for (NetworkStateTracker networkStateTracker : mNetTrackers) {
+                    if (networkStateTracker == null) continue;
+                    NetworkInfo networkInfo = networkStateTracker.getNetworkInfo();
+                    if (networkInfo.isConnected() && networkInfo.getType() != prevNetType) {
+                        LinkProperties l = networkStateTracker.getLinkProperties();
+                        if (l == null) continue;
+                        if (oldIface.equals(l.getInterfaceName())) {
+                            doReset = false;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        // do this before we broadcast the change
+        handleConnectivityChange(prevNetType, doReset);
+
+        final Intent immediateIntent = new Intent(intent);
+        immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+        sendStickyBroadcast(immediateIntent);
+        sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
+        /*
+         * If the failover network is already connected, then immediately send
+         * out a followup broadcast indicating successful failover
+         */
+        if (mActiveDefaultNetwork != -1) {
+            sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
+                    getConnectivityChangeDelay());
+        }
+    }
+
+    private void tryFailover(int prevNetType) {
+        /*
+         * If this is a default network, check if other defaults are available.
+         * Try to reconnect on all available and let them hash it out when
+         * more than one connects.
+         */
+        if (mNetConfigs[prevNetType].isDefault()) {
+            if (mActiveDefaultNetwork == prevNetType) {
+                if (DBG) {
+                    log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType);
+                }
+                mActiveDefaultNetwork = -1;
+            }
+
+            // don't signal a reconnect for anything lower or equal priority than our
+            // current connected default
+            // TODO - don't filter by priority now - nice optimization but risky
+//            int currentPriority = -1;
+//            if (mActiveDefaultNetwork != -1) {
+//                currentPriority = mNetConfigs[mActiveDefaultNetwork].mPriority;
+//            }
+
+            for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+                if (checkType == prevNetType) continue;
+                if (mNetConfigs[checkType] == null) continue;
+                if (!mNetConfigs[checkType].isDefault()) continue;
+                if (mNetTrackers[checkType] == null) continue;
+
+// Enabling the isAvailable() optimization caused mobile to not get
+// selected if it was in the middle of error handling. Specifically
+// a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL
+// would not be available and we wouldn't get connected to anything.
+// So removing the isAvailable() optimization below for now. TODO: This
+// optimization should work and we need to investigate why it doesn't work.
+// This could be related to how DEACTIVATE_DATA_CALL is reporting its
+// complete before it is really complete.
+
+//                if (!mNetTrackers[checkType].isAvailable()) continue;
+
+//                if (currentPriority >= mNetConfigs[checkType].mPriority) continue;
+
+                NetworkStateTracker checkTracker = mNetTrackers[checkType];
+                NetworkInfo checkInfo = checkTracker.getNetworkInfo();
+                if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) {
+                    checkInfo.setFailover(true);
+                    checkTracker.reconnect();
+                }
+                if (DBG) log("Attempting to switch to " + checkInfo.getTypeName());
+            }
+        }
+    }
+
+    public void sendConnectedBroadcast(NetworkInfo info) {
+        enforceConnectivityInternalPermission();
+        sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+        sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
+    }
+
+    private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
+        sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+        sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
+    }
+
+    private void sendInetConditionBroadcast(NetworkInfo info) {
+        sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
+    }
+
+    private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
+        if (mLockdownTracker != null) {
+            info = mLockdownTracker.augmentNetworkInfo(info);
+        }
+
+        Intent intent = new Intent(bcastType);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+        if (info.isFailover()) {
+            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+            info.setFailover(false);
+        }
+        if (info.getReason() != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason());
+        }
+        if (info.getExtraInfo() != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO,
+                    info.getExtraInfo());
+        }
+        intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+        return intent;
+    }
+
+    private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
+        sendStickyBroadcast(makeGeneralIntent(info, bcastType));
+    }
+
+    private void sendGeneralBroadcastDelayed(NetworkInfo info, String bcastType, int delayMs) {
+        sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
+    }
+
+    private void sendDataActivityBroadcast(int deviceType, boolean active) {
+        Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
+        intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
+        intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
+                    RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Called when an attempt to fail over to another network has failed.
+     * @param info the {@link NetworkInfo} for the failed network
+     */
+    private void handleConnectionFailure(NetworkInfo info) {
+        mNetTrackers[info.getType()].setTeardownRequested(false);
+
+        String reason = info.getReason();
+        String extraInfo = info.getExtraInfo();
+
+        String reasonText;
+        if (reason == null) {
+            reasonText = ".";
+        } else {
+            reasonText = " (" + reason + ").";
+        }
+        loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText);
+
+        Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+        if (getActiveNetworkInfo() == null) {
+            intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+        }
+        if (reason != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_REASON, reason);
+        }
+        if (extraInfo != null) {
+            intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo);
+        }
+        if (info.isFailover()) {
+            intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true);
+            info.setFailover(false);
+        }
+
+        if (mNetConfigs[info.getType()].isDefault()) {
+            tryFailover(info.getType());
+            if (mActiveDefaultNetwork != -1) {
+                NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+                intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo);
+            } else {
+                mDefaultInetConditionPublished = 0;
+                intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true);
+            }
+        }
+
+        intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished);
+
+        final Intent immediateIntent = new Intent(intent);
+        immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+        sendStickyBroadcast(immediateIntent);
+        sendStickyBroadcast(intent);
+        /*
+         * If the failover network is already connected, then immediately send
+         * out a followup broadcast indicating successful failover
+         */
+        if (mActiveDefaultNetwork != -1) {
+            sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo());
+        }
+    }
+
+    private void sendStickyBroadcast(Intent intent) {
+        synchronized(this) {
+            if (!mSystemReady) {
+                mInitialBroadcast = new Intent(intent);
+            }
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            if (VDBG) {
+                log("sendStickyBroadcast: action=" + intent.getAction());
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
+        if (delayMs <= 0) {
+            sendStickyBroadcast(intent);
+        } else {
+            if (VDBG) {
+                log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+                        + intent.getAction());
+            }
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                    EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
+        }
+    }
+
+    void systemReady() {
+        mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
+        loadGlobalProxy();
+
+        synchronized(this) {
+            mSystemReady = true;
+            if (mInitialBroadcast != null) {
+                mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
+                mInitialBroadcast = null;
+            }
+        }
+        // load the global proxy at startup
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
+
+        // Try bringing up tracker, but if KeyStore isn't ready yet, wait
+        // for user to unlock device.
+        if (!updateLockdownVpn()) {
+            final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+            mContext.registerReceiver(mUserPresentReceiver, filter);
+        }
+    }
+
+    private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Try creating lockdown tracker, since user present usually means
+            // unlocked keystore.
+            if (updateLockdownVpn()) {
+                mContext.unregisterReceiver(this);
+            }
+        }
+    };
+
+    private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+        if (((type != mNetworkPreference)
+                      && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority))
+                   || (mNetworkPreference == mActiveDefaultNetwork)) {
+            return false;
+        }
+        return true;
+    }
+
+    private void handleConnect(NetworkInfo info) {
+        final int newNetType = info.getType();
+
+        setupDataActivityTracking(newNetType);
+
+        // snapshot isFailover, because sendConnectedBroadcast() resets it
+        boolean isFailover = info.isFailover();
+        final NetworkStateTracker thisNet = mNetTrackers[newNetType];
+        final String thisIface = thisNet.getLinkProperties().getInterfaceName();
+
+        if (VDBG) {
+            log("handleConnect: E newNetType=" + newNetType + " thisIface=" + thisIface
+                    + " isFailover" + isFailover);
+        }
+
+        // if this is a default net and other default is running
+        // kill the one not preferred
+        if (mNetConfigs[newNetType].isDefault()) {
+            if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+                if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
+                    // tear down the other
+                    NetworkStateTracker otherNet =
+                            mNetTrackers[mActiveDefaultNetwork];
+                    if (DBG) {
+                        log("Policy requires " + otherNet.getNetworkInfo().getTypeName() +
+                            " teardown");
+                    }
+                    if (!teardown(otherNet)) {
+                        loge("Network declined teardown request");
+                        teardown(thisNet);
+                        return;
+                    }
+                } else {
+                       // don't accept this one
+                        if (VDBG) {
+                            log("Not broadcasting CONNECT_ACTION " +
+                                "to torn down network " + info.getTypeName());
+                        }
+                        teardown(thisNet);
+                        return;
+                }
+            }
+            synchronized (ConnectivityService.this) {
+                // have a new default network, release the transition wakelock in a second
+                // if it's held.  The second pause is to allow apps to reconnect over the
+                // new network
+                if (mNetTransitionWakeLock.isHeld()) {
+                    mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                            EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                            mNetTransitionWakeLockSerialNumber, 0),
+                            1000);
+                }
+            }
+            mActiveDefaultNetwork = newNetType;
+            // this will cause us to come up initially as unconnected and switching
+            // to connected after our normal pause unless somebody reports us as reall
+            // disconnected
+            mDefaultInetConditionPublished = 0;
+            mDefaultConnectionSequence++;
+            mInetConditionChangeInFlight = false;
+            // Don't do this - if we never sign in stay, grey
+            //reportNetworkCondition(mActiveDefaultNetwork, 100);
+        }
+        thisNet.setTeardownRequested(false);
+        updateNetworkSettings(thisNet);
+        updateMtuSizeSettings(thisNet);
+        handleConnectivityChange(newNetType, false);
+        sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
+
+        // notify battery stats service about this network
+        if (thisIface != null) {
+            try {
+                BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
+            } catch (RemoteException e) {
+                // ignored; service lives in system_server
+            }
+        }
+    }
+
+    private void handleCaptivePortalTrackerCheck(NetworkInfo info) {
+        if (DBG) log("Captive portal check " + info);
+        int type = info.getType();
+        final NetworkStateTracker thisNet = mNetTrackers[type];
+        if (mNetConfigs[type].isDefault()) {
+            if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) {
+                if (isNewNetTypePreferredOverCurrentNetType(type)) {
+                    if (DBG) log("Captive check on " + info.getTypeName());
+                    mCaptivePortalTracker.detectCaptivePortal(new NetworkInfo(info));
+                    return;
+                } else {
+                    if (DBG) log("Tear down low priority net " + info.getTypeName());
+                    teardown(thisNet);
+                    return;
+                }
+            }
+        }
+
+        if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info);
+        thisNet.captivePortalCheckComplete();
+    }
+
+    /** @hide */
+    @Override
+    public void captivePortalCheckComplete(NetworkInfo info) {
+        enforceConnectivityInternalPermission();
+        if (DBG) log("captivePortalCheckComplete: ni=" + info);
+        mNetTrackers[info.getType()].captivePortalCheckComplete();
+    }
+
+    /** @hide */
+    @Override
+    public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+        enforceConnectivityInternalPermission();
+        if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
+        mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
+    }
+
+    /**
+     * Setup data activity tracking for the given network interface.
+     *
+     * Every {@code setupDataActivityTracking} should be paired with a
+     * {@link removeDataActivityTracking} for cleanup.
+     */
+    private void setupDataActivityTracking(int type) {
+        final NetworkStateTracker thisNet = mNetTrackers[type];
+        final String iface = thisNet.getLinkProperties().getInterfaceName();
+
+        final int timeout;
+
+        if (ConnectivityManager.isNetworkTypeMobile(type)) {
+            timeout = Settings.Global.getInt(mContext.getContentResolver(),
+                                             Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+                                             0);
+            // Canonicalize mobile network type
+            type = ConnectivityManager.TYPE_MOBILE;
+        } else if (ConnectivityManager.TYPE_WIFI == type) {
+            timeout = Settings.Global.getInt(mContext.getContentResolver(),
+                                             Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+                                             0);
+        } else {
+            // do not track any other networks
+            timeout = 0;
+        }
+
+        if (timeout > 0 && iface != null) {
+            try {
+                mNetd.addIdleTimer(iface, timeout, Integer.toString(type));
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Remove data activity tracking when network disconnects.
+     */
+    private void removeDataActivityTracking(int type) {
+        final NetworkStateTracker net = mNetTrackers[type];
+        final String iface = net.getLinkProperties().getInterfaceName();
+
+        if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) ||
+                              ConnectivityManager.TYPE_WIFI == type)) {
+            try {
+                // the call fails silently if no idletimer setup for this interface
+                mNetd.removeIdleTimer(iface);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * After a change in the connectivity state of a network. We're mainly
+     * concerned with making sure that the list of DNS servers is set up
+     * according to which networks are connected, and ensuring that the
+     * right routing table entries exist.
+     */
+    private void handleConnectivityChange(int netType, boolean doReset) {
+        int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
+        boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
+        if (VDBG) {
+            log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset
+                    + " resetMask=" + resetMask);
+        }
+
+        /*
+         * If a non-default network is enabled, add the host routes that
+         * will allow it's DNS servers to be accessed.
+         */
+        handleDnsConfigurationChange(netType);
+
+        LinkProperties curLp = mCurrentLinkProperties[netType];
+        LinkProperties newLp = null;
+
+        if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+            newLp = mNetTrackers[netType].getLinkProperties();
+            if (VDBG) {
+                log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+                        " doReset=" + doReset + " resetMask=" + resetMask +
+                        "\n   curLp=" + curLp +
+                        "\n   newLp=" + newLp);
+            }
+
+            if (curLp != null) {
+                if (curLp.isIdenticalInterfaceName(newLp)) {
+                    CompareResult<LinkAddress> car = curLp.compareAddresses(newLp);
+                    if ((car.removed.size() != 0) || (car.added.size() != 0)) {
+                        for (LinkAddress linkAddr : car.removed) {
+                            if (linkAddr.getAddress() instanceof Inet4Address) {
+                                resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
+                            }
+                            if (linkAddr.getAddress() instanceof Inet6Address) {
+                                resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
+                            }
+                        }
+                        if (DBG) {
+                            log("handleConnectivityChange: addresses changed" +
+                                    " linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
+                                    "\n   car=" + car);
+                        }
+                    } else {
+                        if (DBG) {
+                            log("handleConnectivityChange: address are the same reset per doReset" +
+                                   " linkProperty[" + netType + "]:" +
+                                   " resetMask=" + resetMask);
+                        }
+                    }
+                } else {
+                    resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
+                    if (DBG) {
+                        log("handleConnectivityChange: interface not not equivalent reset both" +
+                                " linkProperty[" + netType + "]:" +
+                                " resetMask=" + resetMask);
+                    }
+                }
+            }
+            if (mNetConfigs[netType].isDefault()) {
+                handleApplyDefaultProxy(newLp.getHttpProxy());
+            }
+        } else {
+            if (VDBG) {
+                log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+                        " doReset=" + doReset + " resetMask=" + resetMask +
+                        "\n  curLp=" + curLp +
+                        "\n  newLp= null");
+            }
+        }
+        mCurrentLinkProperties[netType] = newLp;
+        boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt);
+
+        if (resetMask != 0 || resetDns) {
+            if (VDBG) log("handleConnectivityChange: resetting");
+            if (curLp != null) {
+                if (VDBG) log("handleConnectivityChange: resetting curLp=" + curLp);
+                for (String iface : curLp.getAllInterfaceNames()) {
+                    if (TextUtils.isEmpty(iface) == false) {
+                        if (resetMask != 0) {
+                            if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
+                            NetworkUtils.resetConnections(iface, resetMask);
+
+                            // Tell VPN the interface is down. It is a temporary
+                            // but effective fix to make VPN aware of the change.
+                            if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
+                                synchronized(mVpns) {
+                                    for (int i = 0; i < mVpns.size(); i++) {
+                                        mVpns.valueAt(i).interfaceStatusChanged(iface, false);
+                                    }
+                                }
+                            }
+                        }
+                        if (resetDns) {
+                            flushVmDnsCache();
+                            if (VDBG) log("resetting DNS cache for " + iface);
+                            try {
+                                mNetd.flushInterfaceDnsCache(iface);
+                            } catch (Exception e) {
+                                // never crash - catch them all
+                                if (DBG) loge("Exception resetting dns cache: " + e);
+                            }
+                        }
+                    } else {
+                        loge("Can't reset connection for type "+netType);
+                    }
+                }
+            }
+        }
+
+        // Update 464xlat state.
+        NetworkStateTracker tracker = mNetTrackers[netType];
+        if (mClat.requiresClat(netType, tracker)) {
+
+            // If the connection was previously using clat, but is not using it now, stop the clat
+            // daemon. Normally, this happens automatically when the connection disconnects, but if
+            // the disconnect is not reported, or if the connection's LinkProperties changed for
+            // some other reason (e.g., handoff changes the IP addresses on the link), it would
+            // still be running. If it's not running, then stopping it is a no-op.
+            if (Nat464Xlat.isRunningClat(curLp) && !Nat464Xlat.isRunningClat(newLp)) {
+                mClat.stopClat();
+            }
+            // If the link requires clat to be running, then start the daemon now.
+            if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+                mClat.startClat(tracker);
+            } else {
+                mClat.stopClat();
+            }
+        }
+
+        // TODO: Temporary notifying upstread change to Tethering.
+        //       @see bug/4455071
+        /** Notify TetheringService if interface name has been changed. */
+        if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
+                             PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
+            if (isTetheringSupported()) {
+                mTethering.handleTetherIfaceChange();
+            }
+        }
+    }
+
+    /**
+     * Add and remove routes using the old properties (null if not previously connected),
+     * new properties (null if becoming disconnected).  May even be double null, which
+     * is a noop.
+     * Uses isLinkDefault to determine if default routes should be set or conversely if
+     * host routes should be set to the dns servers
+     * returns a boolean indicating the routes changed
+     */
+    private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
+            boolean isLinkDefault, boolean exempt) {
+        Collection<RouteInfo> routesToAdd = null;
+        CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
+        CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
+        if (curLp != null) {
+            // check for the delta between the current set and the new
+            routeDiff = curLp.compareAllRoutes(newLp);
+            dnsDiff = curLp.compareDnses(newLp);
+        } else if (newLp != null) {
+            routeDiff.added = newLp.getAllRoutes();
+            dnsDiff.added = newLp.getDnses();
+        }
+
+        boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
+
+        for (RouteInfo r : routeDiff.removed) {
+            if (isLinkDefault || ! r.isDefaultRoute()) {
+                if (VDBG) log("updateRoutes: default remove route r=" + r);
+                removeRoute(curLp, r, TO_DEFAULT_TABLE);
+            }
+            if (isLinkDefault == false) {
+                // remove from a secondary route table
+                removeRoute(curLp, r, TO_SECONDARY_TABLE);
+            }
+        }
+
+        if (!isLinkDefault) {
+            // handle DNS routes
+            if (routesChanged) {
+                // routes changed - remove all old dns entries and add new
+                if (curLp != null) {
+                    for (InetAddress oldDns : curLp.getDnses()) {
+                        removeRouteToAddress(curLp, oldDns);
+                    }
+                }
+                if (newLp != null) {
+                    for (InetAddress newDns : newLp.getDnses()) {
+                        addRouteToAddress(newLp, newDns, exempt);
+                    }
+                }
+            } else {
+                // no change in routes, check for change in dns themselves
+                for (InetAddress oldDns : dnsDiff.removed) {
+                    removeRouteToAddress(curLp, oldDns);
+                }
+                for (InetAddress newDns : dnsDiff.added) {
+                    addRouteToAddress(newLp, newDns, exempt);
+                }
+            }
+        }
+
+        for (RouteInfo r :  routeDiff.added) {
+            if (isLinkDefault || ! r.isDefaultRoute()) {
+                addRoute(newLp, r, TO_DEFAULT_TABLE, exempt);
+            } else {
+                // add to a secondary route table
+                addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT);
+
+                // many radios add a default route even when we don't want one.
+                // remove the default route unless somebody else has asked for it
+                String ifaceName = newLp.getInterfaceName();
+                synchronized (mRoutesLock) {
+                    if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
+                        if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+                        try {
+                            mNetd.removeRoute(ifaceName, r);
+                        } catch (Exception e) {
+                            // never crash - catch them all
+                            if (DBG) loge("Exception trying to remove a route: " + e);
+                        }
+                    }
+                }
+            }
+        }
+
+        return routesChanged;
+    }
+
+   /**
+     * Reads the network specific MTU size from reources.
+     * and set it on it's iface.
+     */
+   private void updateMtuSizeSettings(NetworkStateTracker nt) {
+       final String iface = nt.getLinkProperties().getInterfaceName();
+       final int mtu = nt.getLinkProperties().getMtu();
+
+       if (mtu < 68 || mtu > 10000) {
+           loge("Unexpected mtu value: " + nt);
+           return;
+       }
+
+       try {
+           if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
+           mNetd.setMtu(iface, mtu);
+       } catch (Exception e) {
+           Slog.e(TAG, "exception in setMtu()" + e);
+       }
+   }
+
+    /**
+     * Reads the network specific TCP buffer sizes from SystemProperties
+     * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+     * wide use
+     */
+    private void updateNetworkSettings(NetworkStateTracker nt) {
+        String key = nt.getTcpBufferSizesPropName();
+        String bufferSizes = key == null ? null : SystemProperties.get(key);
+
+        if (TextUtils.isEmpty(bufferSizes)) {
+            if (VDBG) log(key + " not found in system properties. Using defaults");
+
+            // Setting to default values so we won't be stuck to previous values
+            key = "net.tcp.buffersize.default";
+            bufferSizes = SystemProperties.get(key);
+        }
+
+        // Set values in kernel
+        if (bufferSizes.length() != 0) {
+            if (VDBG) {
+                log("Setting TCP values: [" + bufferSizes
+                        + "] which comes from [" + key + "]");
+            }
+            setBufferSize(bufferSizes);
+        }
+    }
+
+    /**
+     * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
+     * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
+     *
+     * @param bufferSizes in the format of "readMin, readInitial, readMax,
+     *        writeMin, writeInitial, writeMax"
+     */
+    private void setBufferSize(String bufferSizes) {
+        try {
+            String[] values = bufferSizes.split(",");
+
+            if (values.length == 6) {
+              final String prefix = "/sys/kernel/ipv4/tcp_";
+                FileUtils.stringToFile(prefix + "rmem_min", values[0]);
+                FileUtils.stringToFile(prefix + "rmem_def", values[1]);
+                FileUtils.stringToFile(prefix + "rmem_max", values[2]);
+                FileUtils.stringToFile(prefix + "wmem_min", values[3]);
+                FileUtils.stringToFile(prefix + "wmem_def", values[4]);
+                FileUtils.stringToFile(prefix + "wmem_max", values[5]);
+            } else {
+                loge("Invalid buffersize string: " + bufferSizes);
+            }
+        } catch (IOException e) {
+            loge("Can't set tcp buffer sizes:" + e);
+        }
+    }
+
+    /**
+     * Adjust the per-process dns entries (net.dns<x>.<pid>) based
+     * on the highest priority active net which this process requested.
+     * If there aren't any, clear it out
+     */
+    private void reassessPidDns(int pid, boolean doBump)
+    {
+        if (VDBG) log("reassessPidDns for pid " + pid);
+        Integer myPid = new Integer(pid);
+        for(int i : mPriorityList) {
+            if (mNetConfigs[i].isDefault()) {
+                continue;
+            }
+            NetworkStateTracker nt = mNetTrackers[i];
+            if (nt.getNetworkInfo().isConnected() &&
+                    !nt.isTeardownRequested()) {
+                LinkProperties p = nt.getLinkProperties();
+                if (p == null) continue;
+                if (mNetRequestersPids[i].contains(myPid)) {
+                    try {
+                        mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "exception reasseses pid dns: " + e);
+                    }
+                    return;
+                }
+           }
+        }
+        // nothing found - delete
+        try {
+            mNetd.clearDnsInterfaceForPid(pid);
+        } catch (Exception e) {
+            Slog.e(TAG, "exception clear interface from pid: " + e);
+        }
+    }
+
+    private void flushVmDnsCache() {
+        /*
+         * Tell the VMs to toss their DNS caches
+         */
+        Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        /*
+         * Connectivity events can happen before boot has completed ...
+         */
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // Caller must grab mDnsLock.
+    private void updateDnsLocked(String network, String iface,
+            Collection<InetAddress> dnses, String domains, boolean defaultDns) {
+        int last = 0;
+        if (dnses.size() == 0 && mDefaultDns != null) {
+            dnses = new ArrayList();
+            dnses.add(mDefaultDns);
+            if (DBG) {
+                loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
+            }
+        }
+
+        try {
+            mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
+            if (defaultDns) {
+                mNetd.setDefaultInterfaceForDns(iface);
+            }
+
+            for (InetAddress dns : dnses) {
+                ++last;
+                String key = "net.dns" + last;
+                String value = dns.getHostAddress();
+                SystemProperties.set(key, value);
+            }
+            for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+                String key = "net.dns" + i;
+                SystemProperties.set(key, "");
+            }
+            mNumDnsEntries = last;
+        } catch (Exception e) {
+            loge("exception setting default dns interface: " + e);
+        }
+    }
+
+    private void handleDnsConfigurationChange(int netType) {
+        // add default net's dns entries
+        NetworkStateTracker nt = mNetTrackers[netType];
+        if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) {
+            LinkProperties p = nt.getLinkProperties();
+            if (p == null) return;
+            Collection<InetAddress> dnses = p.getDnses();
+            if (mNetConfigs[netType].isDefault()) {
+                String network = nt.getNetworkInfo().getTypeName();
+                synchronized (mDnsLock) {
+                    updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true);
+                }
+            } else {
+                try {
+                    mNetd.setDnsServersForInterface(p.getInterfaceName(),
+                            NetworkUtils.makeStrings(dnses), p.getDomains());
+                } catch (Exception e) {
+                    if (DBG) loge("exception setting dns servers: " + e);
+                }
+                // set per-pid dns for attached secondary nets
+                List<Integer> pids = mNetRequestersPids[netType];
+                for (Integer pid : pids) {
+                    try {
+                        mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+                    } catch (Exception e) {
+                        Slog.e(TAG, "exception setting interface for pid: " + e);
+                    }
+                }
+            }
+            flushVmDnsCache();
+        }
+    }
+
+    private int getRestoreDefaultNetworkDelay(int networkType) {
+        String restoreDefaultNetworkDelayStr = SystemProperties.get(
+                NETWORK_RESTORE_DELAY_PROP_NAME);
+        if(restoreDefaultNetworkDelayStr != null &&
+                restoreDefaultNetworkDelayStr.length() != 0) {
+            try {
+                return Integer.valueOf(restoreDefaultNetworkDelayStr);
+            } catch (NumberFormatException e) {
+            }
+        }
+        // if the system property isn't set, use the value for the apn type
+        int ret = RESTORE_DEFAULT_NETWORK_DELAY;
+
+        if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
+                (mNetConfigs[networkType] != null)) {
+            ret = mNetConfigs[networkType].restoreTime;
+        }
+        return ret;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ConnectivityService " +
+                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
+                    Binder.getCallingUid());
+            return;
+        }
+
+        // TODO: add locking to get atomic snapshot
+        pw.println();
+        for (int i = 0; i < mNetTrackers.length; i++) {
+            final NetworkStateTracker nst = mNetTrackers[i];
+            if (nst != null) {
+                pw.println("NetworkStateTracker for " + getNetworkTypeName(i) + ":");
+                pw.increaseIndent();
+                if (nst.getNetworkInfo().isConnected()) {
+                    pw.println("Active network: " + nst.getNetworkInfo().
+                            getTypeName());
+                }
+                pw.println(nst.getNetworkInfo());
+                pw.println(nst.getLinkProperties());
+                pw.println(nst);
+                pw.println();
+                pw.decreaseIndent();
+            }
+        }
+
+        pw.println("Network Requester Pids:");
+        pw.increaseIndent();
+        for (int net : mPriorityList) {
+            String pidString = net + ": ";
+            for (Integer pid : mNetRequestersPids[net]) {
+                pidString = pidString + pid.toString() + ", ";
+            }
+            pw.println(pidString);
+        }
+        pw.println();
+        pw.decreaseIndent();
+
+        pw.println("FeatureUsers:");
+        pw.increaseIndent();
+        for (Object requester : mFeatureUsers) {
+            pw.println(requester.toString());
+        }
+        pw.println();
+        pw.decreaseIndent();
+
+        synchronized (this) {
+            pw.println("NetworkTranstionWakeLock is currently " +
+                    (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
+            pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
+        }
+        pw.println();
+
+        mTethering.dump(fd, pw, args);
+
+        if (mInetLog != null) {
+            pw.println();
+            pw.println("Inet condition reports:");
+            pw.increaseIndent();
+            for(int i = 0; i < mInetLog.size(); i++) {
+                pw.println(mInetLog.get(i));
+            }
+            pw.decreaseIndent();
+        }
+    }
+
+    // must be stateless - things change under us.
+    private class NetworkStateTrackerHandler extends Handler {
+        public NetworkStateTrackerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            NetworkInfo info;
+            switch (msg.what) {
+                case NetworkStateTracker.EVENT_STATE_CHANGED: {
+                    info = (NetworkInfo) msg.obj;
+                    NetworkInfo.State state = info.getState();
+
+                    if (VDBG || (state == NetworkInfo.State.CONNECTED) ||
+                            (state == NetworkInfo.State.DISCONNECTED) ||
+                            (state == NetworkInfo.State.SUSPENDED)) {
+                        log("ConnectivityChange for " +
+                            info.getTypeName() + ": " +
+                            state + "/" + info.getDetailedState());
+                    }
+
+                    // Since mobile has the notion of a network/apn that can be used for
+                    // provisioning we need to check every time we're connected as
+                    // CaptiveProtalTracker won't detected it because DCT doesn't report it
+                    // as connected as ACTION_ANY_DATA_CONNECTION_STATE_CHANGED instead its
+                    // reported as ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN. Which
+                    // is received by MDST and sent here as EVENT_STATE_CHANGED.
+                    if (ConnectivityManager.isNetworkTypeMobile(info.getType())
+                            && (0 != Settings.Global.getInt(mContext.getContentResolver(),
+                                        Settings.Global.DEVICE_PROVISIONED, 0))
+                            && (((state == NetworkInfo.State.CONNECTED)
+                                    && (info.getType() == ConnectivityManager.TYPE_MOBILE))
+                                || info.isConnectedToProvisioningNetwork())) {
+                        log("ConnectivityChange checkMobileProvisioning for"
+                                + " TYPE_MOBILE or ProvisioningNetwork");
+                        checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS);
+                    }
+
+                    EventLogTags.writeConnectivityStateChanged(
+                            info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
+
+                    if (info.getDetailedState() ==
+                            NetworkInfo.DetailedState.FAILED) {
+                        handleConnectionFailure(info);
+                    } else if (info.getDetailedState() ==
+                            DetailedState.CAPTIVE_PORTAL_CHECK) {
+                        handleCaptivePortalTrackerCheck(info);
+                    } else if (info.isConnectedToProvisioningNetwork()) {
+                        /**
+                         * TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
+                         * for now its an in between network, its a network that
+                         * is actually a default network but we don't want it to be
+                         * announced as such to keep background applications from
+                         * trying to use it. It turns out that some still try so we
+                         * take the additional step of clearing any default routes
+                         * to the link that may have incorrectly setup by the lower
+                         * levels.
+                         */
+                        LinkProperties lp = getLinkProperties(info.getType());
+                        if (DBG) {
+                            log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
+                        }
+
+                        // Clear any default routes setup by the radio so
+                        // any activity by applications trying to use this
+                        // connection will fail until the provisioning network
+                        // is enabled.
+                        for (RouteInfo r : lp.getRoutes()) {
+                            removeRoute(lp, r, TO_DEFAULT_TABLE);
+                        }
+                    } else if (state == NetworkInfo.State.DISCONNECTED) {
+                        handleDisconnect(info);
+                    } else if (state == NetworkInfo.State.SUSPENDED) {
+                        // TODO: need to think this over.
+                        // the logic here is, handle SUSPENDED the same as
+                        // DISCONNECTED. The only difference being we are
+                        // broadcasting an intent with NetworkInfo that's
+                        // suspended. This allows the applications an
+                        // opportunity to handle DISCONNECTED and SUSPENDED
+                        // differently, or not.
+                        handleDisconnect(info);
+                    } else if (state == NetworkInfo.State.CONNECTED) {
+                        handleConnect(info);
+                    }
+                    if (mLockdownTracker != null) {
+                        mLockdownTracker.onNetworkInfoChanged(info);
+                    }
+                    break;
+                }
+                case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: {
+                    info = (NetworkInfo) msg.obj;
+                    // TODO: Temporary allowing network configuration
+                    //       change not resetting sockets.
+                    //       @see bug/4455071
+                    handleConnectivityChange(info.getType(), false);
+                    break;
+                }
+                case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
+                    info = (NetworkInfo) msg.obj;
+                    int type = info.getType();
+                    updateNetworkSettings(mNetTrackers[type]);
+                    break;
+                }
+            }
+        }
+    }
+
+    private class InternalHandler extends Handler {
+        public InternalHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            NetworkInfo info;
+            switch (msg.what) {
+                case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: {
+                    String causedBy = null;
+                    synchronized (ConnectivityService.this) {
+                        if (msg.arg1 == mNetTransitionWakeLockSerialNumber &&
+                                mNetTransitionWakeLock.isHeld()) {
+                            mNetTransitionWakeLock.release();
+                            causedBy = mNetTransitionWakeLockCausedBy;
+                        }
+                    }
+                    if (causedBy != null) {
+                        log("NetTransition Wakelock for " + causedBy + " released by timeout");
+                    }
+                    break;
+                }
+                case EVENT_RESTORE_DEFAULT_NETWORK: {
+                    FeatureUser u = (FeatureUser)msg.obj;
+                    u.expire();
+                    break;
+                }
+                case EVENT_INET_CONDITION_CHANGE: {
+                    int netType = msg.arg1;
+                    int condition = msg.arg2;
+                    handleInetConditionChange(netType, condition);
+                    break;
+                }
+                case EVENT_INET_CONDITION_HOLD_END: {
+                    int netType = msg.arg1;
+                    int sequence = msg.arg2;
+                    handleInetConditionHoldEnd(netType, sequence);
+                    break;
+                }
+                case EVENT_SET_NETWORK_PREFERENCE: {
+                    int preference = msg.arg1;
+                    handleSetNetworkPreference(preference);
+                    break;
+                }
+                case EVENT_SET_MOBILE_DATA: {
+                    boolean enabled = (msg.arg1 == ENABLED);
+                    handleSetMobileData(enabled);
+                    break;
+                }
+                case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
+                    handleDeprecatedGlobalHttpProxy();
+                    break;
+                }
+                case EVENT_SET_DEPENDENCY_MET: {
+                    boolean met = (msg.arg1 == ENABLED);
+                    handleSetDependencyMet(msg.arg2, met);
+                    break;
+                }
+                case EVENT_SEND_STICKY_BROADCAST_INTENT: {
+                    Intent intent = (Intent)msg.obj;
+                    sendStickyBroadcast(intent);
+                    break;
+                }
+                case EVENT_SET_POLICY_DATA_ENABLE: {
+                    final int networkType = msg.arg1;
+                    final boolean enabled = msg.arg2 == ENABLED;
+                    handleSetPolicyDataEnable(networkType, enabled);
+                    break;
+                }
+                case EVENT_VPN_STATE_CHANGED: {
+                    if (mLockdownTracker != null) {
+                        mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
+                    }
+                    break;
+                }
+                case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
+                    int tag = mEnableFailFastMobileDataTag.get();
+                    if (msg.arg1 == tag) {
+                        MobileDataStateTracker mobileDst =
+                            (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+                        if (mobileDst != null) {
+                            mobileDst.setEnableFailFastMobileData(msg.arg2);
+                        }
+                    } else {
+                        log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1
+                                + " != tag:" + tag);
+                    }
+                    break;
+                }
+                case EVENT_SAMPLE_INTERVAL_ELAPSED: {
+                    handleNetworkSamplingTimeout();
+                    break;
+                }
+                case EVENT_PROXY_HAS_CHANGED: {
+                    handleApplyDefaultProxy((ProxyProperties)msg.obj);
+                    break;
+                }
+            }
+        }
+    }
+
+    // javadoc from interface
+    public int tether(String iface) {
+        enforceTetherChangePermission();
+
+        if (isTetheringSupported()) {
+            return mTethering.tether(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
+    }
+
+    // javadoc from interface
+    public int untether(String iface) {
+        enforceTetherChangePermission();
+
+        if (isTetheringSupported()) {
+            return mTethering.untether(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
+    }
+
+    // javadoc from interface
+    public int getLastTetherError(String iface) {
+        enforceTetherAccessPermission();
+
+        if (isTetheringSupported()) {
+            return mTethering.getLastTetherError(iface);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
+    }
+
+    // TODO - proper iface API for selection by property, inspection, etc
+    public String[] getTetherableUsbRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableUsbRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
+    public String[] getTetherableWifiRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableWifiRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
+    public String[] getTetherableBluetoothRegexs() {
+        enforceTetherAccessPermission();
+        if (isTetheringSupported()) {
+            return mTethering.getTetherableBluetoothRegexs();
+        } else {
+            return new String[0];
+        }
+    }
+
+    public int setUsbTethering(boolean enable) {
+        enforceTetherChangePermission();
+        if (isTetheringSupported()) {
+            return mTethering.setUsbTethering(enable);
+        } else {
+            return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+        }
+    }
+
+    // TODO - move iface listing, queries, etc to new module
+    // javadoc from interface
+    public String[] getTetherableIfaces() {
+        enforceTetherAccessPermission();
+        return mTethering.getTetherableIfaces();
+    }
+
+    public String[] getTetheredIfaces() {
+        enforceTetherAccessPermission();
+        return mTethering.getTetheredIfaces();
+    }
+
+    public String[] getTetheringErroredIfaces() {
+        enforceTetherAccessPermission();
+        return mTethering.getErroredIfaces();
+    }
+
+    // if ro.tether.denied = true we default to no tethering
+    // gservices could set the secure setting to 1 though to enable it on a build where it
+    // had previously been turned off.
+    public boolean isTetheringSupported() {
+        enforceTetherAccessPermission();
+        int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1);
+        boolean tetherEnabledInSettings = (Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.TETHER_SUPPORTED, defaultVal) != 0);
+        return tetherEnabledInSettings && ((mTethering.getTetherableUsbRegexs().length != 0 ||
+                mTethering.getTetherableWifiRegexs().length != 0 ||
+                mTethering.getTetherableBluetoothRegexs().length != 0) &&
+                mTethering.getUpstreamIfaceTypes().length != 0);
+    }
+
+    // An API NetworkStateTrackers can call when they lose their network.
+    // This will automatically be cleared after X seconds or a network becomes CONNECTED,
+    // whichever happens first.  The timer is started by the first caller and not
+    // restarted by subsequent callers.
+    public void requestNetworkTransitionWakelock(String forWhom) {
+        enforceConnectivityInternalPermission();
+        synchronized (this) {
+            if (mNetTransitionWakeLock.isHeld()) return;
+            mNetTransitionWakeLockSerialNumber++;
+            mNetTransitionWakeLock.acquire();
+            mNetTransitionWakeLockCausedBy = forWhom;
+        }
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+                mNetTransitionWakeLockSerialNumber, 0),
+                mNetTransitionWakeLockTimeout);
+        return;
+    }
+
+    // 100 percent is full good, 0 is full bad.
+    public void reportInetCondition(int networkType, int percentage) {
+        if (VDBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")");
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.STATUS_BAR,
+                "ConnectivityService");
+
+        if (DBG) {
+            int pid = getCallingPid();
+            int uid = getCallingUid();
+            String s = pid + "(" + uid + ") reports inet is " +
+                (percentage > 50 ? "connected" : "disconnected") + " (" + percentage + ") on " +
+                "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime();
+            mInetLog.add(s);
+            while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) {
+                mInetLog.remove(0);
+            }
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(
+            EVENT_INET_CONDITION_CHANGE, networkType, percentage));
+    }
+
+    private void handleInetConditionChange(int netType, int condition) {
+        if (mActiveDefaultNetwork == -1) {
+            if (DBG) log("handleInetConditionChange: no active default network - ignore");
+            return;
+        }
+        if (mActiveDefaultNetwork != netType) {
+            if (DBG) log("handleInetConditionChange: net=" + netType +
+                            " != default=" + mActiveDefaultNetwork + " - ignore");
+            return;
+        }
+        if (VDBG) {
+            log("handleInetConditionChange: net=" +
+                    netType + ", condition=" + condition +
+                    ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+        }
+        mDefaultInetCondition = condition;
+        int delay;
+        if (mInetConditionChangeInFlight == false) {
+            if (VDBG) log("handleInetConditionChange: starting a change hold");
+            // setup a new hold to debounce this
+            if (mDefaultInetCondition > 50) {
+                delay = Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+            } else {
+                delay = Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000);
+            }
+            mInetConditionChangeInFlight = true;
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END,
+                    mActiveDefaultNetwork, mDefaultConnectionSequence), delay);
+        } else {
+            // we've set the new condition, when this hold ends that will get picked up
+            if (VDBG) log("handleInetConditionChange: currently in hold - not setting new end evt");
+        }
+    }
+
+    private void handleInetConditionHoldEnd(int netType, int sequence) {
+        if (DBG) {
+            log("handleInetConditionHoldEnd: net=" + netType +
+                    ", condition=" + mDefaultInetCondition +
+                    ", published condition=" + mDefaultInetConditionPublished);
+        }
+        mInetConditionChangeInFlight = false;
+
+        if (mActiveDefaultNetwork == -1) {
+            if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
+            return;
+        }
+        if (mDefaultConnectionSequence != sequence) {
+            if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
+            return;
+        }
+        // TODO: Figure out why this optimization sometimes causes a
+        //       change in mDefaultInetCondition to be missed and the
+        //       UI to not be updated.
+        //if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+        //    if (DBG) log("no change in condition - aborting");
+        //    return;
+        //}
+        NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+        if (networkInfo.isConnected() == false) {
+            if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
+            return;
+        }
+        mDefaultInetConditionPublished = mDefaultInetCondition;
+        sendInetConditionBroadcast(networkInfo);
+        return;
+    }
+
+    public ProxyProperties getProxy() {
+        // this information is already available as a world read/writable jvm property
+        // so this API change wouldn't have a benifit.  It also breaks the passing
+        // of proxy info to all the JVMs.
+        // enforceAccessPermission();
+        synchronized (mProxyLock) {
+            ProxyProperties ret = mGlobalProxy;
+            if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
+            return ret;
+        }
+    }
+
+    public void setGlobalProxy(ProxyProperties proxyProperties) {
+        enforceConnectivityInternalPermission();
+
+        synchronized (mProxyLock) {
+            if (proxyProperties == mGlobalProxy) return;
+            if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
+            if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
+
+            String host = "";
+            int port = 0;
+            String exclList = "";
+            String pacFileUrl = "";
+            if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
+                    !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
+                if (!proxyProperties.isValid()) {
+                    if (DBG)
+                        log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+                    return;
+                }
+                mGlobalProxy = new ProxyProperties(proxyProperties);
+                host = mGlobalProxy.getHost();
+                port = mGlobalProxy.getPort();
+                exclList = mGlobalProxy.getExclusionList();
+                if (proxyProperties.getPacFileUrl() != null) {
+                    pacFileUrl = proxyProperties.getPacFileUrl();
+                }
+            } else {
+                mGlobalProxy = null;
+            }
+            ContentResolver res = mContext.getContentResolver();
+            final long token = Binder.clearCallingIdentity();
+            try {
+                Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
+                Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
+                Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+                        exclList);
+                Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+        if (mGlobalProxy == null) {
+            proxyProperties = mDefaultProxy;
+        }
+        sendProxyBroadcast(proxyProperties);
+    }
+
+    private void loadGlobalProxy() {
+        ContentResolver res = mContext.getContentResolver();
+        String host = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST);
+        int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0);
+        String exclList = Settings.Global.getString(res,
+                Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+        String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
+        if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
+            ProxyProperties proxyProperties;
+            if (!TextUtils.isEmpty(pacFileUrl)) {
+                proxyProperties = new ProxyProperties(pacFileUrl);
+            } else {
+                proxyProperties = new ProxyProperties(host, port, exclList);
+            }
+            if (!proxyProperties.isValid()) {
+                if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+                return;
+            }
+
+            synchronized (mProxyLock) {
+                mGlobalProxy = proxyProperties;
+            }
+        }
+    }
+
+    public ProxyProperties getGlobalProxy() {
+        // this information is already available as a world read/writable jvm property
+        // so this API change wouldn't have a benifit.  It also breaks the passing
+        // of proxy info to all the JVMs.
+        // enforceAccessPermission();
+        synchronized (mProxyLock) {
+            return mGlobalProxy;
+        }
+    }
+
+    private void handleApplyDefaultProxy(ProxyProperties proxy) {
+        if (proxy != null && TextUtils.isEmpty(proxy.getHost())
+                && TextUtils.isEmpty(proxy.getPacFileUrl())) {
+            proxy = null;
+        }
+        synchronized (mProxyLock) {
+            if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
+            if (mDefaultProxy == proxy) return; // catches repeated nulls
+            if (proxy != null &&  !proxy.isValid()) {
+                if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
+                return;
+            }
+            mDefaultProxy = proxy;
+
+            if (mGlobalProxy != null) return;
+            if (!mDefaultProxyDisabled) {
+                sendProxyBroadcast(proxy);
+            }
+        }
+    }
+
+    private void handleDeprecatedGlobalHttpProxy() {
+        String proxy = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.HTTP_PROXY);
+        if (!TextUtils.isEmpty(proxy)) {
+            String data[] = proxy.split(":");
+            if (data.length == 0) {
+                return;
+            }
+
+            String proxyHost =  data[0];
+            int proxyPort = 8080;
+            if (data.length > 1) {
+                try {
+                    proxyPort = Integer.parseInt(data[1]);
+                } catch (NumberFormatException e) {
+                    return;
+                }
+            }
+            ProxyProperties p = new ProxyProperties(data[0], proxyPort, "");
+            setGlobalProxy(p);
+        }
+    }
+
+    private void sendProxyBroadcast(ProxyProperties proxy) {
+        if (proxy == null) proxy = new ProxyProperties("", 0, "");
+        if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
+        if (DBG) log("sending Proxy Broadcast for " + proxy);
+        Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+            Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy);
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private static class SettingsObserver extends ContentObserver {
+        private int mWhat;
+        private Handler mHandler;
+        SettingsObserver(Handler handler, int what) {
+            super(handler);
+            mHandler = handler;
+            mWhat = what;
+        }
+
+        void observe(Context context) {
+            ContentResolver resolver = context.getContentResolver();
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.HTTP_PROXY), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            mHandler.obtainMessage(mWhat).sendToTarget();
+        }
+    }
+
+    private static void log(String s) {
+        Slog.d(TAG, s);
+    }
+
+    private static void loge(String s) {
+        Slog.e(TAG, s);
+    }
+
+    int convertFeatureToNetworkType(int networkType, String feature) {
+        int usedNetworkType = networkType;
+
+        if(networkType == ConnectivityManager.TYPE_MOBILE) {
+            if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN) ||
+                    TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
+            } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
+                usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
+            } else {
+                Slog.e(TAG, "Can't match any mobile netTracker!");
+            }
+        } else if (networkType == ConnectivityManager.TYPE_WIFI) {
+            if (TextUtils.equals(feature, "p2p")) {
+                usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
+            } else {
+                Slog.e(TAG, "Can't match any wifi netTracker!");
+            }
+        } else {
+            Slog.e(TAG, "Unexpected network type");
+        }
+        return usedNetworkType;
+    }
+
+    private static <T> T checkNotNull(T value, String message) {
+        if (value == null) {
+            throw new NullPointerException(message);
+        }
+        return value;
+    }
+
+    /**
+     * Protect a socket from VPN routing rules. This method is used by
+     * VpnBuilder and not available in ConnectivityManager. Permissions
+     * are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public boolean protectVpn(ParcelFileDescriptor socket) {
+        throwIfLockdownEnabled();
+        try {
+            int type = mActiveDefaultNetwork;
+            int user = UserHandle.getUserId(Binder.getCallingUid());
+            if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
+                synchronized(mVpns) {
+                    mVpns.get(user).protect(socket,
+                            mNetTrackers[type].getLinkProperties().getInterfaceName());
+                }
+                return true;
+            }
+        } catch (Exception e) {
+            // ignore
+        } finally {
+            try {
+                socket.close();
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Prepare for a VPN application. This method is used by VpnDialogs
+     * and not available in ConnectivityManager. Permissions are checked
+     * in Vpn class.
+     * @hide
+     */
+    @Override
+    public boolean prepareVpn(String oldPackage, String newPackage) {
+        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).prepare(oldPackage, newPackage);
+        }
+    }
+
+    @Override
+    public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
+        enforceMarkNetworkSocketPermission();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            int mark = mNetd.getMarkForUid(uid);
+            // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
+            if (mark == -1) {
+                mark = 0;
+            }
+            NetworkUtils.markSocket(socket.getFd(), mark);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Configure a TUN interface and return its file descriptor. Parameters
+     * are encoded and opaque to this class. This method is used by VpnBuilder
+     * and not available in ConnectivityManager. Permissions are checked in
+     * Vpn class.
+     * @hide
+     */
+    @Override
+    public ParcelFileDescriptor establishVpn(VpnConfig config) {
+        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).establish(config);
+        }
+    }
+
+    /**
+     * Start legacy VPN, controlling native daemons as needed. Creates a
+     * secondary thread to perform connection work, returning quickly.
+     */
+    @Override
+    public void startLegacyVpn(VpnProfile profile) {
+        throwIfLockdownEnabled();
+        final LinkProperties egress = getActiveLinkProperties();
+        if (egress == null) {
+            throw new IllegalStateException("Missing active network connection");
+        }
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+        }
+    }
+
+    /**
+     * Return the information of the ongoing legacy VPN. This method is used
+     * by VpnSettings and not available in ConnectivityManager. Permissions
+     * are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public LegacyVpnInfo getLegacyVpnInfo() {
+        throwIfLockdownEnabled();
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).getLegacyVpnInfo();
+        }
+    }
+
+    /**
+     * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
+     * not available in ConnectivityManager.
+     * Permissions are checked in Vpn class.
+     * @hide
+     */
+    @Override
+    public VpnConfig getVpnConfig() {
+        int user = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized(mVpns) {
+            return mVpns.get(user).getVpnConfig();
+        }
+    }
+
+    /**
+     * Callback for VPN subsystem. Currently VPN is not adapted to the service
+     * through NetworkStateTracker since it works differently. For example, it
+     * needs to override DNS servers but never takes the default routes. It
+     * relies on another data network, and it could keep existing connections
+     * alive after reconnecting, switching between networks, or even resuming
+     * from deep sleep. Calls from applications should be done synchronously
+     * to avoid race conditions. As these are all hidden APIs, refactoring can
+     * be done whenever a better abstraction is developed.
+     */
+    public class VpnCallback {
+        private VpnCallback() {
+        }
+
+        public void onStateChanged(NetworkInfo info) {
+            mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
+        }
+
+        public void override(String iface, List<String> dnsServers, List<String> searchDomains) {
+            if (dnsServers == null) {
+                restore();
+                return;
+            }
+
+            // Convert DNS servers into addresses.
+            List<InetAddress> addresses = new ArrayList<InetAddress>();
+            for (String address : dnsServers) {
+                // Double check the addresses and remove invalid ones.
+                try {
+                    addresses.add(InetAddress.parseNumericAddress(address));
+                } catch (Exception e) {
+                    // ignore
+                }
+            }
+            if (addresses.isEmpty()) {
+                restore();
+                return;
+            }
+
+            // Concatenate search domains into a string.
+            StringBuilder buffer = new StringBuilder();
+            if (searchDomains != null) {
+                for (String domain : searchDomains) {
+                    buffer.append(domain).append(' ');
+                }
+            }
+            String domains = buffer.toString().trim();
+
+            // Apply DNS changes.
+            synchronized (mDnsLock) {
+                updateDnsLocked("VPN", iface, addresses, domains, false);
+            }
+
+            // Temporarily disable the default proxy (not global).
+            synchronized (mProxyLock) {
+                mDefaultProxyDisabled = true;
+                if (mGlobalProxy == null && mDefaultProxy != null) {
+                    sendProxyBroadcast(null);
+                }
+            }
+
+            // TODO: support proxy per network.
+        }
+
+        public void restore() {
+            synchronized (mProxyLock) {
+                mDefaultProxyDisabled = false;
+                if (mGlobalProxy == null && mDefaultProxy != null) {
+                    sendProxyBroadcast(mDefaultProxy);
+                }
+            }
+        }
+
+        public void protect(ParcelFileDescriptor socket) {
+            try {
+                final int mark = mNetd.getMarkForProtect();
+                NetworkUtils.markSocket(socket.getFd(), mark);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void setRoutes(String interfaze, List<RouteInfo> routes) {
+            for (RouteInfo route : routes) {
+                try {
+                    mNetd.setMarkedForwardingRoute(interfaze, route);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        public void setMarkedForwarding(String interfaze) {
+            try {
+                mNetd.setMarkedForwarding(interfaze);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void clearMarkedForwarding(String interfaze) {
+            try {
+                mNetd.clearMarkedForwarding(interfaze);
+            } catch (RemoteException e) {
+            }
+        }
+
+        public void addUserForwarding(String interfaze, int uid, boolean forwardDns) {
+            int uidStart = uid * UserHandle.PER_USER_RANGE;
+            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+            addUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+        }
+
+        public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) {
+            int uidStart = uid * UserHandle.PER_USER_RANGE;
+            int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+            clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+        }
+
+        public void addUidForwarding(String interfaze, int uidStart, int uidEnd,
+                boolean forwardDns) {
+            try {
+                mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd);
+                if (forwardDns) mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
+            } catch (RemoteException e) {
+            }
+
+        }
+
+        public void clearUidForwarding(String interfaze, int uidStart, int uidEnd,
+                boolean forwardDns) {
+            try {
+                mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
+                if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
+            } catch (RemoteException e) {
+            }
+
+        }
+    }
+
+    @Override
+    public boolean updateLockdownVpn() {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            Slog.w(TAG, "Lockdown VPN only available to AID_SYSTEM");
+            return false;
+        }
+
+        // Tear down existing lockdown if profile was removed
+        mLockdownEnabled = LockdownVpnTracker.isEnabled();
+        if (mLockdownEnabled) {
+            if (!mKeyStore.isUnlocked()) {
+                Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
+                return false;
+            }
+
+            final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+            final VpnProfile profile = VpnProfile.decode(
+                    profileName, mKeyStore.get(Credentials.VPN + profileName));
+            int user = UserHandle.getUserId(Binder.getCallingUid());
+            synchronized(mVpns) {
+                setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
+                            profile));
+            }
+        } else {
+            setLockdownTracker(null);
+        }
+
+        return true;
+    }
+
+    /**
+     * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+     * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+     */
+    private void setLockdownTracker(LockdownVpnTracker tracker) {
+        // Shutdown any existing tracker
+        final LockdownVpnTracker existing = mLockdownTracker;
+        mLockdownTracker = null;
+        if (existing != null) {
+            existing.shutdown();
+        }
+
+        try {
+            if (tracker != null) {
+                mNetd.setFirewallEnabled(true);
+                mNetd.setFirewallInterfaceRule("lo", true);
+                mLockdownTracker = tracker;
+                mLockdownTracker.init();
+            } else {
+                mNetd.setFirewallEnabled(false);
+            }
+        } catch (RemoteException e) {
+            // ignored; NMS lives inside system_server
+        }
+    }
+
+    private void throwIfLockdownEnabled() {
+        if (mLockdownEnabled) {
+            throw new IllegalStateException("Unavailable in lockdown mode");
+        }
+    }
+
+    public void supplyMessenger(int networkType, Messenger messenger) {
+        enforceConnectivityInternalPermission();
+
+        if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
+            mNetTrackers[networkType].supplyMessenger(messenger);
+        }
+    }
+
+    public int findConnectionTypeForIface(String iface) {
+        enforceConnectivityInternalPermission();
+
+        if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                LinkProperties lp = tracker.getLinkProperties();
+                if (lp != null && iface.equals(lp.getInterfaceName())) {
+                    return tracker.getNetworkInfo().getType();
+                }
+            }
+        }
+        return ConnectivityManager.TYPE_NONE;
+    }
+
+    /**
+     * Have mobile data fail fast if enabled.
+     *
+     * @param enabled DctConstants.ENABLED/DISABLED
+     */
+    private void setEnableFailFastMobileData(int enabled) {
+        int tag;
+
+        if (enabled == DctConstants.ENABLED) {
+            tag = mEnableFailFastMobileDataTag.incrementAndGet();
+        } else {
+            tag = mEnableFailFastMobileDataTag.get();
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_ENABLE_FAIL_FAST_MOBILE_DATA, tag,
+                         enabled));
+    }
+
+    private boolean isMobileDataStateTrackerReady() {
+        MobileDataStateTracker mdst =
+                (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+        return (mdst != null) && (mdst.isReady());
+    }
+
+    /**
+     * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
+     */
+
+    /**
+     * No connection was possible to the network.
+     * This is NOT a warm sim.
+     */
+    private static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
+
+    /**
+     * A connection was made to the internet, all is well.
+     * This is NOT a warm sim.
+     */
+    private static final int CMP_RESULT_CODE_CONNECTABLE = 1;
+
+    /**
+     * A connection was made but no dns server was available to resolve a name to address.
+     * This is NOT a warm sim since provisioning network is supported.
+     */
+    private static final int CMP_RESULT_CODE_NO_DNS = 2;
+
+    /**
+     * A connection was made but could not open a TCP connection.
+     * This is NOT a warm sim since provisioning network is supported.
+     */
+    private static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 3;
+
+    /**
+     * A connection was made but there was a redirection, we appear to be in walled garden.
+     * This is an indication of a warm sim on a mobile network such as T-Mobile.
+     */
+    private static final int CMP_RESULT_CODE_REDIRECTED = 4;
+
+    /**
+     * The mobile network is a provisioning network.
+     * This is an indication of a warm sim on a mobile network such as AT&T.
+     */
+    private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
+
+    private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
+
+    @Override
+    public int checkMobileProvisioning(int suggestedTimeOutMs) {
+        int timeOutMs = -1;
+        if (DBG) log("checkMobileProvisioning: E suggestedTimeOutMs=" + suggestedTimeOutMs);
+        enforceConnectivityInternalPermission();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            timeOutMs = suggestedTimeOutMs;
+            if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
+                timeOutMs = CheckMp.MAX_TIMEOUT_MS;
+            }
+
+            // Check that mobile networks are supported
+            if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE)
+                    || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) {
+                if (DBG) log("checkMobileProvisioning: X no mobile network");
+                return timeOutMs;
+            }
+
+            // If we're already checking don't do it again
+            // TODO: Add a queue of results...
+            if (mIsCheckingMobileProvisioning.getAndSet(true)) {
+                if (DBG) log("checkMobileProvisioning: X already checking ignore for the moment");
+                return timeOutMs;
+            }
+
+            // Start off with mobile notification off
+            setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+            CheckMp checkMp = new CheckMp(mContext, this);
+            CheckMp.CallBack cb = new CheckMp.CallBack() {
+                @Override
+                void onComplete(Integer result) {
+                    if (DBG) log("CheckMp.onComplete: result=" + result);
+                    NetworkInfo ni =
+                            mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+                    switch(result) {
+                        case CMP_RESULT_CODE_CONNECTABLE:
+                        case CMP_RESULT_CODE_NO_CONNECTION:
+                        case CMP_RESULT_CODE_NO_DNS:
+                        case CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+                            if (DBG) log("CheckMp.onComplete: ignore, connected or no connection");
+                            break;
+                        }
+                        case CMP_RESULT_CODE_REDIRECTED: {
+                            if (DBG) log("CheckMp.onComplete: warm sim");
+                            String url = getMobileProvisioningUrl();
+                            if (TextUtils.isEmpty(url)) {
+                                url = getMobileRedirectedProvisioningUrl();
+                            }
+                            if (TextUtils.isEmpty(url) == false) {
+                                if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url);
+                                setProvNotificationVisible(true,
+                                        ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+                                        url);
+                            } else {
+                                if (DBG) log("CheckMp.onComplete: warm (redirected), no url");
+                            }
+                            break;
+                        }
+                        case CMP_RESULT_CODE_PROVISIONING_NETWORK: {
+                            String url = getMobileProvisioningUrl();
+                            if (TextUtils.isEmpty(url) == false) {
+                                if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url);
+                                setProvNotificationVisible(true,
+                                        ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+                                        url);
+                            } else {
+                                if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
+                            }
+                            break;
+                        }
+                        default: {
+                            loge("CheckMp.onComplete: ignore unexpected result=" + result);
+                            break;
+                        }
+                    }
+                    mIsCheckingMobileProvisioning.set(false);
+                }
+            };
+            CheckMp.Params params =
+                    new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+            if (DBG) log("checkMobileProvisioning: params=" + params);
+            checkMp.execute(params);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+            if (DBG) log("checkMobileProvisioning: X");
+        }
+        return timeOutMs;
+    }
+
+    static class CheckMp extends
+            AsyncTask<CheckMp.Params, Void, Integer> {
+        private static final String CHECKMP_TAG = "CheckMp";
+
+        // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures
+        private static boolean mTestingFailures;
+
+        // Choosing 4 loops as half of them will use HTTPS and the other half HTTP
+        private static final int MAX_LOOPS = 4;
+
+        // Number of milli-seconds to complete all of the retires
+        public static final int MAX_TIMEOUT_MS =  60000;
+
+        // The socket should retry only 5 seconds, the default is longer
+        private static final int SOCKET_TIMEOUT_MS = 5000;
+
+        // Sleep time for network errors
+        private static final int NET_ERROR_SLEEP_SEC = 3;
+
+        // Sleep time for network route establishment
+        private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3;
+
+        // Short sleep time for polling :(
+        private static final int POLLING_SLEEP_SEC = 1;
+
+        private Context mContext;
+        private ConnectivityService mCs;
+        private TelephonyManager mTm;
+        private Params mParams;
+
+        /**
+         * Parameters for AsyncTask.execute
+         */
+        static class Params {
+            private String mUrl;
+            private long mTimeOutMs;
+            private CallBack mCb;
+
+            Params(String url, long timeOutMs, CallBack cb) {
+                mUrl = url;
+                mTimeOutMs = timeOutMs;
+                mCb = cb;
+            }
+
+            @Override
+            public String toString() {
+                return "{" + " url=" + mUrl + " mTimeOutMs=" + mTimeOutMs + " mCb=" + mCb + "}";
+            }
+        }
+
+        // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be
+        // issued by name or ip address, for Google its by name so when we construct
+        // this HostnameVerifier we'll pass the original Uri and use it to verify
+        // the host. If the host name in the original uril fails we'll test the
+        // hostname parameter just incase things change.
+        static class CheckMpHostnameVerifier implements HostnameVerifier {
+            Uri mOrgUri;
+
+            CheckMpHostnameVerifier(Uri orgUri) {
+                mOrgUri = orgUri;
+            }
+
+            @Override
+            public boolean verify(String hostname, SSLSession session) {
+                HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+                String orgUriHost = mOrgUri.getHost();
+                boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session);
+                if (DBG) {
+                    log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname
+                        + " orgUriHost=" + orgUriHost);
+                }
+                return retVal;
+            }
+        }
+
+        /**
+         * The call back object passed in Params. onComplete will be called
+         * on the main thread.
+         */
+        abstract static class CallBack {
+            // Called on the main thread.
+            abstract void onComplete(Integer result);
+        }
+
+        public CheckMp(Context context, ConnectivityService cs) {
+            if (Build.IS_DEBUGGABLE) {
+                mTestingFailures =
+                        SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1;
+            } else {
+                mTestingFailures = false;
+            }
+
+            mContext = context;
+            mCs = cs;
+
+            // Setup access to TelephonyService we'll be using.
+            mTm = (TelephonyManager) mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+        }
+
+        /**
+         * Get the default url to use for the test.
+         */
+        public String getDefaultUrl() {
+            // See http://go/clientsdns for usage approval
+            String server = Settings.Global.getString(mContext.getContentResolver(),
+                    Settings.Global.CAPTIVE_PORTAL_SERVER);
+            if (server == null) {
+                server = "clients3.google.com";
+            }
+            return "http://" + server + "/generate_204";
+        }
+
+        /**
+         * Detect if its possible to connect to the http url. DNS based detection techniques
+         * do not work at all hotspots. The best way to check is to perform a request to
+         * a known address that fetches the data we expect.
+         */
+        private synchronized Integer isMobileOk(Params params) {
+            Integer result = CMP_RESULT_CODE_NO_CONNECTION;
+            Uri orgUri = Uri.parse(params.mUrl);
+            Random rand = new Random();
+            mParams = params;
+
+            if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+                result = CMP_RESULT_CODE_NO_CONNECTION;
+                log("isMobileOk: X not mobile capable result=" + result);
+                return result;
+            }
+
+            // See if we've already determined we've got a provisioning connection,
+            // if so we don't need to do anything active.
+            MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
+                    mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+            boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork();
+            log("isMobileOk: isDefaultProvisioning=" + isDefaultProvisioning);
+
+            MobileDataStateTracker mdstHipri = (MobileDataStateTracker)
+                    mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+            boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork();
+            log("isMobileOk: isHipriProvisioning=" + isHipriProvisioning);
+
+            if (isDefaultProvisioning || isHipriProvisioning) {
+                result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+                log("isMobileOk: X default || hipri is provisioning result=" + result);
+                return result;
+            }
+
+            try {
+                // Continue trying to connect until time has run out
+                long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+
+                if (!mCs.isMobileDataStateTrackerReady()) {
+                    // Wait for MobileDataStateTracker to be ready.
+                    if (DBG) log("isMobileOk: mdst is not ready");
+                    while(SystemClock.elapsedRealtime() < endTime) {
+                        if (mCs.isMobileDataStateTrackerReady()) {
+                            // Enable fail fast as we'll do retries here and use a
+                            // hipri connection so the default connection stays active.
+                            if (DBG) log("isMobileOk: mdst ready, enable fail fast of mobile data");
+                            mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+                            break;
+                        }
+                        sleep(POLLING_SLEEP_SEC);
+                    }
+                }
+
+                log("isMobileOk: start hipri url=" + params.mUrl);
+
+                // First wait until we can start using hipri
+                Binder binder = new Binder();
+                while(SystemClock.elapsedRealtime() < endTime) {
+                    int ret = mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                            Phone.FEATURE_ENABLE_HIPRI, binder);
+                    if ((ret == PhoneConstants.APN_ALREADY_ACTIVE)
+                        || (ret == PhoneConstants.APN_REQUEST_STARTED)) {
+                            log("isMobileOk: hipri started");
+                            break;
+                    }
+                    if (VDBG) log("isMobileOk: hipri not started yet");
+                    result = CMP_RESULT_CODE_NO_CONNECTION;
+                    sleep(POLLING_SLEEP_SEC);
+                }
+
+                // Continue trying to connect until time has run out
+                while(SystemClock.elapsedRealtime() < endTime) {
+                    try {
+                        // Wait for hipri to connect.
+                        // TODO: Don't poll and handle situation where hipri fails
+                        // because default is retrying. See b/9569540
+                        NetworkInfo.State state = mCs
+                                .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+                        if (state != NetworkInfo.State.CONNECTED) {
+                            if (true/*VDBG*/) {
+                                log("isMobileOk: not connected ni=" +
+                                    mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+                            }
+                            sleep(POLLING_SLEEP_SEC);
+                            result = CMP_RESULT_CODE_NO_CONNECTION;
+                            continue;
+                        }
+
+                        // Hipri has started check if this is a provisioning url
+                        MobileDataStateTracker mdst = (MobileDataStateTracker)
+                                mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+                        if (mdst.isProvisioningNetwork()) {
+                            result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+                            if (DBG) log("isMobileOk: X isProvisioningNetwork result=" + result);
+                            return result;
+                        } else {
+                            if (DBG) log("isMobileOk: isProvisioningNetwork is false, continue");
+                        }
+
+                        // Get of the addresses associated with the url host. We need to use the
+                        // address otherwise HttpURLConnection object will use the name to get
+                        // the addresses and will try every address but that will bypass the
+                        // route to host we setup and the connection could succeed as the default
+                        // interface might be connected to the internet via wifi or other interface.
+                        InetAddress[] addresses;
+                        try {
+                            addresses = InetAddress.getAllByName(orgUri.getHost());
+                        } catch (UnknownHostException e) {
+                            result = CMP_RESULT_CODE_NO_DNS;
+                            log("isMobileOk: X UnknownHostException result=" + result);
+                            return result;
+                        }
+                        log("isMobileOk: addresses=" + inetAddressesToString(addresses));
+
+                        // Get the type of addresses supported by this link
+                        LinkProperties lp = mCs.getLinkProperties(
+                                ConnectivityManager.TYPE_MOBILE_HIPRI);
+                        boolean linkHasIpv4 = lp.hasIPv4Address();
+                        boolean linkHasIpv6 = lp.hasIPv6Address();
+                        log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+                                + " linkHasIpv6=" + linkHasIpv6);
+
+                        final ArrayList<InetAddress> validAddresses =
+                                new ArrayList<InetAddress>(addresses.length);
+
+                        for (InetAddress addr : addresses) {
+                            if (((addr instanceof Inet4Address) && linkHasIpv4) ||
+                                    ((addr instanceof Inet6Address) && linkHasIpv6)) {
+                                validAddresses.add(addr);
+                            }
+                        }
+
+                        if (validAddresses.size() == 0) {
+                            return CMP_RESULT_CODE_NO_CONNECTION;
+                        }
+
+                        int addrTried = 0;
+                        while (true) {
+                            // Loop through at most MAX_LOOPS valid addresses or until
+                            // we run out of time
+                            if (addrTried++ >= MAX_LOOPS) {
+                                log("isMobileOk: too many loops tried - giving up");
+                                break;
+                            }
+                            if (SystemClock.elapsedRealtime() >= endTime) {
+                                log("isMobileOk: spend too much time - giving up");
+                                break;
+                            }
+
+                            InetAddress hostAddr = validAddresses.get(rand.nextInt(
+                                    validAddresses.size()));
+
+                            // Make a route to host so we check the specific interface.
+                            if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
+                                    hostAddr.getAddress())) {
+                                // Wait a short time to be sure the route is established ??
+                                log("isMobileOk:"
+                                        + " wait to establish route to hostAddr=" + hostAddr);
+                                sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC);
+                            } else {
+                                log("isMobileOk:"
+                                        + " could not establish route to hostAddr=" + hostAddr);
+                                // Wait a short time before the next attempt
+                                sleep(NET_ERROR_SLEEP_SEC);
+                                continue;
+                            }
+
+                            // Rewrite the url to have numeric address to use the specific route
+                            // using http for half the attempts and https for the other half.
+                            // Doing https first and http second as on a redirected walled garden
+                            // such as t-mobile uses we get a SocketTimeoutException: "SSL
+                            // handshake timed out" which we declare as
+                            // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by
+                            // having http second we will be using logic used for some time.
+                            URL newUrl;
+                            String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http";
+                            newUrl = new URL(scheme, hostAddr.getHostAddress(),
+                                        orgUri.getPath());
+                            log("isMobileOk: newUrl=" + newUrl);
+
+                            HttpURLConnection urlConn = null;
+                            try {
+                                // Open the connection set the request headers and get the response
+                                urlConn = (HttpURLConnection)newUrl.openConnection(
+                                        java.net.Proxy.NO_PROXY);
+                                if (scheme.equals("https")) {
+                                    ((HttpsURLConnection)urlConn).setHostnameVerifier(
+                                            new CheckMpHostnameVerifier(orgUri));
+                                }
+                                urlConn.setInstanceFollowRedirects(false);
+                                urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                                urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
+                                urlConn.setUseCaches(false);
+                                urlConn.setAllowUserInteraction(false);
+                                // Set the "Connection" to "Close" as by default "Keep-Alive"
+                                // is used which is useless in this case.
+                                urlConn.setRequestProperty("Connection", "close");
+                                int responseCode = urlConn.getResponseCode();
+
+                                // For debug display the headers
+                                Map<String, List<String>> headers = urlConn.getHeaderFields();
+                                log("isMobileOk: headers=" + headers);
+
+                                // Close the connection
+                                urlConn.disconnect();
+                                urlConn = null;
+
+                                if (mTestingFailures) {
+                                    // Pretend no connection, this tests using http and https
+                                    result = CMP_RESULT_CODE_NO_CONNECTION;
+                                    log("isMobileOk: TESTING_FAILURES, pretend no connction");
+                                    continue;
+                                }
+
+                                if (responseCode == 204) {
+                                    // Return
+                                    result = CMP_RESULT_CODE_CONNECTABLE;
+                                    log("isMobileOk: X got expected responseCode=" + responseCode
+                                            + " result=" + result);
+                                    return result;
+                                } else {
+                                    // Retry to be sure this was redirected, we've gotten
+                                    // occasions where a server returned 200 even though
+                                    // the device didn't have a "warm" sim.
+                                    log("isMobileOk: not expected responseCode=" + responseCode);
+                                    // TODO - it would be nice in the single-address case to do
+                                    // another DNS resolve here, but flushing the cache is a bit
+                                    // heavy-handed.
+                                    result = CMP_RESULT_CODE_REDIRECTED;
+                                }
+                            } catch (Exception e) {
+                                log("isMobileOk: HttpURLConnection Exception" + e);
+                                result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
+                                if (urlConn != null) {
+                                    urlConn.disconnect();
+                                    urlConn = null;
+                                }
+                                sleep(NET_ERROR_SLEEP_SEC);
+                                continue;
+                            }
+                        }
+                        log("isMobileOk: X loops|timed out result=" + result);
+                        return result;
+                    } catch (Exception e) {
+                        log("isMobileOk: Exception e=" + e);
+                        continue;
+                    }
+                }
+                log("isMobileOk: timed out");
+            } finally {
+                log("isMobileOk: F stop hipri");
+                mCs.setEnableFailFastMobileData(DctConstants.DISABLED);
+                mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                        Phone.FEATURE_ENABLE_HIPRI);
+
+                // Wait for hipri to disconnect.
+                long endTime = SystemClock.elapsedRealtime() + 5000;
+
+                while(SystemClock.elapsedRealtime() < endTime) {
+                    NetworkInfo.State state = mCs
+                            .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+                    if (state != NetworkInfo.State.DISCONNECTED) {
+                        if (VDBG) {
+                            log("isMobileOk: connected ni=" +
+                                mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+                        }
+                        sleep(POLLING_SLEEP_SEC);
+                        continue;
+                    }
+                }
+
+                log("isMobileOk: X result=" + result);
+            }
+            return result;
+        }
+
+        @Override
+        protected Integer doInBackground(Params... params) {
+            return isMobileOk(params[0]);
+        }
+
+        @Override
+        protected void onPostExecute(Integer result) {
+            log("onPostExecute: result=" + result);
+            if ((mParams != null) && (mParams.mCb != null)) {
+                mParams.mCb.onComplete(result);
+            }
+        }
+
+        private String inetAddressesToString(InetAddress[] addresses) {
+            StringBuffer sb = new StringBuffer();
+            boolean firstTime = true;
+            for(InetAddress addr : addresses) {
+                if (firstTime) {
+                    firstTime = false;
+                } else {
+                    sb.append(",");
+                }
+                sb.append(addr);
+            }
+            return sb.toString();
+        }
+
+        private void printNetworkInfo() {
+            boolean hasIccCard = mTm.hasIccCard();
+            int simState = mTm.getSimState();
+            log("hasIccCard=" + hasIccCard
+                    + " simState=" + simState);
+            NetworkInfo[] ni = mCs.getAllNetworkInfo();
+            if (ni != null) {
+                log("ni.length=" + ni.length);
+                for (NetworkInfo netInfo: ni) {
+                    log("netInfo=" + netInfo.toString());
+                }
+            } else {
+                log("no network info ni=null");
+            }
+        }
+
+        /**
+         * Sleep for a few seconds then return.
+         * @param seconds
+         */
+        private static void sleep(int seconds) {
+            try {
+                Thread.sleep(seconds * 1000);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        private static void log(String s) {
+            Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+        }
+    }
+
+    // TODO: Move to ConnectivityManager and make public?
+    private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION =
+            "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION";
+
+    private BroadcastReceiver mProvisioningReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(CONNECTED_TO_PROVISIONING_NETWORK_ACTION)) {
+                handleMobileProvisioningAction(intent.getStringExtra("EXTRA_URL"));
+            }
+        }
+    };
+
+    private void handleMobileProvisioningAction(String url) {
+        // Notication mark notification as not visible
+        setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+        // If provisioning network handle as a special case,
+        // otherwise launch browser with the intent directly.
+        NetworkInfo ni = getProvisioningNetworkInfo();
+        if ((ni != null) && ni.isConnectedToProvisioningNetwork()) {
+            if (DBG) log("handleMobileProvisioningAction: on provisioning network");
+            MobileDataStateTracker mdst = (MobileDataStateTracker)
+                    mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+            mdst.enableMobileProvisioning(url);
+        } else {
+            if (DBG) log("handleMobileProvisioningAction: on default network");
+            Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+                    Intent.CATEGORY_APP_BROWSER);
+            newIntent.setData(Uri.parse(url));
+            newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                    Intent.FLAG_ACTIVITY_NEW_TASK);
+            try {
+                mContext.startActivity(newIntent);
+            } catch (ActivityNotFoundException e) {
+                loge("handleMobileProvisioningAction: startActivity failed" + e);
+            }
+        }
+    }
+
+    private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+    private volatile boolean mIsNotificationVisible = false;
+
+    private void setProvNotificationVisible(boolean visible, int networkType, String extraInfo,
+            String url) {
+        if (DBG) {
+            log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
+                + " extraInfo=" + extraInfo + " url=" + url);
+        }
+
+        Resources r = Resources.getSystem();
+        NotificationManager notificationManager = (NotificationManager) mContext
+            .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (visible) {
+            CharSequence title;
+            CharSequence details;
+            int icon;
+            Intent intent;
+            Notification notification = new Notification();
+            switch (networkType) {
+                case ConnectivityManager.TYPE_WIFI:
+                    title = r.getString(R.string.wifi_available_sign_in, 0);
+                    details = r.getString(R.string.network_available_sign_in_detailed,
+                            extraInfo);
+                    icon = R.drawable.stat_notify_wifi_in_range;
+                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                            Intent.FLAG_ACTIVITY_NEW_TASK);
+                    notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+                    break;
+                case ConnectivityManager.TYPE_MOBILE:
+                case ConnectivityManager.TYPE_MOBILE_HIPRI:
+                    title = r.getString(R.string.network_available_sign_in, 0);
+                    // TODO: Change this to pull from NetworkInfo once a printable
+                    // name has been added to it
+                    details = mTelephonyManager.getNetworkOperatorName();
+                    icon = R.drawable.stat_notify_rssi_in_range;
+                    intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+                    intent.putExtra("EXTRA_URL", url);
+                    intent.setFlags(0);
+                    notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+                    break;
+                default:
+                    title = r.getString(R.string.network_available_sign_in, 0);
+                    details = r.getString(R.string.network_available_sign_in_detailed,
+                            extraInfo);
+                    icon = R.drawable.stat_notify_rssi_in_range;
+                    intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+                            Intent.FLAG_ACTIVITY_NEW_TASK);
+                    notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+                    break;
+            }
+
+            notification.when = 0;
+            notification.icon = icon;
+            notification.flags = Notification.FLAG_AUTO_CANCEL;
+            notification.tickerText = title;
+            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+            try {
+                notificationManager.notify(NOTIFICATION_ID, networkType, notification);
+            } catch (NullPointerException npe) {
+                loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
+                npe.printStackTrace();
+            }
+        } else {
+            try {
+                notificationManager.cancel(NOTIFICATION_ID, networkType);
+            } catch (NullPointerException npe) {
+                loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
+                npe.printStackTrace();
+            }
+        }
+        mIsNotificationVisible = visible;
+    }
+
+    /** Location to an updatable file listing carrier provisioning urls.
+     *  An example:
+     *
+     * <?xml version="1.0" encoding="utf-8"?>
+     *  <provisioningUrls>
+     *   <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
+     *   <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
+     *  </provisioningUrls>
+     */
+    private static final String PROVISIONING_URL_PATH =
+            "/data/misc/radio/provisioning_urls.xml";
+    private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);
+
+    /** XML tag for root element. */
+    private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
+    /** XML tag for individual url */
+    private static final String TAG_PROVISIONING_URL = "provisioningUrl";
+    /** XML tag for redirected url */
+    private static final String TAG_REDIRECTED_URL = "redirectedUrl";
+    /** XML attribute for mcc */
+    private static final String ATTR_MCC = "mcc";
+    /** XML attribute for mnc */
+    private static final String ATTR_MNC = "mnc";
+
+    private static final int REDIRECTED_PROVISIONING = 1;
+    private static final int PROVISIONING = 2;
+
+    private String getProvisioningUrlBaseFromFile(int type) {
+        FileReader fileReader = null;
+        XmlPullParser parser = null;
+        Configuration config = mContext.getResources().getConfiguration();
+        String tagType;
+
+        switch (type) {
+            case PROVISIONING:
+                tagType = TAG_PROVISIONING_URL;
+                break;
+            case REDIRECTED_PROVISIONING:
+                tagType = TAG_REDIRECTED_URL;
+                break;
+            default:
+                throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
+                        type);
+        }
+
+        try {
+            fileReader = new FileReader(mProvisioningUrlFile);
+            parser = Xml.newPullParser();
+            parser.setInput(fileReader);
+            XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                String element = parser.getName();
+                if (element == null) break;
+
+                if (element.equals(tagType)) {
+                    String mcc = parser.getAttributeValue(null, ATTR_MCC);
+                    try {
+                        if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
+                            String mnc = parser.getAttributeValue(null, ATTR_MNC);
+                            if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
+                                parser.next();
+                                if (parser.getEventType() == XmlPullParser.TEXT) {
+                                    return parser.getText();
+                                }
+                            }
+                        }
+                    } catch (NumberFormatException e) {
+                        loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e);
+                    }
+                }
+            }
+            return null;
+        } catch (FileNotFoundException e) {
+            loge("Carrier Provisioning Urls file not found");
+        } catch (XmlPullParserException e) {
+            loge("Xml parser exception reading Carrier Provisioning Urls file: " + e);
+        } catch (IOException e) {
+            loge("I/O exception reading Carrier Provisioning Urls file: " + e);
+        } finally {
+            if (fileReader != null) {
+                try {
+                    fileReader.close();
+                } catch (IOException e) {}
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getMobileRedirectedProvisioningUrl() {
+        enforceConnectivityInternalPermission();
+        String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
+        if (TextUtils.isEmpty(url)) {
+            url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
+        }
+        return url;
+    }
+
+    @Override
+    public String getMobileProvisioningUrl() {
+        enforceConnectivityInternalPermission();
+        String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+        if (TextUtils.isEmpty(url)) {
+            url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+            log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
+        } else {
+            log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
+        }
+        // populate the iccid, imei and phone number in the provisioning url.
+        if (!TextUtils.isEmpty(url)) {
+            String phoneNumber = mTelephonyManager.getLine1Number();
+            if (TextUtils.isEmpty(phoneNumber)) {
+                phoneNumber = "0000000000";
+            }
+            url = String.format(url,
+                    mTelephonyManager.getSimSerialNumber() /* ICCID */,
+                    mTelephonyManager.getDeviceId() /* IMEI */,
+                    phoneNumber /* Phone numer */);
+        }
+
+        return url;
+    }
+
+    @Override
+    public void setProvisioningNotificationVisible(boolean visible, int networkType,
+            String extraInfo, String url) {
+        enforceConnectivityInternalPermission();
+        setProvNotificationVisible(visible, networkType, extraInfo, url);
+    }
+
+    @Override
+    public void setAirplaneMode(boolean enable) {
+        enforceConnectivityInternalPermission();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            final ContentResolver cr = mContext.getContentResolver();
+            Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            intent.putExtra("state", enable);
+            mContext.sendBroadcast(intent);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void onUserStart(int userId) {
+        synchronized(mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn != null) {
+                loge("Starting user already has a VPN");
+                return;
+            }
+            userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
+            mVpns.put(userId, userVpn);
+            userVpn.startMonitoring(mContext, mTrackerHandler);
+        }
+    }
+
+    private void onUserStop(int userId) {
+        synchronized(mVpns) {
+            Vpn userVpn = mVpns.get(userId);
+            if (userVpn == null) {
+                loge("Stopping user has no VPN");
+                return;
+            }
+            mVpns.delete(userId);
+        }
+    }
+
+    private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId == UserHandle.USER_NULL) return;
+
+            if (Intent.ACTION_USER_STARTING.equals(action)) {
+                onUserStart(userId);
+            } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+                onUserStop(userId);
+            }
+        }
+    };
+
+    @Override
+    public LinkQualityInfo getLinkQualityInfo(int networkType) {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(networkType)) {
+            return mNetTrackers[networkType].getLinkQualityInfo();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public LinkQualityInfo getActiveLinkQualityInfo() {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+            return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public LinkQualityInfo[] getAllLinkQualityInfo() {
+        enforceAccessPermission();
+        final ArrayList<LinkQualityInfo> result = Lists.newArrayList();
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                LinkQualityInfo li = tracker.getLinkQualityInfo();
+                if (li != null) {
+                    result.add(li);
+                }
+            }
+        }
+
+        return result.toArray(new LinkQualityInfo[result.size()]);
+    }
+
+    /* Infrastructure for network sampling */
+
+    private void handleNetworkSamplingTimeout() {
+
+        log("Sampling interval elapsed, updating statistics ..");
+
+        // initialize list of interfaces ..
+        Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample =
+                new HashMap<String, SamplingDataTracker.SamplingSnapshot>();
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                String ifaceName = tracker.getNetworkInterfaceName();
+                if (ifaceName != null) {
+                    mapIfaceToSample.put(ifaceName, null);
+                }
+            }
+        }
+
+        // Read samples for all interfaces
+        SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample);
+
+        // process samples for all networks
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                String ifaceName = tracker.getNetworkInterfaceName();
+                SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName);
+                if (ss != null) {
+                    // end the previous sampling cycle
+                    tracker.stopSampling(ss);
+                    // start a new sampling cycle ..
+                    tracker.startSampling(ss);
+                }
+            }
+        }
+
+        log("Done.");
+
+        int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+                DEFAULT_SAMPLING_INTERVAL_IN_SECONDS);
+
+        if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds");
+
+        setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
+    }
+
+    void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
+        long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+    }
+}
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
new file mode 100644
index 0000000..783dff1
--- /dev/null
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -0,0 +1,135 @@
+/*
+ * 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.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.hardware.IConsumerIrService;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import java.lang.RuntimeException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+public class ConsumerIrService extends IConsumerIrService.Stub {
+    private static final String TAG = "ConsumerIrService";
+
+    private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */
+
+    private static native int halOpen();
+    private static native int halTransmit(int halObject, int carrierFrequency, int[] pattern);
+    private static native int[] halGetCarrierFrequencies(int halObject);
+
+    private final Context mContext;
+    private final PowerManager.WakeLock mWakeLock;
+    private final int mHal;
+    private final Object mHalLock = new Object();
+
+    ConsumerIrService(Context context) {
+        mContext = context;
+        PowerManager pm = (PowerManager)context.getSystemService(
+                Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.setReferenceCounted(true);
+
+        mHal = halOpen();
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) {
+            if (mHal == 0) {
+                throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!");
+            }
+        } else if (mHal != 0) {
+            throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!");
+        }
+    }
+
+    @Override
+    public boolean hasIrEmitter() {
+        return mHal != 0;
+    }
+
+    private void throwIfNoIrEmitter() {
+        if (mHal == 0) {
+            throw new UnsupportedOperationException("IR emitter not available");
+        }
+    }
+
+
+    @Override
+    public void transmit(String packageName, int carrierFrequency, int[] pattern) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TRANSMIT_IR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires TRANSMIT_IR permission");
+        }
+
+        long totalXmitTime = 0;
+
+        for (int slice : pattern) {
+            if (slice <= 0) {
+                throw new IllegalArgumentException("Non-positive IR slice");
+            }
+            totalXmitTime += slice;
+        }
+
+        if (totalXmitTime > MAX_XMIT_TIME ) {
+            throw new IllegalArgumentException("IR pattern too long");
+        }
+
+        throwIfNoIrEmitter();
+
+        // Right now there is no mechanism to ensure fair queing of IR requests
+        synchronized (mHalLock) {
+            int err = halTransmit(mHal, carrierFrequency, pattern);
+
+            if (err < 0) {
+                Slog.e(TAG, "Error transmitting: " + err);
+            }
+        }
+    }
+
+    @Override
+    public int[] getCarrierFrequencies() {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TRANSMIT_IR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires TRANSMIT_IR permission");
+        }
+
+        throwIfNoIrEmitter();
+
+        synchronized(mHalLock) {
+            return halGetCarrierFrequencies(mHal);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
new file mode 100644
index 0000000..a478b2f
--- /dev/null
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 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 java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+import com.android.internal.os.BackgroundThread;
+import com.android.server.location.ComprehensiveCountryDetector;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.ICountryDetector;
+import android.location.ICountryListener;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.Slog;
+
+/**
+ * This class detects the country that the user is in through
+ * {@link ComprehensiveCountryDetector}.
+ *
+ * @hide
+ */
+public class CountryDetectorService extends ICountryDetector.Stub implements Runnable {
+
+    /**
+     * The class represents the remote listener, it will also removes itself
+     * from listener list when the remote process was died.
+     */
+    private final class Receiver implements IBinder.DeathRecipient {
+        private final ICountryListener mListener;
+        private final IBinder mKey;
+
+        public Receiver(ICountryListener listener) {
+            mListener = listener;
+            mKey = listener.asBinder();
+        }
+
+        public void binderDied() {
+            removeListener(mKey);
+        }
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (otherObj instanceof Receiver) {
+                return mKey.equals(((Receiver) otherObj).mKey);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mKey.hashCode();
+        }
+
+        public ICountryListener getListener() {
+            return mListener;
+        }
+    }
+
+    private final static String TAG = "CountryDetector";
+
+    /** Whether to dump the state of the country detector service to bugreports */
+    private static final boolean DEBUG = false;
+
+    private final HashMap<IBinder, Receiver> mReceivers;
+    private final Context mContext;
+    private ComprehensiveCountryDetector mCountryDetector;
+    private boolean mSystemReady;
+    private Handler mHandler;
+    private CountryListener mLocationBasedDetectorListener;
+
+    public CountryDetectorService(Context context) {
+        super();
+        mReceivers = new HashMap<IBinder, Receiver>();
+        mContext = context;
+    }
+
+    @Override
+    public Country detectCountry() {
+        if (!mSystemReady) {
+            return null;   // server not yet active
+        } else {
+            return mCountryDetector.detectCountry();
+        }
+    }
+
+    /**
+     * Add the ICountryListener into the listener list.
+     */
+    @Override
+    public void addCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        addListener(listener);
+    }
+
+    /**
+     * Remove the ICountryListener from the listener list.
+     */
+    @Override
+    public void removeCountryListener(ICountryListener listener) throws RemoteException {
+        if (!mSystemReady) {
+            throw new RemoteException();
+        }
+        removeListener(listener.asBinder());
+    }
+
+    private void addListener(ICountryListener listener) {
+        synchronized (mReceivers) {
+            Receiver r = new Receiver(listener);
+            try {
+                listener.asBinder().linkToDeath(r, 0);
+                mReceivers.put(listener.asBinder(), r);
+                if (mReceivers.size() == 1) {
+                    Slog.d(TAG, "The first listener is added");
+                    setCountryListener(mLocationBasedDetectorListener);
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "linkToDeath failed:", e);
+            }
+        }
+    }
+
+    private void removeListener(IBinder key) {
+        synchronized (mReceivers) {
+            mReceivers.remove(key);
+            if (mReceivers.isEmpty()) {
+                setCountryListener(null);
+                Slog.d(TAG, "No listener is left");
+            }
+        }
+    }
+
+
+    protected void notifyReceivers(Country country) {
+        synchronized(mReceivers) {
+            for (Receiver receiver : mReceivers.values()) {
+                try {
+                    receiver.getListener().onCountryDetected(country);
+                } catch (RemoteException e) {
+                    // TODO: Shall we remove the receiver?
+                    Slog.e(TAG, "notifyReceivers failed:", e);
+                }
+            }
+        }
+    }
+
+    void systemRunning() {
+        // Shall we wait for the initialization finish.
+        BackgroundThread.getHandler().post(this);
+    }
+
+    private void initialize() {
+        mCountryDetector = new ComprehensiveCountryDetector(mContext);
+        mLocationBasedDetectorListener = new CountryListener() {
+            public void onCountryDetected(final Country country) {
+                mHandler.post(new Runnable() {
+                    public void run() {
+                        notifyReceivers(country);
+                    }
+                });
+            }
+        };
+    }
+
+    public void run() {
+        mHandler = new Handler();
+        initialize();
+        mSystemReady = true;
+    }
+
+    protected void setCountryListener(final CountryListener listener) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mCountryDetector.setCountryListener(listener);
+            }
+        });
+    }
+
+    // For testing
+    boolean isSystemReady() {
+        return mSystemReady;
+    }
+
+    @SuppressWarnings("unused")
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        if (!DEBUG) return;
+        try {
+            final Printer p = new PrintWriterPrinter(fout);
+            p.println("CountryDetectorService state:");
+            p.println("  Number of listeners=" + mReceivers.keySet().size());
+            if (mCountryDetector == null) {
+                p.println("  ComprehensiveCountryDetector not initialized");
+            } else {
+                p.println("  " + mCountryDetector.toString());
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Failed to dump CountryDetectorService: ", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java
new file mode 100644
index 0000000..ac25dc5
--- /dev/null
+++ b/services/core/java/com/android/server/DiskStatsService.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2010 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.Binder;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.StatFs;
+import android.os.SystemClock;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * This service exists only as a "dumpsys" target which reports
+ * statistics about the status of the disk.
+ */
+public class DiskStatsService extends Binder {
+    private static final String TAG = "DiskStatsService";
+
+    private final Context mContext;
+
+    public DiskStatsService(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        // Run a quick-and-dirty performance test: write 512 bytes
+        byte[] junk = new byte[512];
+        for (int i = 0; i < junk.length; i++) junk[i] = (byte) i;  // Write nonzero bytes
+
+        File tmp = new File(Environment.getDataDirectory(), "system/perftest.tmp");
+        FileOutputStream fos = null;
+        IOException error = null;
+
+        long before = SystemClock.uptimeMillis();
+        try {
+            fos = new FileOutputStream(tmp);
+            fos.write(junk);
+        } catch (IOException e) {
+            error = e;
+        } finally {
+            try { if (fos != null) fos.close(); } catch (IOException e) {}
+        }
+
+        long after = SystemClock.uptimeMillis();
+        if (tmp.exists()) tmp.delete();
+
+        if (error != null) {
+            pw.print("Test-Error: ");
+            pw.println(error.toString());
+        } else {
+            pw.print("Latency: ");
+            pw.print(after - before);
+            pw.println("ms [512B Data Write]");
+        }
+
+        reportFreeSpace(Environment.getDataDirectory(), "Data", pw);
+        reportFreeSpace(Environment.getDownloadCacheDirectory(), "Cache", pw);
+        reportFreeSpace(new File("/system"), "System", pw);
+
+        // TODO: Read /proc/yaffs and report interesting values;
+        // add configurable (through args) performance test parameters.
+    }
+
+    private void reportFreeSpace(File path, String name, PrintWriter pw) {
+        try {
+            StatFs statfs = new StatFs(path.getPath());
+            long bsize = statfs.getBlockSize();
+            long avail = statfs.getAvailableBlocks();
+            long total = statfs.getBlockCount();
+            if (bsize <= 0 || total <= 0) {
+                throw new IllegalArgumentException(
+                        "Invalid stat: bsize=" + bsize + " avail=" + avail + " total=" + total);
+            }
+
+            pw.print(name);
+            pw.print("-Free: ");
+            pw.print(avail * bsize / 1024);
+            pw.print("K / ");
+            pw.print(total * bsize / 1024);
+            pw.print("K total = ");
+            pw.print(avail * 100 / total);
+            pw.println("% free");
+        } catch (IllegalArgumentException e) {
+            pw.print(name);
+            pw.print("-Error: ");
+            pw.println(e.toString());
+            return;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
new file mode 100644
index 0000000..4a8bf72
--- /dev/null
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+
+/**
+ * <p>DockObserver monitors for a docking station.
+ */
+final class DockObserver extends UEventObserver {
+    private static final String TAG = DockObserver.class.getSimpleName();
+
+    private static final String DOCK_UEVENT_MATCH = "DEVPATH=/devices/virtual/switch/dock";
+    private static final String DOCK_STATE_PATH = "/sys/class/switch/dock/state";
+
+    private static final int MSG_DOCK_STATE_CHANGED = 0;
+
+    private final Object mLock = new Object();
+
+    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    private boolean mSystemReady;
+
+    private final Context mContext;
+    private final PowerManager mPowerManager;
+    private final PowerManager.WakeLock mWakeLock;
+
+    public DockObserver(Context context) {
+        mContext = context;
+
+        mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        init();  // set initial status
+        startObserving(DOCK_UEVENT_MATCH);
+    }
+
+    @Override
+    public void onUEvent(UEventObserver.UEvent event) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Slog.v(TAG, "Dock UEVENT: " + event.toString());
+        }
+
+        synchronized (mLock) {
+            try {
+                int newState = Integer.parseInt(event.get("SWITCH_STATE"));
+                if (newState != mDockState) {
+                    mPreviousDockState = mDockState;
+                    mDockState = newState;
+                    if (mSystemReady) {
+                        // Wake up immediately when docked or undocked.
+                        mPowerManager.wakeUp(SystemClock.uptimeMillis());
+
+                        updateLocked();
+                    }
+                }
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Could not parse switch state from event " + event);
+            }
+        }
+    }
+
+    private void init() {
+        synchronized (mLock) {
+            try {
+                char[] buffer = new char[1024];
+                FileReader file = new FileReader(DOCK_STATE_PATH);
+                try {
+                    int len = file.read(buffer, 0, 1024);
+                    mDockState = Integer.valueOf((new String(buffer, 0, len)).trim());
+                    mPreviousDockState = mDockState;
+                } finally {
+                    file.close();
+                }
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "This kernel does not have dock station support");
+            } catch (Exception e) {
+                Slog.e(TAG, "" , e);
+            }
+        }
+    }
+
+    void systemReady() {
+        synchronized (mLock) {
+            // don't bother broadcasting undocked here
+            if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                updateLocked();
+            }
+            mSystemReady = true;
+        }
+    }
+
+    private void updateLocked() {
+        mWakeLock.acquire();
+        mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED);
+    }
+
+    private void handleDockStateChange() {
+        synchronized (mLock) {
+            Slog.i(TAG, "Dock state changed: " + mDockState);
+
+            // Skip the dock intent if not yet provisioned.
+            final ContentResolver cr = mContext.getContentResolver();
+            if (Settings.Global.getInt(cr,
+                    Settings.Global.DEVICE_PROVISIONED, 0) == 0) {
+                Slog.i(TAG, "Device not provisioned, skipping dock broadcast");
+                return;
+            }
+
+            // Pack up the values and broadcast them to everyone
+            Intent intent = new Intent(Intent.ACTION_DOCK_EVENT);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.putExtra(Intent.EXTRA_DOCK_STATE, mDockState);
+
+            // Play a sound to provide feedback to confirm dock connection.
+            // Particularly useful for flaky contact pins...
+            if (Settings.Global.getInt(cr,
+                    Settings.Global.DOCK_SOUNDS_ENABLED, 1) == 1) {
+                String whichSound = null;
+                if (mDockState == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                    if ((mPreviousDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+                        (mPreviousDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
+                        whichSound = Settings.Global.DESK_UNDOCK_SOUND;
+                    } else if (mPreviousDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+                        whichSound = Settings.Global.CAR_UNDOCK_SOUND;
+                    }
+                } else {
+                    if ((mDockState == Intent.EXTRA_DOCK_STATE_DESK) ||
+                        (mDockState == Intent.EXTRA_DOCK_STATE_LE_DESK) ||
+                        (mDockState == Intent.EXTRA_DOCK_STATE_HE_DESK)) {
+                        whichSound = Settings.Global.DESK_DOCK_SOUND;
+                    } else if (mDockState == Intent.EXTRA_DOCK_STATE_CAR) {
+                        whichSound = Settings.Global.CAR_DOCK_SOUND;
+                    }
+                }
+
+                if (whichSound != null) {
+                    final String soundPath = Settings.Global.getString(cr, whichSound);
+                    if (soundPath != null) {
+                        final Uri soundUri = Uri.parse("file://" + soundPath);
+                        if (soundUri != null) {
+                            final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+                            if (sfx != null) {
+                                sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                                sfx.play();
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Send the dock event intent.
+            // There are many components in the system watching for this so as to
+            // adjust audio routing, screen orientation, etc.
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+
+            // Release the wake lock that was acquired when the message was posted.
+            mWakeLock.release();
+        }
+    }
+
+    private final Handler mHandler = new Handler(true /*async*/) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DOCK_STATE_CHANGED:
+                    handleDockStateChange();
+                    break;
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
new file mode 100644
index 0000000..29b04da
--- /dev/null
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -0,0 +1,781 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.DropBoxManager;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Message;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.Time;
+import android.util.Slog;
+
+import com.android.internal.os.IDropBoxManagerService;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Implementation of {@link IDropBoxManagerService} using the filesystem.
+ * Clients use {@link DropBoxManager} to access this service.
+ */
+public final class DropBoxManagerService extends IDropBoxManagerService.Stub {
+    private static final String TAG = "DropBoxManagerService";
+    private static final int DEFAULT_AGE_SECONDS = 3 * 86400;
+    private static final int DEFAULT_MAX_FILES = 1000;
+    private static final int DEFAULT_QUOTA_KB = 5 * 1024;
+    private static final int DEFAULT_QUOTA_PERCENT = 10;
+    private static final int DEFAULT_RESERVE_PERCENT = 10;
+    private static final int QUOTA_RESCAN_MILLIS = 5000;
+
+    // mHandler 'what' value.
+    private static final int MSG_SEND_BROADCAST = 1;
+
+    private static final boolean PROFILE_DUMP = false;
+
+    // TODO: This implementation currently uses one file per entry, which is
+    // inefficient for smallish entries -- consider using a single queue file
+    // per tag (or even globally) instead.
+
+    // The cached context and derived objects
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final File mDropBoxDir;
+
+    // Accounting of all currently written log files (set in init()).
+
+    private FileList mAllFiles = null;
+    private HashMap<String, FileList> mFilesByTag = null;
+
+    // Various bits of disk information
+
+    private StatFs mStatFs = null;
+    private int mBlockSize = 0;
+    private int mCachedQuotaBlocks = 0;  // Space we can use: computed from free space, etc.
+    private long mCachedQuotaUptimeMillis = 0;
+
+    private volatile boolean mBooted = false;
+
+    // Provide a way to perform sendBroadcast asynchronously to avoid deadlocks.
+    private final Handler mHandler;
+
+    /** Receives events that might indicate a need to clean up files. */
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent != null && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+                mBooted = true;
+                return;
+            }
+
+            // Else, for ACTION_DEVICE_STORAGE_LOW:
+            mCachedQuotaUptimeMillis = 0;  // Force a re-check of quota size
+
+            // Run the initialization in the background (not this main thread).
+            // The init() and trimToFit() methods are synchronized, so they still
+            // block other users -- but at least the onReceive() call can finish.
+            new Thread() {
+                public void run() {
+                    try {
+                        init();
+                        trimToFit();
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Can't init", e);
+                    }
+                }
+            }.start();
+        }
+    };
+
+    /**
+     * Creates an instance of managed drop box storage.  Normally there is one of these
+     * run by the system, but others can be created for testing and other purposes.
+     *
+     * @param context to use for receiving free space & gservices intents
+     * @param path to store drop box entries in
+     */
+    public DropBoxManagerService(final Context context, File path) {
+        mDropBoxDir = path;
+
+        // Set up intent receivers
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_DEVICE_STORAGE_LOW);
+        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+        context.registerReceiver(mReceiver, filter);
+
+        mContentResolver.registerContentObserver(
+            Settings.Global.CONTENT_URI, true,
+            new ContentObserver(new Handler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    mReceiver.onReceive(context, (Intent) null);
+                }
+            });
+
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_SEND_BROADCAST) {
+                    mContext.sendBroadcastAsUser((Intent)msg.obj, UserHandle.OWNER,
+                            android.Manifest.permission.READ_LOGS);
+                }
+            }
+        };
+
+        // The real work gets done lazily in init() -- that way service creation always
+        // succeeds, and things like disk problems cause individual method failures.
+    }
+
+    /** Unregisters broadcast receivers and any other hooks -- for test instances */
+    public void stop() {
+        mContext.unregisterReceiver(mReceiver);
+    }
+
+    @Override
+    public void add(DropBoxManager.Entry entry) {
+        File temp = null;
+        OutputStream output = null;
+        final String tag = entry.getTag();
+        try {
+            int flags = entry.getFlags();
+            if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+            init();
+            if (!isTagEnabled(tag)) return;
+            long max = trimToFit();
+            long lastTrim = System.currentTimeMillis();
+
+            byte[] buffer = new byte[mBlockSize];
+            InputStream input = entry.getInputStream();
+
+            // First, accumulate up to one block worth of data in memory before
+            // deciding whether to compress the data or not.
+
+            int read = 0;
+            while (read < buffer.length) {
+                int n = input.read(buffer, read, buffer.length - read);
+                if (n <= 0) break;
+                read += n;
+            }
+
+            // If we have at least one block, compress it -- otherwise, just write
+            // the data in uncompressed form.
+
+            temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
+            int bufferSize = mBlockSize;
+            if (bufferSize > 4096) bufferSize = 4096;
+            if (bufferSize < 512) bufferSize = 512;
+            FileOutputStream foutput = new FileOutputStream(temp);
+            output = new BufferedOutputStream(foutput, bufferSize);
+            if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
+                output = new GZIPOutputStream(output);
+                flags = flags | DropBoxManager.IS_GZIPPED;
+            }
+
+            do {
+                output.write(buffer, 0, read);
+
+                long now = System.currentTimeMillis();
+                if (now - lastTrim > 30 * 1000) {
+                    max = trimToFit();  // In case data dribbles in slowly
+                    lastTrim = now;
+                }
+
+                read = input.read(buffer);
+                if (read <= 0) {
+                    FileUtils.sync(foutput);
+                    output.close();  // Get a final size measurement
+                    output = null;
+                } else {
+                    output.flush();  // So the size measurement is pseudo-reasonable
+                }
+
+                long len = temp.length();
+                if (len > max) {
+                    Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");
+                    temp.delete();
+                    temp = null;  // Pass temp = null to createEntry() to leave a tombstone
+                    break;
+                }
+            } while (read > 0);
+
+            long time = createEntry(temp, tag, flags);
+            temp = null;
+
+            final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
+            dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
+            dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
+            if (!mBooted) {
+                dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            }
+            // Call sendBroadcast after returning from this call to avoid deadlock. In particular
+            // the caller may be holding the WindowManagerService lock but sendBroadcast requires a
+            // lock in ActivityManagerService. ActivityManagerService has been caught holding that
+            // very lock while waiting for the WindowManagerService lock.
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't write: " + tag, e);
+        } finally {
+            try { if (output != null) output.close(); } catch (IOException e) {}
+            entry.close();
+            if (temp != null) temp.delete();
+        }
+    }
+
+    public boolean isTagEnabled(String tag) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return !"disabled".equals(Settings.Global.getString(
+                    mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag));
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public synchronized DropBoxManager.Entry getNextEntry(String tag, long millis) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.READ_LOGS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("READ_LOGS permission required");
+        }
+
+        try {
+            init();
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't init", e);
+            return null;
+        }
+
+        FileList list = tag == null ? mAllFiles : mFilesByTag.get(tag);
+        if (list == null) return null;
+
+        for (EntryFile entry : list.contents.tailSet(new EntryFile(millis + 1))) {
+            if (entry.tag == null) continue;
+            if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) {
+                return new DropBoxManager.Entry(entry.tag, entry.timestampMillis);
+            }
+            try {
+                return new DropBoxManager.Entry(
+                        entry.tag, entry.timestampMillis, entry.file, entry.flags);
+            } catch (IOException e) {
+                Slog.e(TAG, "Can't read: " + entry.file, e);
+                // Continue to next file
+            }
+        }
+
+        return null;
+    }
+
+    public synchronized 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 DropBoxManagerService");
+            return;
+        }
+
+        try {
+            init();
+        } catch (IOException e) {
+            pw.println("Can't initialize: " + e);
+            Slog.e(TAG, "Can't init", e);
+            return;
+        }
+
+        if (PROFILE_DUMP) Debug.startMethodTracing("/data/trace/dropbox.dump");
+
+        StringBuilder out = new StringBuilder();
+        boolean doPrint = false, doFile = false;
+        ArrayList<String> searchArgs = new ArrayList<String>();
+        for (int i = 0; args != null && i < args.length; i++) {
+            if (args[i].equals("-p") || args[i].equals("--print")) {
+                doPrint = true;
+            } else if (args[i].equals("-f") || args[i].equals("--file")) {
+                doFile = true;
+            } else if (args[i].startsWith("-")) {
+                out.append("Unknown argument: ").append(args[i]).append("\n");
+            } else {
+                searchArgs.add(args[i]);
+            }
+        }
+
+        out.append("Drop box contents: ").append(mAllFiles.contents.size()).append(" entries\n");
+
+        if (!searchArgs.isEmpty()) {
+            out.append("Searching for:");
+            for (String a : searchArgs) out.append(" ").append(a);
+            out.append("\n");
+        }
+
+        int numFound = 0, numArgs = searchArgs.size();
+        Time time = new Time();
+        out.append("\n");
+        for (EntryFile entry : mAllFiles.contents) {
+            time.set(entry.timestampMillis);
+            String date = time.format("%Y-%m-%d %H:%M:%S");
+            boolean match = true;
+            for (int i = 0; i < numArgs && match; i++) {
+                String arg = searchArgs.get(i);
+                match = (date.contains(arg) || arg.equals(entry.tag));
+            }
+            if (!match) continue;
+
+            numFound++;
+            if (doPrint) out.append("========================================\n");
+            out.append(date).append(" ").append(entry.tag == null ? "(no tag)" : entry.tag);
+            if (entry.file == null) {
+                out.append(" (no file)\n");
+                continue;
+            } else if ((entry.flags & DropBoxManager.IS_EMPTY) != 0) {
+                out.append(" (contents lost)\n");
+                continue;
+            } else {
+                out.append(" (");
+                if ((entry.flags & DropBoxManager.IS_GZIPPED) != 0) out.append("compressed ");
+                out.append((entry.flags & DropBoxManager.IS_TEXT) != 0 ? "text" : "data");
+                out.append(", ").append(entry.file.length()).append(" bytes)\n");
+            }
+
+            if (doFile || (doPrint && (entry.flags & DropBoxManager.IS_TEXT) == 0)) {
+                if (!doPrint) out.append("    ");
+                out.append(entry.file.getPath()).append("\n");
+            }
+
+            if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) {
+                DropBoxManager.Entry dbe = null;
+                InputStreamReader isr = null;
+                try {
+                    dbe = new DropBoxManager.Entry(
+                             entry.tag, entry.timestampMillis, entry.file, entry.flags);
+
+                    if (doPrint) {
+                        isr = new InputStreamReader(dbe.getInputStream());
+                        char[] buf = new char[4096];
+                        boolean newline = false;
+                        for (;;) {
+                            int n = isr.read(buf);
+                            if (n <= 0) break;
+                            out.append(buf, 0, n);
+                            newline = (buf[n - 1] == '\n');
+
+                            // Flush periodically when printing to avoid out-of-memory.
+                            if (out.length() > 65536) {
+                                pw.write(out.toString());
+                                out.setLength(0);
+                            }
+                        }
+                        if (!newline) out.append("\n");
+                    } else {
+                        String text = dbe.getText(70);
+                        boolean truncated = (text.length() == 70);
+                        out.append("    ").append(text.trim().replace('\n', '/'));
+                        if (truncated) out.append(" ...");
+                        out.append("\n");
+                    }
+                } catch (IOException e) {
+                    out.append("*** ").append(e.toString()).append("\n");
+                    Slog.e(TAG, "Can't read: " + entry.file, e);
+                } finally {
+                    if (dbe != null) dbe.close();
+                    if (isr != null) {
+                        try {
+                            isr.close();
+                        } catch (IOException unused) {
+                        }
+                    }
+                }
+            }
+
+            if (doPrint) out.append("\n");
+        }
+
+        if (numFound == 0) out.append("(No entries found.)\n");
+
+        if (args == null || args.length == 0) {
+            if (!doPrint) out.append("\n");
+            out.append("Usage: dumpsys dropbox [--print|--file] [YYYY-mm-dd] [HH:MM:SS] [tag]\n");
+        }
+
+        pw.write(out.toString());
+        if (PROFILE_DUMP) Debug.stopMethodTracing();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** Chronologically sorted list of {@link #EntryFile} */
+    private static final class FileList implements Comparable<FileList> {
+        public int blocks = 0;
+        public final TreeSet<EntryFile> contents = new TreeSet<EntryFile>();
+
+        /** Sorts bigger FileList instances before smaller ones. */
+        public final int compareTo(FileList o) {
+            if (blocks != o.blocks) return o.blocks - blocks;
+            if (this == o) return 0;
+            if (hashCode() < o.hashCode()) return -1;
+            if (hashCode() > o.hashCode()) return 1;
+            return 0;
+        }
+    }
+
+    /** Metadata describing an on-disk log file. */
+    private static final class EntryFile implements Comparable<EntryFile> {
+        public final String tag;
+        public final long timestampMillis;
+        public final int flags;
+        public final File file;
+        public final int blocks;
+
+        /** Sorts earlier EntryFile instances before later ones. */
+        public final int compareTo(EntryFile o) {
+            if (timestampMillis < o.timestampMillis) return -1;
+            if (timestampMillis > o.timestampMillis) return 1;
+            if (file != null && o.file != null) return file.compareTo(o.file);
+            if (o.file != null) return -1;
+            if (file != null) return 1;
+            if (this == o) return 0;
+            if (hashCode() < o.hashCode()) return -1;
+            if (hashCode() > o.hashCode()) return 1;
+            return 0;
+        }
+
+        /**
+         * Moves an existing temporary file to a new log filename.
+         * @param temp file to rename
+         * @param dir to store file in
+         * @param tag to use for new log file name
+         * @param timestampMillis of log entry
+         * @param flags for the entry data
+         * @param blockSize to use for space accounting
+         * @throws IOException if the file can't be moved
+         */
+        public EntryFile(File temp, File dir, String tag,long timestampMillis,
+                         int flags, int blockSize) throws IOException {
+            if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();
+
+            this.tag = tag;
+            this.timestampMillis = timestampMillis;
+            this.flags = flags;
+            this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis +
+                    ((flags & DropBoxManager.IS_TEXT) != 0 ? ".txt" : ".dat") +
+                    ((flags & DropBoxManager.IS_GZIPPED) != 0 ? ".gz" : ""));
+
+            if (!temp.renameTo(this.file)) {
+                throw new IOException("Can't rename " + temp + " to " + this.file);
+            }
+            this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+        }
+
+        /**
+         * Creates a zero-length tombstone for a file whose contents were lost.
+         * @param dir to store file in
+         * @param tag to use for new log file name
+         * @param timestampMillis of log entry
+         * @throws IOException if the file can't be created.
+         */
+        public EntryFile(File dir, String tag, long timestampMillis) throws IOException {
+            this.tag = tag;
+            this.timestampMillis = timestampMillis;
+            this.flags = DropBoxManager.IS_EMPTY;
+            this.file = new File(dir, Uri.encode(tag) + "@" + timestampMillis + ".lost");
+            this.blocks = 0;
+            new FileOutputStream(this.file).close();
+        }
+
+        /**
+         * Extracts metadata from an existing on-disk log filename.
+         * @param file name of existing log file
+         * @param blockSize to use for space accounting
+         */
+        public EntryFile(File file, int blockSize) {
+            this.file = file;
+            this.blocks = (int) ((this.file.length() + blockSize - 1) / blockSize);
+
+            String name = file.getName();
+            int at = name.lastIndexOf('@');
+            if (at < 0) {
+                this.tag = null;
+                this.timestampMillis = 0;
+                this.flags = DropBoxManager.IS_EMPTY;
+                return;
+            }
+
+            int flags = 0;
+            this.tag = Uri.decode(name.substring(0, at));
+            if (name.endsWith(".gz")) {
+                flags |= DropBoxManager.IS_GZIPPED;
+                name = name.substring(0, name.length() - 3);
+            }
+            if (name.endsWith(".lost")) {
+                flags |= DropBoxManager.IS_EMPTY;
+                name = name.substring(at + 1, name.length() - 5);
+            } else if (name.endsWith(".txt")) {
+                flags |= DropBoxManager.IS_TEXT;
+                name = name.substring(at + 1, name.length() - 4);
+            } else if (name.endsWith(".dat")) {
+                name = name.substring(at + 1, name.length() - 4);
+            } else {
+                this.flags = DropBoxManager.IS_EMPTY;
+                this.timestampMillis = 0;
+                return;
+            }
+            this.flags = flags;
+
+            long millis;
+            try { millis = Long.valueOf(name); } catch (NumberFormatException e) { millis = 0; }
+            this.timestampMillis = millis;
+        }
+
+        /**
+         * Creates a EntryFile object with only a timestamp for comparison purposes.
+         * @param timestampMillis to compare with.
+         */
+        public EntryFile(long millis) {
+            this.tag = null;
+            this.timestampMillis = millis;
+            this.flags = DropBoxManager.IS_EMPTY;
+            this.file = null;
+            this.blocks = 0;
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+
+    /** If never run before, scans disk contents to build in-memory tracking data. */
+    private synchronized void init() throws IOException {
+        if (mStatFs == null) {
+            if (!mDropBoxDir.isDirectory() && !mDropBoxDir.mkdirs()) {
+                throw new IOException("Can't mkdir: " + mDropBoxDir);
+            }
+            try {
+                mStatFs = new StatFs(mDropBoxDir.getPath());
+                mBlockSize = mStatFs.getBlockSize();
+            } catch (IllegalArgumentException e) {  // StatFs throws this on error
+                throw new IOException("Can't statfs: " + mDropBoxDir);
+            }
+        }
+
+        if (mAllFiles == null) {
+            File[] files = mDropBoxDir.listFiles();
+            if (files == null) throw new IOException("Can't list files: " + mDropBoxDir);
+
+            mAllFiles = new FileList();
+            mFilesByTag = new HashMap<String, FileList>();
+
+            // Scan pre-existing files.
+            for (File file : files) {
+                if (file.getName().endsWith(".tmp")) {
+                    Slog.i(TAG, "Cleaning temp file: " + file);
+                    file.delete();
+                    continue;
+                }
+
+                EntryFile entry = new EntryFile(file, mBlockSize);
+                if (entry.tag == null) {
+                    Slog.w(TAG, "Unrecognized file: " + file);
+                    continue;
+                } else if (entry.timestampMillis == 0) {
+                    Slog.w(TAG, "Invalid filename: " + file);
+                    file.delete();
+                    continue;
+                }
+
+                enrollEntry(entry);
+            }
+        }
+    }
+
+    /** Adds a disk log file to in-memory tracking for accounting and enumeration. */
+    private synchronized void enrollEntry(EntryFile entry) {
+        mAllFiles.contents.add(entry);
+        mAllFiles.blocks += entry.blocks;
+
+        // mFilesByTag is used for trimming, so don't list empty files.
+        // (Zero-length/lost files are trimmed by date from mAllFiles.)
+
+        if (entry.tag != null && entry.file != null && entry.blocks > 0) {
+            FileList tagFiles = mFilesByTag.get(entry.tag);
+            if (tagFiles == null) {
+                tagFiles = new FileList();
+                mFilesByTag.put(entry.tag, tagFiles);
+            }
+            tagFiles.contents.add(entry);
+            tagFiles.blocks += entry.blocks;
+        }
+    }
+
+    /** Moves a temporary file to a final log filename and enrolls it. */
+    private synchronized long createEntry(File temp, String tag, int flags) throws IOException {
+        long t = System.currentTimeMillis();
+
+        // Require each entry to have a unique timestamp; if there are entries
+        // >10sec in the future (due to clock skew), drag them back to avoid
+        // keeping them around forever.
+
+        SortedSet<EntryFile> tail = mAllFiles.contents.tailSet(new EntryFile(t + 10000));
+        EntryFile[] future = null;
+        if (!tail.isEmpty()) {
+            future = tail.toArray(new EntryFile[tail.size()]);
+            tail.clear();  // Remove from mAllFiles
+        }
+
+        if (!mAllFiles.contents.isEmpty()) {
+            t = Math.max(t, mAllFiles.contents.last().timestampMillis + 1);
+        }
+
+        if (future != null) {
+            for (EntryFile late : future) {
+                mAllFiles.blocks -= late.blocks;
+                FileList tagFiles = mFilesByTag.get(late.tag);
+                if (tagFiles != null && tagFiles.contents.remove(late)) {
+                    tagFiles.blocks -= late.blocks;
+                }
+                if ((late.flags & DropBoxManager.IS_EMPTY) == 0) {
+                    enrollEntry(new EntryFile(
+                            late.file, mDropBoxDir, late.tag, t++, late.flags, mBlockSize));
+                } else {
+                    enrollEntry(new EntryFile(mDropBoxDir, late.tag, t++));
+                }
+            }
+        }
+
+        if (temp == null) {
+            enrollEntry(new EntryFile(mDropBoxDir, tag, t));
+        } else {
+            enrollEntry(new EntryFile(temp, mDropBoxDir, tag, t, flags, mBlockSize));
+        }
+        return t;
+    }
+
+    /**
+     * Trims the files on disk to make sure they aren't using too much space.
+     * @return the overall quota for storage (in bytes)
+     */
+    private synchronized long trimToFit() {
+        // Expunge aged items (including tombstones marking deleted data).
+
+        int ageSeconds = Settings.Global.getInt(mContentResolver,
+                Settings.Global.DROPBOX_AGE_SECONDS, DEFAULT_AGE_SECONDS);
+        int maxFiles = Settings.Global.getInt(mContentResolver,
+                Settings.Global.DROPBOX_MAX_FILES, DEFAULT_MAX_FILES);
+        long cutoffMillis = System.currentTimeMillis() - ageSeconds * 1000;
+        while (!mAllFiles.contents.isEmpty()) {
+            EntryFile entry = mAllFiles.contents.first();
+            if (entry.timestampMillis > cutoffMillis && mAllFiles.contents.size() < maxFiles) break;
+
+            FileList tag = mFilesByTag.get(entry.tag);
+            if (tag != null && tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+            if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+            if (entry.file != null) entry.file.delete();
+        }
+
+        // Compute overall quota (a fraction of available free space) in blocks.
+        // The quota changes dynamically based on the amount of free space;
+        // that way when lots of data is available we can use it, but we'll get
+        // out of the way if storage starts getting tight.
+
+        long uptimeMillis = SystemClock.uptimeMillis();
+        if (uptimeMillis > mCachedQuotaUptimeMillis + QUOTA_RESCAN_MILLIS) {
+            int quotaPercent = Settings.Global.getInt(mContentResolver,
+                    Settings.Global.DROPBOX_QUOTA_PERCENT, DEFAULT_QUOTA_PERCENT);
+            int reservePercent = Settings.Global.getInt(mContentResolver,
+                    Settings.Global.DROPBOX_RESERVE_PERCENT, DEFAULT_RESERVE_PERCENT);
+            int quotaKb = Settings.Global.getInt(mContentResolver,
+                    Settings.Global.DROPBOX_QUOTA_KB, DEFAULT_QUOTA_KB);
+
+            mStatFs.restat(mDropBoxDir.getPath());
+            int available = mStatFs.getAvailableBlocks();
+            int nonreserved = available - mStatFs.getBlockCount() * reservePercent / 100;
+            int maximum = quotaKb * 1024 / mBlockSize;
+            mCachedQuotaBlocks = Math.min(maximum, Math.max(0, nonreserved * quotaPercent / 100));
+            mCachedQuotaUptimeMillis = uptimeMillis;
+        }
+
+        // If we're using too much space, delete old items to make room.
+        //
+        // We trim each tag independently (this is why we keep per-tag lists).
+        // Space is "fairly" shared between tags -- they are all squeezed
+        // equally until enough space is reclaimed.
+        //
+        // A single circular buffer (a la logcat) would be simpler, but this
+        // way we can handle fat/bursty data (like 1MB+ bugreports, 300KB+
+        // kernel crash dumps, and 100KB+ ANR reports) without swamping small,
+        // well-behaved data streams (event statistics, profile data, etc).
+        //
+        // Deleted files are replaced with zero-length tombstones to mark what
+        // was lost.  Tombstones are expunged by age (see above).
+
+        if (mAllFiles.blocks > mCachedQuotaBlocks) {
+            // Find a fair share amount of space to limit each tag
+            int unsqueezed = mAllFiles.blocks, squeezed = 0;
+            TreeSet<FileList> tags = new TreeSet<FileList>(mFilesByTag.values());
+            for (FileList tag : tags) {
+                if (squeezed > 0 && tag.blocks <= (mCachedQuotaBlocks - unsqueezed) / squeezed) {
+                    break;
+                }
+                unsqueezed -= tag.blocks;
+                squeezed++;
+            }
+            int tagQuota = (mCachedQuotaBlocks - unsqueezed) / squeezed;
+
+            // Remove old items from each tag until it meets the per-tag quota.
+            for (FileList tag : tags) {
+                if (mAllFiles.blocks < mCachedQuotaBlocks) break;
+                while (tag.blocks > tagQuota && !tag.contents.isEmpty()) {
+                    EntryFile entry = tag.contents.first();
+                    if (tag.contents.remove(entry)) tag.blocks -= entry.blocks;
+                    if (mAllFiles.contents.remove(entry)) mAllFiles.blocks -= entry.blocks;
+
+                    try {
+                        if (entry.file != null) entry.file.delete();
+                        enrollEntry(new EntryFile(mDropBoxDir, entry.tag, entry.timestampMillis));
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Can't write tombstone file", e);
+                    }
+                }
+            }
+        }
+
+        return mCachedQuotaBlocks * mBlockSize;
+    }
+}
diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java
new file mode 100644
index 0000000..cfdbf7d
--- /dev/null
+++ b/services/core/java/com/android/server/EntropyMixer.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+/**
+ * A service designed to load and periodically save &quot;randomness&quot;
+ * for the Linux kernel RNG and to mix in data from Hardware RNG (if present)
+ * into the Linux RNG.
+ *
+ * <p>When a Linux system starts up, the entropy pool associated with
+ * {@code /dev/random} may be in a fairly predictable state.  Applications which
+ * depend strongly on randomness may find {@code /dev/random} or
+ * {@code /dev/urandom} returning predictable data.  In order to counteract
+ * this effect, it's helpful to carry the entropy pool information across
+ * shutdowns and startups.
+ *
+ * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW
+ * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever
+ * EntropyMixer periodically runs to save a block of output from Linux RNG on
+ * disk. This mixing is done in a way that does not increase the Linux RNG's
+ * entropy estimate is not increased. This is to avoid having to trust/verify
+ * the quality and authenticity of the &quot;randomness&quot; of the HW RNG.
+ *
+ * <p>This class was modeled after the script in
+ * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">man
+ * 4 random</a>.
+ */
+public class EntropyMixer extends Binder {
+    private static final String TAG = "EntropyMixer";
+    private static final int ENTROPY_WHAT = 1;
+    private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000;  // 3 hrs
+    private static final long START_TIME = System.currentTimeMillis();
+    private static final long START_NANOTIME = System.nanoTime();
+
+    private final String randomDevice;
+    private final String hwRandomDevice;
+    private final String entropyFile;
+
+    /**
+     * Handler that periodically updates the entropy on disk.
+     */
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what != ENTROPY_WHAT) {
+                Slog.e(TAG, "Will not process invalid message");
+                return;
+            }
+            addHwRandomEntropy();
+            writeEntropy();
+            scheduleEntropyWriter();
+        }
+    };
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            writeEntropy();
+        }
+    };
+
+    public EntropyMixer(Context context) {
+        this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random");
+    }
+
+    /** Test only interface, not for public use */
+    public EntropyMixer(
+            Context context,
+            String entropyFile,
+            String randomDevice,
+            String hwRandomDevice) {
+        if (randomDevice == null) { throw new NullPointerException("randomDevice"); }
+        if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); }
+        if (entropyFile == null) { throw new NullPointerException("entropyFile"); }
+
+        this.randomDevice = randomDevice;
+        this.hwRandomDevice = hwRandomDevice;
+        this.entropyFile = entropyFile;
+        loadInitialEntropy();
+        addDeviceSpecificEntropy();
+        addHwRandomEntropy();
+        writeEntropy();
+        scheduleEntropyWriter();
+        IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+        broadcastFilter.addAction(Intent.ACTION_POWER_CONNECTED);
+        broadcastFilter.addAction(Intent.ACTION_REBOOT);
+        context.registerReceiver(mBroadcastReceiver, broadcastFilter);
+    }
+
+    private void scheduleEntropyWriter() {
+        mHandler.removeMessages(ENTROPY_WHAT);
+        mHandler.sendEmptyMessageDelayed(ENTROPY_WHAT, ENTROPY_WRITE_PERIOD);
+    }
+
+    private void loadInitialEntropy() {
+        try {
+            RandomBlock.fromFile(entropyFile).toFile(randomDevice, false);
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, "No existing entropy file -- first boot?");
+        } catch (IOException e) {
+            Slog.w(TAG, "Failure loading existing entropy file", e);
+        }
+    }
+
+    private void writeEntropy() {
+        try {
+            Slog.i(TAG, "Writing entropy...");
+            RandomBlock.fromFile(randomDevice).toFile(entropyFile, true);
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to write entropy", e);
+        }
+    }
+
+    /**
+     * Add additional information to the kernel entropy pool.  The
+     * information isn't necessarily "random", but that's ok.  Even
+     * sending non-random information to {@code /dev/urandom} is useful
+     * because, while it doesn't increase the "quality" of the entropy pool,
+     * it mixes more bits into the pool, which gives us a higher degree
+     * of uncertainty in the generated randomness.  Like nature, writes to
+     * the random device can only cause the quality of the entropy in the
+     * kernel to stay the same or increase.
+     *
+     * <p>For maximum effect, we try to target information which varies
+     * on a per-device basis, and is not easily observable to an
+     * attacker.
+     */
+    private void addDeviceSpecificEntropy() {
+        PrintWriter out = null;
+        try {
+            out = new PrintWriter(new FileOutputStream(randomDevice));
+            out.println("Copyright (C) 2009 The Android Open Source Project");
+            out.println("All Your Randomness Are Belong To Us");
+            out.println(START_TIME);
+            out.println(START_NANOTIME);
+            out.println(SystemProperties.get("ro.serialno"));
+            out.println(SystemProperties.get("ro.bootmode"));
+            out.println(SystemProperties.get("ro.baseband"));
+            out.println(SystemProperties.get("ro.carrier"));
+            out.println(SystemProperties.get("ro.bootloader"));
+            out.println(SystemProperties.get("ro.hardware"));
+            out.println(SystemProperties.get("ro.revision"));
+            out.println(SystemProperties.get("ro.build.fingerprint"));
+            out.println(new Object().hashCode());
+            out.println(System.currentTimeMillis());
+            out.println(System.nanoTime());
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to add device specific data to the entropy pool", e);
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    /**
+     * Mixes in the output from HW RNG (if present) into the Linux RNG.
+     */
+    private void addHwRandomEntropy() {
+        try {
+            RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false);
+            Slog.i(TAG, "Added HW RNG output to entropy pool");
+        } catch (FileNotFoundException ignored) {
+            // HW RNG not present/exposed -- ignore
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e);
+        }
+    }
+
+    private static String getSystemDir() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        systemDir.mkdirs();
+        return systemDir.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
new file mode 100644
index 0000000..399e7d1
--- /dev/null
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -0,0 +1,188 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.server
+
+# ---------------------------
+# BatteryService.java
+# ---------------------------
+2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)
+2723 battery_status (status|1|5),(health|1|5),(present|1|5),(plugged|1|5),(technology|3)
+# This is logged when battery goes from discharging to charging.
+# It lets us count the total amount of time between charges and the discharge level
+2730 battery_discharge (duration|2|3),(minLevel|1|6),(maxLevel|1|6)
+
+
+# ---------------------------
+# PowerManagerService.java
+# ---------------------------
+# This is logged when the device is being forced to sleep (typically by
+# the user pressing the power button).
+2724 power_sleep_requested (wakeLocksCleared|1|1)
+# This is logged when the screen on broadcast has completed
+2725 power_screen_broadcast_send (wakelockCount|1|1)
+# This is logged when the screen broadcast has completed
+2726 power_screen_broadcast_done (on|1|5),(broadcastDuration|2|3),(wakelockCount|1|1)
+# This is logged when the screen on broadcast has completed
+2727 power_screen_broadcast_stop (which|1|5),(wakelockCount|1|1)
+# This is logged when the screen is turned on or off.
+2728 power_screen_state (offOrOn|1|5),(becauseOfUser|1|5),(totalTouchDownTime|2|3),(touchCycles|1|1)
+# This is logged when the partial wake lock (keeping the device awake
+# regardless of whether the screen is off) is acquired or released.
+2729 power_partial_wake_state (releasedorAcquired|1|5),(tag|3)
+
+#
+# Leave IDs through 2739 for more power logs (2730 used by battery_discharge above)
+#
+
+
+# ---------------------------
+# DeviceStorageMonitorService.java
+# ---------------------------
+# The disk space free on the /data partition, in bytes
+2744 free_storage_changed (data|2|2)
+# Device low memory notification and disk space free on the /data partition, in bytes at that time
+2745 low_storage (data|2|2)
+# disk space free on the /data, /system, and /cache partitions in bytes
+2746 free_storage_left (data|2|2),(system|2|2),(cache|2|2)
+# file on cache partition was deleted
+2748 cache_file_deleted (path|3)
+
+
+# ---------------------------
+# NotificationManagerService.java
+# ---------------------------
+# when a NotificationManager.notify is called
+2750 notification_enqueue (pkg|3),(id|1|5),(tag|3),(userid|1|5),(notification|3)
+# when someone tries to cancel a notification, the notification manager sometimes
+# calls this with flags too
+2751 notification_cancel (pkg|3),(id|1|5),(tag|3),(userid|1|5),(required_flags|1),(forbidden_flags|1)
+# when someone tries to cancel all of the notifications for a particular package
+2752 notification_cancel_all (pkg|3),(userid|1|5),(required_flags|1),(forbidden_flags|1)
+
+
+# ---------------------------
+# Watchdog.java
+# ---------------------------
+2802 watchdog (Service|3)
+2803 watchdog_proc_pss (Process|3),(Pid|1|5),(Pss|1|2)
+2804 watchdog_soft_reset (Process|3),(Pid|1|5),(MaxPss|1|2),(Pss|1|2),(Skip|3)
+2805 watchdog_hard_reset (Process|3),(Pid|1|5),(MaxPss|1|2),(Pss|1|2)
+2806 watchdog_pss_stats (EmptyPss|1|2),(EmptyCount|1|1),(BackgroundPss|1|2),(BackgroundCount|1|1),(ServicePss|1|2),(ServiceCount|1|1),(VisiblePss|1|2),(VisibleCount|1|1),(ForegroundPss|1|2),(ForegroundCount|1|1),(NoPssCount|1|1)
+2807 watchdog_proc_stats (DeathsInOne|1|1),(DeathsInTwo|1|1),(DeathsInThree|1|1),(DeathsInFour|1|1),(DeathsInFive|1|1)
+2808 watchdog_scheduled_reboot (Now|2|1),(Interval|1|3),(StartTime|1|3),(Window|1|3),(Skip|3)
+2809 watchdog_meminfo (MemFree|1|2),(Buffers|1|2),(Cached|1|2),(Active|1|2),(Inactive|1|2),(AnonPages|1|2),(Mapped|1|2),(Slab|1|2),(SReclaimable|1|2),(SUnreclaim|1|2),(PageTables|1|2)
+2810 watchdog_vmstat (runtime|2|3),(pgfree|1|1),(pgactivate|1|1),(pgdeactivate|1|1),(pgfault|1|1),(pgmajfault|1|1)
+2811 watchdog_requested_reboot (NoWait|1|1),(ScheduleInterval|1|3),(RecheckInterval|1|3),(StartTime|1|3),(Window|1|3),(MinScreenOff|1|3),(MinNextAlarm|1|3)
+
+
+# ---------------------------
+# BackupManagerService.java
+# ---------------------------
+2820 backup_data_changed (Package|3)
+2821 backup_start (Transport|3)
+2822 backup_transport_failure (Package|3)
+2823 backup_agent_failure (Package|3),(Message|3)
+2824 backup_package (Package|3),(Size|1|2)
+2825 backup_success (Packages|1|1),(Time|1|3)
+2826 backup_reset (Transport|3)
+2827 backup_initialize
+2830 restore_start (Transport|3),(Source|2|5)
+2831 restore_transport_failure
+2832 restore_agent_failure (Package|3),(Message|3)
+2833 restore_package (Package|3),(Size|1|2)
+2834 restore_success (Packages|1|1),(Time|1|3)
+
+
+# ---------------------------
+# SystemServer.java
+# ---------------------------
+# SystemServer.run() starts:
+3010 boot_progress_system_run (time|2|3)
+
+
+# ---------------------------
+# PackageManagerService.java
+# ---------------------------
+# Package Manager starts:
+3060 boot_progress_pms_start (time|2|3)
+# Package Manager .apk scan starts:
+3070 boot_progress_pms_system_scan_start (time|2|3)
+# Package Manager .apk scan starts:
+3080 boot_progress_pms_data_scan_start (time|2|3)
+# Package Manager .apk scan ends:
+3090 boot_progress_pms_scan_end (time|2|3)
+# Package Manager ready:
+3100 boot_progress_pms_ready (time|2|3)
+# + check activity_launch_time for Home app
+# Value of "unknown sources" setting at app install time
+3110 unknown_sources_enabled (value|1)
+
+
+# ---------------------------
+# WindowManagerService.java
+# ---------------------------
+# Out of memory for surfaces.
+31000 wm_no_surface_memory (Window|3),(PID|1|5),(Operation|3)
+# Task created.
+31001 wm_task_created (TaskId|1|5),(StackId|1|5)
+# Task moved to top (1) or bottom (0).
+31002 wm_task_moved (TaskId|1|5),(ToTop|1),(Index|1)
+# Task removed with source explanation.
+31003 wm_task_removed (TaskId|1|5),(Reason|3)
+# Stack created.
+31004 wm_stack_created (StackId|1|5),(RelativeBoxId|1|5),(Position|1),(Weight|1|6)
+# Home stack moved to top (1) or bottom (0).
+31005 wm_home_stack_moved (ToTop|1)
+# Stack removed.
+31006 wm_stack_removed (StackId|1|5)
+
+
+# ---------------------------
+# InputMethodManagerService.java
+# ---------------------------
+# Re-connecting to input method service because we haven't received its interface
+32000 imf_force_reconnect_ime (IME|4),(Time Since Connect|2|3),(Showing|1|1)
+
+
+# ---------------------------
+# ConnectivityService.java
+# ---------------------------
+# Connectivity state changed
+50020 connectivity_state_changed (type|1),(subtype|1),(state|1)
+
+
+# ---------------------------
+# NetworkStatsService.java
+# ---------------------------
+51100 netstats_mobile_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+51101 netstats_wifi_sample (dev_rx_bytes|2|2),(dev_tx_bytes|2|2),(dev_rx_pkts|2|1),(dev_tx_pkts|2|1),(xt_rx_bytes|2|2),(xt_tx_bytes|2|2),(xt_rx_pkts|2|1),(xt_tx_pkts|2|1),(uid_rx_bytes|2|2),(uid_tx_bytes|2|2),(uid_rx_pkts|2|1),(uid_tx_pkts|2|1),(trusted_time|2|3)
+
+
+# ---------------------------
+# LockdownVpnTracker.java
+# ---------------------------
+51200 lockdown_vpn_connecting (egress_net|1)
+51201 lockdown_vpn_connected (egress_net|1)
+51202 lockdown_vpn_error (egress_net|1)
+
+# ---------------------------
+# ConfigUpdateInstallReceiver.java
+# ---------------------------
+51300 config_install_failed (dir|3)
+
+# ---------------------------
+# IntentFirewall.java
+# ---------------------------
+51400 ifw_intent_matched (Intent Type|1|5),(Component Name|3),(Caller Uid|1|5),(Caller Pkg Count|1|1),(Caller Pkgs|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+
+# ---------------------------
+# IdleMaintenanceService.java
+# ---------------------------
+2753 idle_maintenance_window_start (time|2|3), (lastUserActivity|2|3), (batteryLevel|1|6), (batteryCharging|1|5)
+2754 idle_maintenance_window_finish (time|2|3), (lastUserActivity|2|3), (batteryLevel|1|6), (batteryCharging|1|5)
+
+# ---------------------------
+# MountService.java
+# ---------------------------
+2755 fstrim_start (time|2|3)
+2756 fstrim_finish (time|2|3)
diff --git a/services/core/java/com/android/server/FgThread.java b/services/core/java/com/android/server/FgThread.java
new file mode 100644
index 0000000..3b655f2
--- /dev/null
+++ b/services/core/java/com/android/server/FgThread.java
@@ -0,0 +1,65 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Shared singleton foreground thread for the system.  This is a thread for regular
+ * foreground service operations, which shouldn't be blocked by anything running in
+ * the background.  In particular, the shared background thread could be doing
+ * relatively long-running operations like saving state to disk (in addition to
+ * simply being a background priority), which can cause operations scheduled on it
+ * to be delayed for a user-noticeable amount of time.
+ */
+public final class FgThread extends HandlerThread {
+    private static FgThread sInstance;
+    private static Handler sHandler;
+
+    private FgThread() {
+        super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new FgThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    android.os.Process.setCanSelfBackground(false);
+                }
+            });
+        }
+    }
+
+    public static FgThread get() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java b/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
new file mode 100644
index 0000000..6fbf713
--- /dev/null
+++ b/services/core/java/com/android/server/INativeDaemonConnectorCallbacks.java
@@ -0,0 +1,24 @@
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+interface INativeDaemonConnectorCallbacks {
+
+    void onDaemonConnected();
+    boolean onEvent(int code, String raw, String[] cooked);
+}
diff --git a/services/core/java/com/android/server/IdleMaintenanceService.java b/services/core/java/com/android/server/IdleMaintenanceService.java
new file mode 100644
index 0000000..b0a1aca
--- /dev/null
+++ b/services/core/java/com/android/server/IdleMaintenanceService.java
@@ -0,0 +1,327 @@
+/*
+ * 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.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * This service observes the device state and when applicable sends
+ * broadcasts at the beginning and at the end of a period during which
+ * observers can perform idle maintenance tasks. Typical use of the
+ * idle maintenance is to perform somehow expensive tasks that can be
+ * postponed to a moment when they will not degrade user experience.
+ *
+ * The current implementation is very simple. The start of a maintenance
+ * window is announced if: the screen is off or showing a dream AND the
+ * battery level is more than twenty percent AND at least one hour passed
+ * activity).
+ *
+ * The end of a maintenance window is announced only if: a start was
+ * announced AND the screen turned on or a dream was stopped.
+ */
+public class IdleMaintenanceService extends BroadcastReceiver {
+
+    private static final boolean DEBUG = false;
+
+    private static final String LOG_TAG = IdleMaintenanceService.class.getSimpleName();
+
+    private static final int LAST_USER_ACTIVITY_TIME_INVALID = -1;
+
+    private static final long MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS = 24 * 60 * 60 * 1000; // 1 day
+
+    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING = 30; // percent
+
+    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING = 80; // percent
+
+    private static final int MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING = 20; // percent
+
+    private static final long MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START = 71 * 60 * 1000; // 71 min
+
+    private static final long MAX_IDLE_MAINTENANCE_DURATION = 71 * 60 * 1000; // 71 min
+
+    private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE =
+        "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE";
+
+    private static final String ACTION_FORCE_IDLE_MAINTENANCE =
+        "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE";
+
+    private static final Intent sIdleMaintenanceStartIntent;
+    static {
+        sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START);
+        sIdleMaintenanceStartIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+    };
+
+    private static final Intent sIdleMaintenanceEndIntent;
+    static {
+        sIdleMaintenanceEndIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_END);
+        sIdleMaintenanceEndIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+    }
+
+    private final AlarmManager mAlarmService;
+
+    private final BatteryService mBatteryService;
+
+    private final PendingIntent mUpdateIdleMaintenanceStatePendingIntent;
+
+    private final Context mContext;
+
+    private final WakeLock mWakeLock;
+
+    private final Handler mHandler;
+
+    private long mLastIdleMaintenanceStartTimeMillis;
+
+    private long mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
+
+    private boolean mIdleMaintenanceStarted;
+
+    public IdleMaintenanceService(Context context, BatteryService batteryService) {
+        mContext = context;
+        mBatteryService = batteryService;
+
+        mAlarmService = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+
+        mHandler = new Handler(mContext.getMainLooper());
+
+        Intent intent = new Intent(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                intent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+        register(mHandler);
+    }
+
+    public void register(Handler handler) {
+        IntentFilter intentFilter = new IntentFilter();
+
+        // Alarm actions.
+        intentFilter.addAction(ACTION_UPDATE_IDLE_MAINTENANCE_STATE);
+
+        // Battery actions.
+        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+
+        // Screen actions.
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+
+        // Dream actions.
+        intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
+        intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED);
+
+        mContext.registerReceiverAsUser(this, UserHandle.ALL,
+                intentFilter, null, mHandler);
+
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE);
+        mContext.registerReceiverAsUser(this, UserHandle.ALL,
+                intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler);
+    }
+
+    private void scheduleUpdateIdleMaintenanceState(long delayMillis) {
+        final long triggetRealTimeMillis = SystemClock.elapsedRealtime() + delayMillis;
+        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggetRealTimeMillis,
+                mUpdateIdleMaintenanceStatePendingIntent);
+    }
+
+    private void unscheduleUpdateIdleMaintenanceState() {
+        mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent);
+    }
+
+    private void updateIdleMaintenanceState(boolean noisy) {
+        if (mIdleMaintenanceStarted) {
+            // Idle maintenance can be interrupted by user activity, or duration
+            // time out, or low battery.
+            if (!lastUserActivityPermitsIdleMaintenanceRunning()
+                    || !batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+                unscheduleUpdateIdleMaintenanceState();
+                mIdleMaintenanceStarted = false;
+                EventLogTags.writeIdleMaintenanceWindowFinish(SystemClock.elapsedRealtime(),
+                        mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+                        isBatteryCharging() ? 1 : 0);
+                sendIdleMaintenanceEndIntent();
+                // We stopped since we don't have enough battery or timed out but the
+                // user is not using the device, so we should be able to run maintenance
+                // in the next maintenance window since the battery may be charged
+                // without interaction and the min interval between maintenances passed.
+                if (!batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning()) {
+                    scheduleUpdateIdleMaintenanceState(
+                            getNextIdleMaintenanceIntervalStartFromNow());
+                }
+            }
+        } else if (deviceStatePermitsIdleMaintenanceStart(noisy)
+                && lastUserActivityPermitsIdleMaintenanceStart(noisy)
+                && lastRunPermitsIdleMaintenanceStart(noisy)) {
+            // Now that we started idle maintenance, we should schedule another
+            // update for the moment when the idle maintenance times out.
+            scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION);
+            mIdleMaintenanceStarted = true;
+            EventLogTags.writeIdleMaintenanceWindowStart(SystemClock.elapsedRealtime(),
+                    mLastUserActivityElapsedTimeMillis, mBatteryService.getBatteryLevel(),
+                    isBatteryCharging() ? 1 : 0);
+            mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime();
+            sendIdleMaintenanceStartIntent();
+        } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) {
+             if (lastRunPermitsIdleMaintenanceStart(noisy)) {
+                // The user does not use the device and we did not run maintenance in more
+                // than the min interval between runs, so schedule an update - maybe the
+                // battery will be charged latter.
+                scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+             } else {
+                 // The user does not use the device but we have run maintenance in the min
+                 // interval between runs, so schedule an update after the min interval ends.
+                 scheduleUpdateIdleMaintenanceState(
+                         getNextIdleMaintenanceIntervalStartFromNow());
+             }
+        }
+    }
+
+    private long getNextIdleMaintenanceIntervalStartFromNow() {
+        return mLastIdleMaintenanceStartTimeMillis + MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS
+                - SystemClock.elapsedRealtime();
+    }
+
+    private void sendIdleMaintenanceStartIntent() {
+        mWakeLock.acquire();
+        try {
+            ActivityManagerNative.getDefault().performIdleMaintenance();
+        } catch (RemoteException e) {
+        }
+        mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL,
+                null, this, mHandler, Activity.RESULT_OK, null, null);
+    }
+
+    private void sendIdleMaintenanceEndIntent() {
+        mWakeLock.acquire();
+        mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceEndIntent, UserHandle.ALL,
+                null, this, mHandler, Activity.RESULT_OK, null, null);
+    }
+
+    private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) {
+        final int minBatteryLevel = isBatteryCharging()
+                ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING
+                : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING;
+        boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
+                && mBatteryService.getBatteryLevel() > minBatteryLevel);
+        if (!allowed && noisy) {
+            Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power");
+        }
+        return allowed;
+    }
+
+    private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) {
+        // The last time the user poked the device is above the threshold.
+        boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID
+                && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis
+                    > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+        if (!allowed && noisy) {
+            Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity");
+        }
+        return allowed;
+    }
+
+    private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) {
+        // Enough time passed since the last maintenance run.
+        boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis
+                > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
+        if (!allowed && noisy) {
+            Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last");
+        }
+        return allowed;
+    }
+
+    private boolean lastUserActivityPermitsIdleMaintenanceRunning() {
+        // The user is not using the device.
+        return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID);
+    }
+
+    private boolean batteryLevelAndMaintenanceTimeoutPermitsIdleMaintenanceRunning() {
+        // Battery not too low and the maintenance duration did not timeout.
+        return (mBatteryService.getBatteryLevel() > MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_RUNNING
+                && mLastIdleMaintenanceStartTimeMillis + MAX_IDLE_MAINTENANCE_DURATION
+                        > SystemClock.elapsedRealtime());
+    }
+
+    private boolean isBatteryCharging() {
+        return mBatteryService.getPlugType() > 0
+                && mBatteryService.getInvalidCharger() == 0;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DEBUG) {
+            Log.i(LOG_TAG, intent.getAction());
+        }
+        String action = intent.getAction();
+        if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+            // We care about battery only if maintenance is in progress so we can
+            // stop it if battery is too low. Note that here we assume that the
+            // maintenance clients are properly holding a wake lock. We will
+            // refactor the maintenance to use services instead of intents for the
+            // next release. The only client for this for now is internal an holds
+            // a wake lock correctly.
+            if (mIdleMaintenanceStarted) {
+                updateIdleMaintenanceState(false);
+            }
+        } else if (Intent.ACTION_SCREEN_ON.equals(action)
+                || Intent.ACTION_DREAMING_STOPPED.equals(action)) {
+            mLastUserActivityElapsedTimeMillis = LAST_USER_ACTIVITY_TIME_INVALID;
+            // Unschedule any future updates since we already know that maintenance
+            // cannot be performed since the user is back.
+            unscheduleUpdateIdleMaintenanceState();
+            // If the screen went on/stopped dreaming, we know the user is using the
+            // device which means that idle maintenance should be stopped if running.
+            updateIdleMaintenanceState(false);
+        } else if (Intent.ACTION_SCREEN_OFF.equals(action)
+                || Intent.ACTION_DREAMING_STARTED.equals(action)) {
+            mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime();
+            // If screen went off/started dreaming, we may be able to start idle maintenance
+            // after the minimal user inactivity elapses. We schedule an alarm for when
+            // this timeout elapses since the device may go to sleep by then.
+            scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START);
+        } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) {
+            updateIdleMaintenanceState(false);
+        } else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) {
+            long now = SystemClock.elapsedRealtime() - 1;
+            mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START;
+            mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS;
+            updateIdleMaintenanceState(true);
+        } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)
+                || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) {
+            // We were holding a wake lock while broadcasting the idle maintenance
+            // intents but now that we finished the broadcast release the wake lock.
+            mWakeLock.release();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
new file mode 100644
index 0000000..26424a5
--- /dev/null
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -0,0 +1,3574 @@
+/*
+ *
+ * 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 com.android.internal.content.PackageMonitor;
+import com.android.internal.inputmethod.InputMethodUtils;
+import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethod;
+import com.android.internal.view.IInputSessionCallback;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.IInputMethodSession;
+import com.android.internal.view.InputBindResult;
+import com.android.server.statusbar.StatusBarManagerService;
+import com.android.server.wm.WindowManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.AlertDialog;
+import android.app.IUserSwitchObserver;
+import android.app.KeyguardManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.inputmethodservice.InputMethodService;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.IRemoteCallback;
+import android.os.Message;
+import android.os.Process;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.util.AtomicFile;
+import android.util.EventLog;
+import android.util.LruCache;
+import android.util.Pair;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.IWindowManager;
+import android.view.InputChannel;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputBinding;
+import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.RadioButton;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.TreeMap;
+
+/**
+ * This class provides a system service that manages input methods.
+ */
+public class InputMethodManagerService extends IInputMethodManager.Stub
+        implements ServiceConnection, Handler.Callback {
+    static final boolean DEBUG = false;
+    static final String TAG = "InputMethodManagerService";
+
+    static final int MSG_SHOW_IM_PICKER = 1;
+    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
+    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
+    static final int MSG_SHOW_IM_CONFIG = 4;
+
+    static final int MSG_UNBIND_INPUT = 1000;
+    static final int MSG_BIND_INPUT = 1010;
+    static final int MSG_SHOW_SOFT_INPUT = 1020;
+    static final int MSG_HIDE_SOFT_INPUT = 1030;
+    static final int MSG_ATTACH_TOKEN = 1040;
+    static final int MSG_CREATE_SESSION = 1050;
+
+    static final int MSG_START_INPUT = 2000;
+    static final int MSG_RESTART_INPUT = 2010;
+
+    static final int MSG_UNBIND_METHOD = 3000;
+    static final int MSG_BIND_METHOD = 3010;
+    static final int MSG_SET_ACTIVE = 3020;
+
+    static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
+
+    static final long TIME_TO_RECONNECT = 10*1000;
+
+    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
+
+    private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+    private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
+
+
+    final Context mContext;
+    final Resources mRes;
+    final Handler mHandler;
+    final InputMethodSettings mSettings;
+    final SettingsObserver mSettingsObserver;
+    final IWindowManager mIWindowManager;
+    final HandlerCaller mCaller;
+    final boolean mHasFeature;
+    private InputMethodFileManager mFileManager;
+    private InputMethodAndSubtypeListManager mImListManager;
+    private final HardKeyboardListener mHardKeyboardListener;
+    private final WindowManagerService mWindowManagerService;
+
+    final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1);
+
+    // All known input methods.  mMethodMap also serves as the global
+    // lock for this class.
+    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
+    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
+    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
+            new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
+
+    // Used to bring IME service up to visible adjustment while it is being shown.
+    final ServiceConnection mVisibleConnection = new ServiceConnection() {
+        @Override public void onServiceConnected(ComponentName name, IBinder service) {
+        }
+
+        @Override public void onServiceDisconnected(ComponentName name) {
+        }
+    };
+    boolean mVisibleBound = false;
+
+    // Ongoing notification
+    private NotificationManager mNotificationManager;
+    private KeyguardManager mKeyguardManager;
+    private StatusBarManagerService mStatusBar;
+    private Notification mImeSwitcherNotification;
+    private PendingIntent mImeSwitchPendingIntent;
+    private boolean mShowOngoingImeSwitcherForPhones;
+    private boolean mNotificationShown;
+    private final boolean mImeSelectedOnBoot;
+
+    class SessionState {
+        final ClientState client;
+        final IInputMethod method;
+
+        IInputMethodSession session;
+        InputChannel channel;
+
+        @Override
+        public String toString() {
+            return "SessionState{uid " + client.uid + " pid " + client.pid
+                    + " method " + Integer.toHexString(
+                            System.identityHashCode(method))
+                    + " session " + Integer.toHexString(
+                            System.identityHashCode(session))
+                    + " channel " + channel
+                    + "}";
+        }
+
+        SessionState(ClientState _client, IInputMethod _method,
+                IInputMethodSession _session, InputChannel _channel) {
+            client = _client;
+            method = _method;
+            session = _session;
+            channel = _channel;
+        }
+    }
+
+    static final class ClientState {
+        final IInputMethodClient client;
+        final IInputContext inputContext;
+        final int uid;
+        final int pid;
+        final InputBinding binding;
+
+        boolean sessionRequested;
+        SessionState curSession;
+
+        @Override
+        public String toString() {
+            return "ClientState{" + Integer.toHexString(
+                    System.identityHashCode(this)) + " uid " + uid
+                    + " pid " + pid + "}";
+        }
+
+        ClientState(IInputMethodClient _client, IInputContext _inputContext,
+                int _uid, int _pid) {
+            client = _client;
+            inputContext = _inputContext;
+            uid = _uid;
+            pid = _pid;
+            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
+        }
+    }
+
+    final HashMap<IBinder, ClientState> mClients
+            = new HashMap<IBinder, ClientState>();
+
+    /**
+     * Set once the system is ready to run third party code.
+     */
+    boolean mSystemReady;
+
+    /**
+     * Id of the currently selected input method.
+     */
+    String mCurMethodId;
+
+    /**
+     * The current binding sequence number, incremented every time there is
+     * a new bind performed.
+     */
+    int mCurSeq;
+
+    /**
+     * The client that is currently bound to an input method.
+     */
+    ClientState mCurClient;
+
+    /**
+     * The last window token that gained focus.
+     */
+    IBinder mCurFocusedWindow;
+
+    /**
+     * The input context last provided by the current client.
+     */
+    IInputContext mCurInputContext;
+
+    /**
+     * The attributes last provided by the current client.
+     */
+    EditorInfo mCurAttribute;
+
+    /**
+     * The input method ID of the input method service that we are currently
+     * connected to or in the process of connecting to.
+     */
+    String mCurId;
+
+    /**
+     * The current subtype of the current input method.
+     */
+    private InputMethodSubtype mCurrentSubtype;
+
+    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
+    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
+            mShortcutInputMethodsAndSubtypes =
+                new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
+
+    // Was the keyguard locked when this client became current?
+    private boolean mCurClientInKeyguard;
+
+    /**
+     * Set to true if our ServiceConnection is currently actively bound to
+     * a service (whether or not we have gotten its IBinder back yet).
+     */
+    boolean mHaveConnection;
+
+    /**
+     * Set if the client has asked for the input method to be shown.
+     */
+    boolean mShowRequested;
+
+    /**
+     * Set if we were explicitly told to show the input method.
+     */
+    boolean mShowExplicitlyRequested;
+
+    /**
+     * Set if we were forced to be shown.
+     */
+    boolean mShowForced;
+
+    /**
+     * Set if we last told the input method to show itself.
+     */
+    boolean mInputShown;
+
+    /**
+     * The Intent used to connect to the current input method.
+     */
+    Intent mCurIntent;
+
+    /**
+     * The token we have made for the currently active input method, to
+     * identify it in the future.
+     */
+    IBinder mCurToken;
+
+    /**
+     * If non-null, this is the input method service we are currently connected
+     * to.
+     */
+    IInputMethod mCurMethod;
+
+    /**
+     * Time that we last initiated a bind to the input method, to determine
+     * if we should try to disconnect and reconnect to it.
+     */
+    long mLastBindTime;
+
+    /**
+     * Have we called mCurMethod.bindInput()?
+     */
+    boolean mBoundToMethod;
+
+    /**
+     * Currently enabled session.  Only touched by service thread, not
+     * protected by a lock.
+     */
+    SessionState mEnabledSession;
+
+    /**
+     * True if the screen is on.  The value is true initially.
+     */
+    boolean mScreenOn = true;
+
+    int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
+    int mImeWindowVis;
+
+    private AlertDialog.Builder mDialogBuilder;
+    private AlertDialog mSwitchingDialog;
+    private View mSwitchingDialogTitleView;
+    private InputMethodInfo[] mIms;
+    private int[] mSubtypeIds;
+    private Locale mLastSystemLocale;
+    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
+    private final IPackageManager mIPackageManager;
+
+    class SettingsObserver extends ContentObserver {
+        String mLastEnabled = "";
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.ENABLED_INPUT_METHODS), false, this);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
+        }
+
+        @Override public void onChange(boolean selfChange) {
+            synchronized (mMethodMap) {
+                boolean enabledChanged = false;
+                String newEnabled = mSettings.getEnabledInputMethodsStr();
+                if (!mLastEnabled.equals(newEnabled)) {
+                    mLastEnabled = newEnabled;
+                    enabledChanged = true;
+                }
+                updateFromSettingsLocked(enabledChanged);
+            }
+        }
+    }
+
+    class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+        private void updateActive() {
+            // Inform the current client of the change in active status
+            if (mCurClient != null && mCurClient.client != null) {
+                executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                        MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
+            }
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mScreenOn = true;
+                refreshImeWindowVisibilityLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mScreenOn = false;
+                setImeWindowVisibilityStatusHiddenLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                hideInputMethodMenu();
+                // No need to updateActive
+                return;
+            } else {
+                Slog.w(TAG, "Unexpected intent " + intent);
+            }
+        }
+    }
+
+    class MyPackageMonitor extends PackageMonitor {
+        private boolean isChangingPackagesOfCurrentUser() {
+            final int userId = getChangingUserId();
+            final boolean retval = userId == mSettings.getCurrentUserId();
+            if (DEBUG) {
+                if (!retval) {
+                    Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
+                }
+            }
+            return retval;
+        }
+
+        @Override
+        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return false;
+            }
+            synchronized (mMethodMap) {
+                String curInputMethodId = mSettings.getSelectedInputMethod();
+                final int N = mMethodList.size();
+                if (curInputMethodId != null) {
+                    for (int i=0; i<N; i++) {
+                        InputMethodInfo imi = mMethodList.get(i);
+                        if (imi.getId().equals(curInputMethodId)) {
+                            for (String pkg : packages) {
+                                if (imi.getPackageName().equals(pkg)) {
+                                    if (!doit) {
+                                        return true;
+                                    }
+                                    resetSelectedInputMethodAndSubtypeLocked("");
+                                    chooseNewDefaultIMELocked();
+                                    return true;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return;
+            }
+            synchronized (mMethodMap) {
+                InputMethodInfo curIm = null;
+                String curInputMethodId = mSettings.getSelectedInputMethod();
+                final int N = mMethodList.size();
+                if (curInputMethodId != null) {
+                    for (int i=0; i<N; i++) {
+                        InputMethodInfo imi = mMethodList.get(i);
+                        final String imiId = imi.getId();
+                        if (imiId.equals(curInputMethodId)) {
+                            curIm = imi;
+                        }
+
+                        int change = isPackageDisappearing(imi.getPackageName());
+                        if (isPackageModified(imi.getPackageName())) {
+                            mFileManager.deleteAllInputMethodSubtypes(imiId);
+                        }
+                        if (change == PACKAGE_TEMPORARY_CHANGE
+                                || change == PACKAGE_PERMANENT_CHANGE) {
+                            Slog.i(TAG, "Input method uninstalled, disabling: "
+                                    + imi.getComponent());
+                            setInputMethodEnabledLocked(imi.getId(), false);
+                        }
+                    }
+                }
+
+                buildInputMethodListLocked(
+                        mMethodList, mMethodMap, false /* resetDefaultEnabledIme */);
+
+                boolean changed = false;
+
+                if (curIm != null) {
+                    int change = isPackageDisappearing(curIm.getPackageName()); 
+                    if (change == PACKAGE_TEMPORARY_CHANGE
+                            || change == PACKAGE_PERMANENT_CHANGE) {
+                        ServiceInfo si = null;
+                        try {
+                            si = mIPackageManager.getServiceInfo(
+                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
+                        } catch (RemoteException ex) {
+                        }
+                        if (si == null) {
+                            // Uh oh, current input method is no longer around!
+                            // Pick another one...
+                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
+                            setImeWindowVisibilityStatusHiddenLocked();
+                            if (!chooseNewDefaultIMELocked()) {
+                                changed = true;
+                                curIm = null;
+                                Slog.i(TAG, "Unsetting current input method");
+                                resetSelectedInputMethodAndSubtypeLocked("");
+                            }
+                        }
+                    }
+                }
+
+                if (curIm == null) {
+                    // We currently don't have a default input method... is
+                    // one now available?
+                    changed = chooseNewDefaultIMELocked();
+                }
+
+                if (changed) {
+                    updateFromSettingsLocked(false);
+                }
+            }
+        }
+    }
+
+    private static final class MethodCallback extends IInputSessionCallback.Stub {
+        private final InputMethodManagerService mParentIMMS;
+        private final IInputMethod mMethod;
+        private final InputChannel mChannel;
+
+        MethodCallback(InputMethodManagerService imms, IInputMethod method,
+                InputChannel channel) {
+            mParentIMMS = imms;
+            mMethod = method;
+            mChannel = channel;
+        }
+
+        @Override
+        public void sessionCreated(IInputMethodSession session) {
+            mParentIMMS.onSessionCreated(mMethod, session, mChannel);
+        }
+    }
+
+    private class HardKeyboardListener
+            implements WindowManagerService.OnHardKeyboardStatusChangeListener {
+        @Override
+        public void onHardKeyboardStatusChange(boolean available, boolean enabled) {
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0));
+        }
+
+        public void handleHardKeyboardStatusChange(boolean available, boolean enabled) {
+            if (DEBUG) {
+                Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = "
+                        + enabled);
+            }
+            synchronized(mMethodMap) {
+                if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
+                        && mSwitchingDialog.isShowing()) {
+                    mSwitchingDialogTitleView.findViewById(
+                            com.android.internal.R.id.hard_keyboard_section).setVisibility(
+                                    available ? View.VISIBLE : View.GONE);
+                }
+            }
+        }
+    }
+
+    public InputMethodManagerService(Context context, WindowManagerService windowManager) {
+        mIPackageManager = AppGlobals.getPackageManager();
+        mContext = context;
+        mRes = context.getResources();
+        mHandler = new Handler(this);
+        mIWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
+            @Override
+            public void executeMessage(Message msg) {
+                handleMessage(msg);
+            }
+        }, true /*asyncHandler*/);
+        mWindowManagerService = windowManager;
+        mHardKeyboardListener = new HardKeyboardListener();
+        mHasFeature = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_INPUT_METHODS);
+
+        mImeSwitcherNotification = new Notification();
+        mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
+        mImeSwitcherNotification.when = 0;
+        mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
+        mImeSwitcherNotification.tickerText = null;
+        mImeSwitcherNotification.defaults = 0; // please be quiet
+        mImeSwitcherNotification.sound = null;
+        mImeSwitcherNotification.vibrate = null;
+
+        // Tag this notification specially so SystemUI knows it's important
+        mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" };
+
+        Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
+        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+
+        mShowOngoingImeSwitcherForPhones = false;
+
+        final IntentFilter broadcastFilter = new IntentFilter();
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
+
+        mNotificationShown = false;
+        int userId = 0;
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            synchronized(mMethodMap) {
+                                switchUserLocked(newUserId);
+                            }
+                            if (reply != null) {
+                                try {
+                                    reply.sendResult(null);
+                                } catch (RemoteException e) {
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+            userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+        }
+        mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+
+        // mSettings should be created before buildInputMethodListLocked
+        mSettings = new InputMethodSettings(
+                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
+        mFileManager = new InputMethodFileManager(mMethodMap, userId);
+        mImListManager = new InputMethodAndSubtypeListManager(context, this);
+
+        // Just checking if defaultImiId is empty or not
+        final String defaultImiId = mSettings.getSelectedInputMethod();
+        if (DEBUG) {
+            Slog.d(TAG, "Initial default ime = " + defaultImiId);
+        }
+        mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
+
+        buildInputMethodListLocked(mMethodList, mMethodMap,
+                !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
+        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
+
+        if (!mImeSelectedOnBoot) {
+            Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
+            resetDefaultImeLocked(context);
+        }
+
+        mSettingsObserver = new SettingsObserver(mHandler);
+        updateFromSettingsLocked(true);
+
+        // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
+        // according to the new system locale.
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        synchronized(mMethodMap) {
+                            resetStateIfCurrentLocaleChangedLocked();
+                        }
+                    }
+                }, filter);
+    }
+
+    private void resetDefaultImeLocked(Context context) {
+        // Do not reset the default (current) IME when it is a 3rd-party IME
+        if (mCurMethodId != null
+                && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
+            return;
+        }
+
+        InputMethodInfo defIm = null;
+        for (InputMethodInfo imi : mMethodList) {
+            if (defIm == null) {
+                if (InputMethodUtils.isValidSystemDefaultIme(
+                        mSystemReady, imi, context)) {
+                    defIm = imi;
+                    Slog.i(TAG, "Selected default: " + imi.getId());
+                }
+            }
+        }
+        if (defIm == null && mMethodList.size() > 0) {
+            defIm = InputMethodUtils.getMostApplicableDefaultIME(
+                    mSettings.getEnabledInputMethodListLocked());
+            Slog.i(TAG, "No default found, using " + defIm.getId());
+        }
+        if (defIm != null) {
+            setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
+        }
+    }
+
+    private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
+            final boolean resetDefaultEnabledIme) {
+        if (!mSystemReady) {
+            // not system ready
+            return;
+        }
+        final Locale newLocale = mRes.getConfiguration().locale;
+        if (!updateOnlyWhenLocaleChanged
+                || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
+            if (!updateOnlyWhenLocaleChanged) {
+                hideCurrentInputLocked(0, null);
+                mCurMethodId = null;
+                unbindCurrentMethodLocked(true, false);
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Locale has been changed to " + newLocale);
+            }
+            // InputMethodAndSubtypeListManager should be reset when the locale is changed.
+            mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
+            buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme);
+            if (!updateOnlyWhenLocaleChanged) {
+                final String selectedImiId = mSettings.getSelectedInputMethod();
+                if (TextUtils.isEmpty(selectedImiId)) {
+                    // This is the first time of the user switch and
+                    // set the current ime to the proper one.
+                    resetDefaultImeLocked(mContext);
+                }
+            } else {
+                // If the locale is changed, needs to reset the default ime
+                resetDefaultImeLocked(mContext);
+            }
+            updateFromSettingsLocked(true);
+            mLastSystemLocale = newLocale;
+            if (!updateOnlyWhenLocaleChanged) {
+                try {
+                    startInputInnerLocked();
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    private void resetStateIfCurrentLocaleChangedLocked() {
+        resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */,
+                true /* resetDefaultImeLocked */);
+    }
+
+    private void switchUserLocked(int newUserId) {
+        mSettings.setCurrentUserId(newUserId);
+        // InputMethodFileManager should be reset when the user is changed
+        mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
+        final String defaultImiId = mSettings.getSelectedInputMethod();
+        // For secondary users, the list of enabled IMEs may not have been updated since the
+        // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
+        // not be empty even if the IME has been uninstalled by the primary user.
+        // Even in such cases, IMMS works fine because it will find the most applicable
+        // IME for that user.
+        final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
+        if (DEBUG) {
+            Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId);
+        }
+        resetAllInternalStateLocked(false  /* updateOnlyWhenLocaleChanged */,
+                initialUserSwitch /* needsToResetDefaultIme */);
+        if (initialUserSwitch) {
+            InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mContext.getPackageManager(),
+                    mSettings.getEnabledInputMethodListLocked());
+        }
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The input method manager only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Input Method Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    public void systemRunning(StatusBarManagerService statusBar) {
+        synchronized (mMethodMap) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- systemReady");
+            }
+            if (!mSystemReady) {
+                mSystemReady = true;
+                mKeyguardManager =
+                        (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+                mNotificationManager = (NotificationManager)
+                        mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+                mStatusBar = statusBar;
+                statusBar.setIconVisibility("ime", false);
+                updateImeWindowStatusLocked();
+                mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
+                        com.android.internal.R.bool.show_ongoing_ime_switcher);
+                if (mShowOngoingImeSwitcherForPhones) {
+                    mWindowManagerService.setOnHardKeyboardStatusChangeListener(
+                            mHardKeyboardListener);
+                }
+                buildInputMethodListLocked(mMethodList, mMethodMap,
+                        !mImeSelectedOnBoot /* resetDefaultEnabledIme */);
+                if (!mImeSelectedOnBoot) {
+                    Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
+                    resetStateIfCurrentLocaleChangedLocked();
+                    InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
+                            mContext.getPackageManager(),
+                            mSettings.getEnabledInputMethodListLocked());
+                }
+                mLastSystemLocale = mRes.getConfiguration().locale;
+                try {
+                    startInputInnerLocked();
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    private void setImeWindowVisibilityStatusHiddenLocked() {
+        mImeWindowVis = 0;
+        updateImeWindowStatusLocked();
+    }
+
+    private void refreshImeWindowVisibilityLocked() {
+        final Configuration conf = mRes.getConfiguration();
+        final boolean haveHardKeyboard = conf.keyboard
+                != Configuration.KEYBOARD_NOKEYS;
+        final boolean hardKeyShown = haveHardKeyboard
+                && conf.hardKeyboardHidden
+                        != Configuration.HARDKEYBOARDHIDDEN_YES;
+
+        final boolean isScreenLocked = isKeyguardLocked();
+        final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown);
+        // We assume the softkeyboard is shown when the input is active as long as the
+        // hard keyboard is not shown.
+        final boolean inputVisible = inputActive && !hardKeyShown;
+        mImeWindowVis = (inputActive ? InputMethodService.IME_ACTIVE : 0)
+                | (inputVisible ? InputMethodService.IME_VISIBLE : 0);
+        updateImeWindowStatusLocked();
+    }
+
+    private void updateImeWindowStatusLocked() {
+        setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
+    }
+
+    // ---------------------------------------------------------------------------------------
+    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
+    // 1) it comes from the system process
+    // 2) the calling process' user id is identical to the current user id IMMS thinks.
+    private boolean calledFromValidUser() {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(uid);
+        if (DEBUG) {
+            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+                    + " calling userId = " + userId + ", foreground user id = "
+                    + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+                    + InputMethodUtils.getApiCallStack());
+        }
+        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+            return true;
+        }
+
+        // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
+        // foreground user, not for the user of that process. Accordingly InputMethodManagerService
+        // must not manage background users' states in any functions.
+        // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
+        // by a token.
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                        == PackageManager.PERMISSION_GRANTED) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- Access granted because the calling process has "
+                        + "the INTERACT_ACROSS_USERS_FULL permission");
+            }
+            return true;
+        }
+        Slog.w(TAG, "--- IPC called from background users. Ignore. \n"
+                + InputMethodUtils.getStackTrace());
+        return false;
+    }
+
+    private boolean bindCurrentInputMethodService(
+            Intent service, ServiceConnection conn, int flags) {
+        if (service == null || conn == null) {
+            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
+            return false;
+        }
+        return mContext.bindServiceAsUser(service, conn, flags,
+                new UserHandle(mSettings.getCurrentUserId()));
+    }
+
+    @Override
+    public List<InputMethodInfo> getInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
+        synchronized (mMethodMap) {
+            return new ArrayList<InputMethodInfo>(mMethodList);
+        }
+    }
+
+    @Override
+    public List<InputMethodInfo> getEnabledInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
+        synchronized (mMethodMap) {
+            return mSettings.getEnabledInputMethodListLocked();
+        }
+    }
+
+    private HashMap<InputMethodInfo, List<InputMethodSubtype>>
+            getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
+        HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
+                new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
+        for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
+            enabledInputMethodAndSubtypes.put(
+                    imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true));
+        }
+        return enabledInputMethodAndSubtypes;
+    }
+
+    /**
+     * @param imiId if null, returns enabled subtypes for the current imi
+     * @return enabled subtypes of the specified imi
+     */
+    @Override
+    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
+            boolean allowsImplicitlySelectedSubtypes) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.<InputMethodSubtype>emptyList();
+        }
+        synchronized (mMethodMap) {
+            final InputMethodInfo imi;
+            if (imiId == null && mCurMethodId != null) {
+                imi = mMethodMap.get(mCurMethodId);
+            } else {
+                imi = mMethodMap.get(imiId);
+            }
+            if (imi == null) {
+                return Collections.<InputMethodSubtype>emptyList();
+            }
+            return mSettings.getEnabledInputMethodSubtypeListLocked(
+                    mContext, imi, allowsImplicitlySelectedSubtypes);
+        }
+    }
+
+    @Override
+    public void addClient(IInputMethodClient client,
+            IInputContext inputContext, int uid, int pid) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            mClients.put(client.asBinder(), new ClientState(client,
+                    inputContext, uid, pid));
+        }
+    }
+
+    @Override
+    public void removeClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            ClientState cs = mClients.remove(client.asBinder());
+            if (cs != null) {
+                clearClientSessionLocked(cs);
+            }
+        }
+    }
+
+    void executeOrSendMessage(IInterface target, Message msg) {
+         if (target.asBinder() instanceof Binder) {
+             mCaller.sendMessage(msg);
+         } else {
+             handleMessage(msg);
+             msg.recycle();
+         }
+    }
+
+    void unbindCurrentClientLocked() {
+        if (mCurClient != null) {
+            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
+                    + mCurClient.client.asBinder());
+            if (mBoundToMethod) {
+                mBoundToMethod = false;
+                if (mCurMethod != null) {
+                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
+                            MSG_UNBIND_INPUT, mCurMethod));
+                }
+            }
+
+            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                    MSG_SET_ACTIVE, 0, mCurClient));
+            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+            mCurClient.sessionRequested = false;
+            mCurClient = null;
+
+            hideInputMethodMenuLocked();
+        }
+    }
+
+    private int getImeShowFlags() {
+        int flags = 0;
+        if (mShowForced) {
+            flags |= InputMethod.SHOW_FORCED
+                    | InputMethod.SHOW_EXPLICIT;
+        } else if (mShowExplicitlyRequested) {
+            flags |= InputMethod.SHOW_EXPLICIT;
+        }
+        return flags;
+    }
+
+    private int getAppShowFlags() {
+        int flags = 0;
+        if (mShowForced) {
+            flags |= InputMethodManager.SHOW_FORCED;
+        } else if (!mShowExplicitlyRequested) {
+            flags |= InputMethodManager.SHOW_IMPLICIT;
+        }
+        return flags;
+    }
+
+    InputBindResult attachNewInputLocked(boolean initial) {
+        if (!mBoundToMethod) {
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
+                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
+            mBoundToMethod = true;
+        }
+        final SessionState session = mCurClient.curSession;
+        if (initial) {
+            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
+                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
+        } else {
+            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
+                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
+        }
+        if (mShowRequested) {
+            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
+            showCurrentInputLocked(getAppShowFlags(), null);
+        }
+        return new InputBindResult(session.session,
+                session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
+    }
+
+    InputBindResult startInputLocked(IInputMethodClient client,
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        // If no method is currently selected, do nothing.
+        if (mCurMethodId == null) {
+            return mNoBinding;
+        }
+
+        ClientState cs = mClients.get(client.asBinder());
+        if (cs == null) {
+            throw new IllegalArgumentException("unknown client "
+                    + client.asBinder());
+        }
+
+        try {
+            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
+                // Check with the window manager to make sure this client actually
+                // has a window with focus.  If not, reject.  This is thread safe
+                // because if the focus changes some time before or after, the
+                // next client receiving focus that has any interest in input will
+                // be calling through here after that change happens.
+                Slog.w(TAG, "Starting input on non-focused client " + cs.client
+                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+                return null;
+            }
+        } catch (RemoteException e) {
+        }
+
+        return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
+    }
+
+    InputBindResult startInputUncheckedLocked(ClientState cs,
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        // If no method is currently selected, do nothing.
+        if (mCurMethodId == null) {
+            return mNoBinding;
+        }
+
+        if (mCurClient != cs) {
+            // Was the keyguard locked when switching over to the new client?
+            mCurClientInKeyguard = isKeyguardLocked();
+            // If the client is changing, we need to switch over to the new
+            // one.
+            unbindCurrentClientLocked();
+            if (DEBUG) Slog.v(TAG, "switching to client: client = "
+                    + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
+
+            // If the screen is on, inform the new client it is active
+            if (mScreenOn) {
+                executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
+                        MSG_SET_ACTIVE, mScreenOn ? 1 : 0, cs));
+            }
+        }
+
+        // Bump up the sequence for this client and attach it.
+        mCurSeq++;
+        if (mCurSeq <= 0) mCurSeq = 1;
+        mCurClient = cs;
+        mCurInputContext = inputContext;
+        mCurAttribute = attribute;
+
+        // Check if the input method is changing.
+        if (mCurId != null && mCurId.equals(mCurMethodId)) {
+            if (cs.curSession != null) {
+                // Fast case: if we are already connected to the input method,
+                // then just return it.
+                return attachNewInputLocked(
+                        (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
+            }
+            if (mHaveConnection) {
+                if (mCurMethod != null) {
+                    // Return to client, and we will get back with it when
+                    // we have had a session made for it.
+                    requestClientSessionLocked(cs);
+                    return new InputBindResult(null, null, mCurId, mCurSeq);
+                } else if (SystemClock.uptimeMillis()
+                        < (mLastBindTime+TIME_TO_RECONNECT)) {
+                    // In this case we have connected to the service, but
+                    // don't yet have its interface.  If it hasn't been too
+                    // long since we did the connection, we'll return to
+                    // the client and wait to get the service interface so
+                    // we can report back.  If it has been too long, we want
+                    // to fall through so we can try a disconnect/reconnect
+                    // to see if we can get back in touch with the service.
+                    return new InputBindResult(null, null, mCurId, mCurSeq);
+                } else {
+                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
+                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
+                }
+            }
+        }
+
+        return startInputInnerLocked();
+    }
+
+    InputBindResult startInputInnerLocked() {
+        if (mCurMethodId == null) {
+            return mNoBinding;
+        }
+
+        if (!mSystemReady) {
+            // If the system is not yet ready, we shouldn't be running third
+            // party code.
+            return new InputBindResult(null, null, mCurMethodId, mCurSeq);
+        }
+
+        InputMethodInfo info = mMethodMap.get(mCurMethodId);
+        if (info == null) {
+            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+        }
+
+        unbindCurrentMethodLocked(false, true);
+
+        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
+        mCurIntent.setComponent(info.getComponent());
+        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                com.android.internal.R.string.input_method_binding_label);
+        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
+                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
+        if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
+                | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
+            mLastBindTime = SystemClock.uptimeMillis();
+            mHaveConnection = true;
+            mCurId = info.getId();
+            mCurToken = new Binder();
+            try {
+                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
+                mIWindowManager.addWindowToken(mCurToken,
+                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
+            } catch (RemoteException e) {
+            }
+            return new InputBindResult(null, null, mCurId, mCurSeq);
+        } else {
+            mCurIntent = null;
+            Slog.w(TAG, "Failure connecting to input method service: "
+                    + mCurIntent);
+        }
+        return null;
+    }
+
+    @Override
+    public InputBindResult startInput(IInputMethodClient client,
+            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mMethodMap) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return startInputLocked(client, inputContext, attribute, controlFlags);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void finishInput(IInputMethodClient client) {
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        synchronized (mMethodMap) {
+            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
+                mCurMethod = IInputMethod.Stub.asInterface(service);
+                if (mCurToken == null) {
+                    Slog.w(TAG, "Service connected without a token!");
+                    unbindCurrentMethodLocked(false, false);
+                    return;
+                }
+                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
+                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
+                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
+                if (mCurClient != null) {
+                    clearClientSessionLocked(mCurClient);
+                    requestClientSessionLocked(mCurClient);
+                }
+            }
+        }
+    }
+
+    void onSessionCreated(IInputMethod method, IInputMethodSession session,
+            InputChannel channel) {
+        synchronized (mMethodMap) {
+            if (mCurMethod != null && method != null
+                    && mCurMethod.asBinder() == method.asBinder()) {
+                if (mCurClient != null) {
+                    clearClientSessionLocked(mCurClient);
+                    mCurClient.curSession = new SessionState(mCurClient,
+                            method, session, channel);
+                    InputBindResult res = attachNewInputLocked(true);
+                    if (res.method != null) {
+                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
+                                MSG_BIND_METHOD, mCurClient.client, res));
+                    }
+                    return;
+                }
+            }
+        }
+
+        // Session abandoned.  Close its associated input channel.
+        channel.dispose();
+    }
+
+    void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
+        if (mVisibleBound) {
+            mContext.unbindService(mVisibleConnection);
+            mVisibleBound = false;
+        }
+
+        if (mHaveConnection) {
+            mContext.unbindService(this);
+            mHaveConnection = false;
+        }
+
+        if (mCurToken != null) {
+            try {
+                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
+                if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
+                    // The current IME is shown. Hence an IME switch (transition) is happening.
+                    mWindowManagerService.saveLastInputMethodWindowForTransition();
+                }
+                mIWindowManager.removeWindowToken(mCurToken);
+            } catch (RemoteException e) {
+            }
+            mCurToken = null;
+        }
+
+        mCurId = null;
+        clearCurMethodLocked();
+
+        if (reportToClient && mCurClient != null) {
+            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+        }
+    }
+
+    void requestClientSessionLocked(ClientState cs) {
+        if (!cs.sessionRequested) {
+            if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
+            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
+            cs.sessionRequested = true;
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
+                    MSG_CREATE_SESSION, mCurMethod, channels[1],
+                    new MethodCallback(this, mCurMethod, channels[0])));
+        }
+    }
+
+    void clearClientSessionLocked(ClientState cs) {
+        finishSessionLocked(cs.curSession);
+        cs.curSession = null;
+        cs.sessionRequested = false;
+    }
+
+    private void finishSessionLocked(SessionState sessionState) {
+        if (sessionState != null) {
+            if (sessionState.session != null) {
+                try {
+                    sessionState.session.finishSession();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Session failed to close due to remote exception", e);
+                    setImeWindowVisibilityStatusHiddenLocked();
+                }
+                sessionState.session = null;
+            }
+            if (sessionState.channel != null) {
+                sessionState.channel.dispose();
+                sessionState.channel = null;
+            }
+        }
+    }
+
+    void clearCurMethodLocked() {
+        if (mCurMethod != null) {
+            for (ClientState cs : mClients.values()) {
+                clearClientSessionLocked(cs);
+            }
+
+            finishSessionLocked(mEnabledSession);
+            mEnabledSession = null;
+            mCurMethod = null;
+        }
+        if (mStatusBar != null) {
+            mStatusBar.setIconVisibility("ime", false);
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        synchronized (mMethodMap) {
+            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
+                    + " mCurIntent=" + mCurIntent);
+            if (mCurMethod != null && mCurIntent != null
+                    && name.equals(mCurIntent.getComponent())) {
+                clearCurMethodLocked();
+                // We consider this to be a new bind attempt, since the system
+                // should now try to restart the service for us.
+                mLastBindTime = SystemClock.uptimeMillis();
+                mShowRequested = mInputShown;
+                mInputShown = false;
+                if (mCurClient != null) {
+                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
+                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (token == null || mCurToken != token) {
+                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
+                return;
+            }
+
+            synchronized (mMethodMap) {
+                if (iconId == 0) {
+                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
+                    if (mStatusBar != null) {
+                        mStatusBar.setIconVisibility("ime", false);
+                    }
+                } else if (packageName != null) {
+                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
+                    CharSequence contentDescription = null;
+                    try {
+                        // Use PackageManager to load label
+                        final PackageManager packageManager = mContext.getPackageManager();
+                        contentDescription = packageManager.getApplicationLabel(
+                                mIPackageManager.getApplicationInfo(packageName, 0,
+                                        mSettings.getCurrentUserId()));
+                    } catch (RemoteException e) {
+                        /* ignore */
+                    }
+                    if (mStatusBar != null) {
+                        mStatusBar.setIcon("ime", packageName, iconId, 0,
+                                contentDescription  != null
+                                        ? contentDescription.toString() : null);
+                        mStatusBar.setIconVisibility("ime", true);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private boolean needsToShowImeSwitchOngoingNotification() {
+        if (!mShowOngoingImeSwitcherForPhones) return false;
+        if (isScreenLocked()) return false;
+        synchronized (mMethodMap) {
+            List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
+            final int N = imis.size();
+            if (N > 2) return true;
+            if (N < 1) return false;
+            int nonAuxCount = 0;
+            int auxCount = 0;
+            InputMethodSubtype nonAuxSubtype = null;
+            InputMethodSubtype auxSubtype = null;
+            for(int i = 0; i < N; ++i) {
+                final InputMethodInfo imi = imis.get(i);
+                final List<InputMethodSubtype> subtypes =
+                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+                final int subtypeCount = subtypes.size();
+                if (subtypeCount == 0) {
+                    ++nonAuxCount;
+                } else {
+                    for (int j = 0; j < subtypeCount; ++j) {
+                        final InputMethodSubtype subtype = subtypes.get(j);
+                        if (!subtype.isAuxiliary()) {
+                            ++nonAuxCount;
+                            nonAuxSubtype = subtype;
+                        } else {
+                            ++auxCount;
+                            auxSubtype = subtype;
+                        }
+                    }
+                }
+            }
+            if (nonAuxCount > 1 || auxCount > 1) {
+                return true;
+            } else if (nonAuxCount == 1 && auxCount == 1) {
+                if (nonAuxSubtype != null && auxSubtype != null
+                        && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
+                                || auxSubtype.overridesImplicitlyEnabledSubtype()
+                                || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
+                        && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
+                    return false;
+                }
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private boolean isKeyguardLocked() {
+        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+    }
+
+    // Caution! This method is called in this class. Handle multi-user carefully
+    @SuppressWarnings("deprecation")
+    @Override
+    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (token == null || mCurToken != token) {
+                int uid = Binder.getCallingUid();
+                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
+                return;
+            }
+            synchronized (mMethodMap) {
+                // apply policy for binder calls
+                if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
+                    vis = 0;
+                }
+                mImeWindowVis = vis;
+                mBackDisposition = backDisposition;
+                if (mStatusBar != null) {
+                    mStatusBar.setImeWindowStatus(token, vis, backDisposition);
+                }
+                final boolean iconVisibility = ((vis & (InputMethodService.IME_ACTIVE)) != 0)
+                        && (mWindowManagerService.isHardKeyboardAvailable()
+                                || (vis & (InputMethodService.IME_VISIBLE)) != 0);
+                final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+                if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+                    // Used to load label
+                    final CharSequence title = mRes.getText(
+                            com.android.internal.R.string.select_input_method);
+                    final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
+                            mContext, imi, mCurrentSubtype);
+
+                    mImeSwitcherNotification.setLatestEventInfo(
+                            mContext, title, summary, mImeSwitchPendingIntent);
+                    if (mNotificationManager != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "--- show notification: label =  " + summary);
+                        }
+                        mNotificationManager.notifyAsUser(null,
+                                com.android.internal.R.string.select_input_method,
+                                mImeSwitcherNotification, UserHandle.ALL);
+                        mNotificationShown = true;
+                    }
+                } else {
+                    if (mNotificationShown && mNotificationManager != null) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "--- hide notification");
+                        }
+                        mNotificationManager.cancelAsUser(null,
+                                com.android.internal.R.string.select_input_method, UserHandle.ALL);
+                        mNotificationShown = false;
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
+            for (int i = 0; i < spans.length; ++i) {
+                SuggestionSpan ss = spans[i];
+                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
+                    mSecureSuggestionSpans.put(ss, currentImi);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
+            // TODO: Do not send the intent if the process of the targetImi is already dead.
+            if (targetImi != null) {
+                final String[] suggestions = span.getSuggestions();
+                if (index < 0 || index >= suggestions.length) return false;
+                final String className = span.getNotificationTargetClassName();
+                final Intent intent = new Intent();
+                // Ensures that only a class in the original IME package will receive the
+                // notification.
+                intent.setClassName(targetImi.getPackageName(), className);
+                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
+                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
+                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
+                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void updateFromSettingsLocked(boolean enabledMayChange) {
+        if (enabledMayChange) {
+            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+            for (int i=0; i<enabled.size(); i++) {
+                // We allow the user to select "disabled until used" apps, so if they
+                // are enabling one of those here we now need to make it enabled.
+                InputMethodInfo imm = enabled.get(i);
+                try {
+                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
+                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                            mSettings.getCurrentUserId());
+                    if (ai != null && ai.enabledSetting
+                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Update state(" + imm.getId()
+                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
+                        }
+                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
+                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
+                                mContext.getBasePackageName());
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
+        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
+        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
+        // enabled.
+        String id = mSettings.getSelectedInputMethod();
+        // There is no input method selected, try to choose new applicable input method.
+        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
+            id = mSettings.getSelectedInputMethod();
+        }
+        if (!TextUtils.isEmpty(id)) {
+            try {
+                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
+            } catch (IllegalArgumentException e) {
+                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
+                mCurMethodId = null;
+                unbindCurrentMethodLocked(true, false);
+            }
+            mShortcutInputMethodsAndSubtypes.clear();
+        } else {
+            // There is no longer an input method set, so stop any current one.
+            mCurMethodId = null;
+            unbindCurrentMethodLocked(true, false);
+        }
+    }
+
+    /* package */ void setInputMethodLocked(String id, int subtypeId) {
+        InputMethodInfo info = mMethodMap.get(id);
+        if (info == null) {
+            throw new IllegalArgumentException("Unknown id: " + id);
+        }
+
+        // See if we need to notify a subtype change within the same IME.
+        if (id.equals(mCurMethodId)) {
+            final int subtypeCount = info.getSubtypeCount();
+            if (subtypeCount <= 0) {
+                return;
+            }
+            final InputMethodSubtype oldSubtype = mCurrentSubtype;
+            final InputMethodSubtype newSubtype;
+            if (subtypeId >= 0 && subtypeId < subtypeCount) {
+                newSubtype = info.getSubtypeAt(subtypeId);
+            } else {
+                // If subtype is null, try to find the most applicable one from
+                // getCurrentInputMethodSubtype.
+                newSubtype = getCurrentInputMethodSubtypeLocked();
+            }
+            if (newSubtype == null || oldSubtype == null) {
+                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+                        + ", new subtype = " + newSubtype);
+                return;
+            }
+            if (newSubtype != oldSubtype) {
+                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
+                if (mCurMethod != null) {
+                    try {
+                        refreshImeWindowVisibilityLocked();
+                        mCurMethod.changeInputMethodSubtype(newSubtype);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
+                    }
+                }
+            }
+            return;
+        }
+
+        // Changing to a different IME.
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            // Set a subtype to this input method.
+            // subtypeId the name of a subtype which will be set.
+            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
+            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
+            // because mCurMethodId is stored as a history in
+            // setSelectedInputMethodAndSubtypeLocked().
+            mCurMethodId = id;
+
+            if (ActivityManagerNative.isSystemReady()) {
+                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                intent.putExtra("input_method_id", id);
+                mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
+            }
+            unbindCurrentClientLocked();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public boolean showSoftInput(IInputMethodClient client, int flags,
+            ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mMethodMap) {
+                if (mCurClient == null || client == null
+                        || mCurClient.client.asBinder() != client.asBinder()) {
+                    try {
+                        // We need to check if this is the current client with
+                        // focus in the window manager, to allow this call to
+                        // be made before input is started in it.
+                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
+                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
+                            return false;
+                        }
+                    } catch (RemoteException e) {
+                        return false;
+                    }
+                }
+
+                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
+                return showCurrentInputLocked(flags, resultReceiver);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
+        mShowRequested = true;
+        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
+            mShowExplicitlyRequested = true;
+        }
+        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
+            mShowExplicitlyRequested = true;
+            mShowForced = true;
+        }
+
+        if (!mSystemReady) {
+            return false;
+        }
+
+        boolean res = false;
+        if (mCurMethod != null) {
+            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
+                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
+                    resultReceiver));
+            mInputShown = true;
+            if (mHaveConnection && !mVisibleBound) {
+                bindCurrentInputMethodService(
+                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
+                mVisibleBound = true;
+            }
+            res = true;
+        } else if (mHaveConnection && SystemClock.uptimeMillis()
+                >= (mLastBindTime+TIME_TO_RECONNECT)) {
+            // The client has asked to have the input method shown, but
+            // we have been sitting here too long with a connection to the
+            // service and no interface received, so let's disconnect/connect
+            // to try to prod things along.
+            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
+                    SystemClock.uptimeMillis()-mLastBindTime,1);
+            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
+            mContext.unbindService(this);
+            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
+                    | Context.BIND_NOT_VISIBLE);
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
+                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
+            }
+        }
+
+        return res;
+    }
+
+    @Override
+    public boolean hideSoftInput(IInputMethodClient client, int flags,
+            ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mMethodMap) {
+                if (mCurClient == null || client == null
+                        || mCurClient.client.asBinder() != client.asBinder()) {
+                    try {
+                        // We need to check if this is the current client with
+                        // focus in the window manager, to allow this call to
+                        // be made before input is started in it.
+                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
+                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+                                    + uid + ": " + client);
+                            setImeWindowVisibilityStatusHiddenLocked();
+                            return false;
+                        }
+                    } catch (RemoteException e) {
+                        setImeWindowVisibilityStatusHiddenLocked();
+                        return false;
+                    }
+                }
+
+                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
+                return hideCurrentInputLocked(flags, resultReceiver);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
+        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
+                && (mShowExplicitlyRequested || mShowForced)) {
+            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
+            return false;
+        }
+        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
+            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
+            return false;
+        }
+        boolean res;
+        if (mInputShown && mCurMethod != null) {
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
+                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
+            res = true;
+        } else {
+            res = false;
+        }
+        if (mHaveConnection && mVisibleBound) {
+            mContext.unbindService(mVisibleConnection);
+            mVisibleBound = false;
+        }
+        mInputShown = false;
+        mShowRequested = false;
+        mShowExplicitlyRequested = false;
+        mShowForced = false;
+        return res;
+    }
+
+    @Override
+    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
+            int controlFlags, int softInputMode, int windowFlags,
+            EditorInfo attribute, IInputContext inputContext) {
+        // Needs to check the validity before clearing calling identity
+        final boolean calledFromValidUser = calledFromValidUser();
+
+        InputBindResult res = null;
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mMethodMap) {
+                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
+                        + " controlFlags=#" + Integer.toHexString(controlFlags)
+                        + " softInputMode=#" + Integer.toHexString(softInputMode)
+                        + " windowFlags=#" + Integer.toHexString(windowFlags));
+
+                ClientState cs = mClients.get(client.asBinder());
+                if (cs == null) {
+                    throw new IllegalArgumentException("unknown client "
+                            + client.asBinder());
+                }
+
+                try {
+                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
+                        // Check with the window manager to make sure this client actually
+                        // has a window with focus.  If not, reject.  This is thread safe
+                        // because if the focus changes some time before or after, the
+                        // next client receiving focus that has any interest in input will
+                        // be calling through here after that change happens.
+                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
+                        return null;
+                    }
+                } catch (RemoteException e) {
+                }
+
+                if (!calledFromValidUser) {
+                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
+                    Slog.w(TAG, "If you want to interect with IME, you need "
+                            + "android.permission.INTERACT_ACROSS_USERS_FULL");
+                    hideCurrentInputLocked(0, null);
+                    return null;
+                }
+
+                if (mCurFocusedWindow == windowToken) {
+                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
+                            + " attribute=" + attribute + ", token = " + windowToken);
+                    if (attribute != null) {
+                        return startInputUncheckedLocked(cs, inputContext, attribute,
+                                controlFlags);
+                    }
+                    return null;
+                }
+                mCurFocusedWindow = windowToken;
+
+                // Should we auto-show the IME even if the caller has not
+                // specified what should be done with it?
+                // We only do this automatically if the window can resize
+                // to accommodate the IME (so what the user sees will give
+                // them good context without input information being obscured
+                // by the IME) or if running on a large screen where there
+                // is more room for the target window + IME.
+                final boolean doAutoShow =
+                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
+                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
+                        || mRes.getConfiguration().isLayoutSizeAtLeast(
+                                Configuration.SCREENLAYOUT_SIZE_LARGE);
+                final boolean isTextEditor =
+                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
+
+                // We want to start input before showing the IME, but after closing
+                // it.  We want to do this after closing it to help the IME disappear
+                // more quickly (not get stuck behind it initializing itself for the
+                // new focused input, even if its window wants to hide the IME).
+                boolean didStart = false;
+                        
+                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
+                        if (!isTextEditor || !doAutoShow) {
+                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
+                                // There is no focus view, and this window will
+                                // be behind any soft input window, so hide the
+                                // soft input window if it is shown.
+                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
+                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
+                            }
+                        } else if (isTextEditor && doAutoShow && (softInputMode &
+                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                            // There is a focus view, and we are navigating forward
+                            // into the window, so show the input window for the user.
+                            // We only do this automatically if the window can resize
+                            // to accommodate the IME (so what the user sees will give
+                            // them good context without input information being obscured
+                            // by the IME) or if running on a large screen where there
+                            // is more room for the target window + IME.
+                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
+                            if (attribute != null) {
+                                res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                        controlFlags);
+                                didStart = true;
+                            }
+                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                        }
+                        break;
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+                        // Do nothing.
+                        break;
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
+                        if ((softInputMode &
+                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
+                            hideCurrentInputLocked(0, null);
+                        }
+                        break;
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
+                        hideCurrentInputLocked(0, null);
+                        break;
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
+                        if ((softInputMode &
+                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
+                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
+                            if (attribute != null) {
+                                res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                        controlFlags);
+                                didStart = true;
+                            }
+                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                        }
+                        break;
+                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
+                        if (attribute != null) {
+                            res = startInputUncheckedLocked(cs, inputContext, attribute,
+                                    controlFlags);
+                            didStart = true;
+                        }
+                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
+                        break;
+                }
+
+                if (!didStart && attribute != null) {
+                    res = startInputUncheckedLocked(cs, inputContext, attribute,
+                            controlFlags);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return res;
+    }
+
+    @Override
+    public void showInputMethodPickerFromClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            if (mCurClient == null || client == null
+                    || mCurClient.client.asBinder() != client.asBinder()) {
+                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
+                        + Binder.getCallingUid() + ": " + client);
+            }
+
+            // Always call subtype picker, because subtype picker is a superset of input method
+            // picker.
+            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
+        }
+    }
+
+    @Override
+    public void setInputMethod(IBinder token, String id) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
+    }
+
+    @Override
+    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            if (subtype != null) {
+                setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode(
+                        mMethodMap.get(id), subtype.hashCode()));
+            } else {
+                setInputMethod(token, id);
+            }
+        }
+    }
+
+    @Override
+    public void showInputMethodAndSubtypeEnablerFromClient(
+            IInputMethodClient client, String inputMethodId) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            if (mCurClient == null || client == null
+                || mCurClient.client.asBinder() != client.asBinder()) {
+                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
+            }
+            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
+                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
+        }
+    }
+
+    @Override
+    public boolean switchToLastInputMethod(IBinder token) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+            final InputMethodInfo lastImi;
+            if (lastIme != null) {
+                lastImi = mMethodMap.get(lastIme.first);
+            } else {
+                lastImi = null;
+            }
+            String targetLastImiId = null;
+            int subtypeId = NOT_A_SUBTYPE_ID;
+            if (lastIme != null && lastImi != null) {
+                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
+                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
+                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
+                        : mCurrentSubtype.hashCode();
+                // If the last IME is the same as the current IME and the last subtype is not
+                // defined, there is no need to switch to the last IME.
+                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
+                    targetLastImiId = lastIme.first;
+                    subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+                }
+            }
+
+            if (TextUtils.isEmpty(targetLastImiId)
+                    && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
+                // This is a safety net. If the currentSubtype can't be added to the history
+                // and the framework couldn't find the last ime, we will make the last ime be
+                // the most applicable enabled keyboard subtype of the system imes.
+                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
+                if (enabled != null) {
+                    final int N = enabled.size();
+                    final String locale = mCurrentSubtype == null
+                            ? mRes.getConfiguration().locale.toString()
+                            : mCurrentSubtype.getLocale();
+                    for (int i = 0; i < N; ++i) {
+                        final InputMethodInfo imi = enabled.get(i);
+                        if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
+                            InputMethodSubtype keyboardSubtype =
+                                    InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
+                                            InputMethodUtils.getSubtypes(imi),
+                                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
+                            if (keyboardSubtype != null) {
+                                targetLastImiId = imi.getId();
+                                subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+                                        imi, keyboardSubtype.hashCode());
+                                if(keyboardSubtype.getLocale().equals(locale)) {
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (!TextUtils.isEmpty(targetLastImiId)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
+                            + ", from: " + mCurMethodId + ", " + subtypeId);
+                }
+                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
+                return true;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    @Override
+    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
+                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+            if (nextSubtype == null) {
+                return false;
+            }
+            setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
+            return true;
+        }
+    }
+
+    @Override
+    public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
+                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
+            if (nextSubtype == null) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    @Override
+    public InputMethodSubtype getLastInputMethodSubtype() {
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mMethodMap) {
+            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+            // TODO: Handle the case of the last IME with no subtypes
+            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+                    || TextUtils.isEmpty(lastIme.second)) return null;
+            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+            if (lastImi == null) return null;
+            try {
+                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
+                final int lastSubtypeId =
+                        InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
+                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+                    return null;
+                }
+                return lastImi.getSubtypeAt(lastSubtypeId);
+            } catch (NumberFormatException e) {
+                return null;
+            }
+        }
+    }
+
+    @Override
+    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        // By this IPC call, only a process which shares the same uid with the IME can add
+        // additional input method subtypes to the IME.
+        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
+        synchronized (mMethodMap) {
+            final InputMethodInfo imi = mMethodMap.get(imiId);
+            if (imi == null) return;
+            final String[] packageInfos;
+            try {
+                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get package infos");
+                return;
+            }
+            if (packageInfos != null) {
+                final int packageNum = packageInfos.length;
+                for (int i = 0; i < packageNum; ++i) {
+                    if (packageInfos[i].equals(imi.getPackageName())) {
+                        mFileManager.addInputMethodSubtypes(imi, subtypes);
+                        final long ident = Binder.clearCallingIdentity();
+                        try {
+                            buildInputMethodListLocked(mMethodList, mMethodMap,
+                                    false /* resetDefaultEnabledIme */);
+                        } finally {
+                            Binder.restoreCallingIdentity(ident);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+        return;
+    }
+
+    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
+        synchronized (mMethodMap) {
+            if (token == null) {
+                if (mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException(
+                            "Using null token requires permission "
+                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
+                }
+            } else if (mCurToken != token) {
+                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+                        + " token: " + token);
+                return;
+            }
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                setInputMethodLocked(id, subtypeId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void hideMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            if (token == null || mCurToken != token) {
+                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+                        + Binder.getCallingUid() + " token: " + token);
+                return;
+            }
+            long ident = Binder.clearCallingIdentity();
+            try {
+                hideCurrentInputLocked(flags, null);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void showMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized (mMethodMap) {
+            if (token == null || mCurToken != token) {
+                Slog.w(TAG, "Ignoring showMySoftInput of uid "
+                        + Binder.getCallingUid() + " token: " + token);
+                return;
+            }
+            long ident = Binder.clearCallingIdentity();
+            try {
+                showCurrentInputLocked(flags, null);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    void setEnabledSessionInMainThread(SessionState session) {
+        if (mEnabledSession != session) {
+            if (mEnabledSession != null) {
+                try {
+                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
+                    mEnabledSession.method.setSessionEnabled(
+                            mEnabledSession.session, false);
+                } catch (RemoteException e) {
+                }
+            }
+            mEnabledSession = session;
+            try {
+                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
+                session.method.setSessionEnabled(
+                        session.session, true);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        SomeArgs args;
+        switch (msg.what) {
+            case MSG_SHOW_IM_PICKER:
+                showInputMethodMenu();
+                return true;
+
+            case MSG_SHOW_IM_SUBTYPE_PICKER:
+                showInputMethodSubtypeMenu();
+                return true;
+
+            case MSG_SHOW_IM_SUBTYPE_ENABLER:
+                args = (SomeArgs)msg.obj;
+                showInputMethodAndSubtypeEnabler((String)args.arg1);
+                args.recycle();
+                return true;
+
+            case MSG_SHOW_IM_CONFIG:
+                showConfigureInputMethods();
+                return true;
+
+            // ---------------------------------------------------------
+
+            case MSG_UNBIND_INPUT:
+                try {
+                    ((IInputMethod)msg.obj).unbindInput();
+                } catch (RemoteException e) {
+                    // There is nothing interesting about the method dying.
+                }
+                return true;
+            case MSG_BIND_INPUT:
+                args = (SomeArgs)msg.obj;
+                try {
+                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+            case MSG_SHOW_SOFT_INPUT:
+                args = (SomeArgs)msg.obj;
+                try {
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+                            + msg.arg1 + ", " + args.arg2 + ")");
+                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+            case MSG_HIDE_SOFT_INPUT:
+                args = (SomeArgs)msg.obj;
+                try {
+                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+                            + args.arg2 + ")");
+                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+            case MSG_ATTACH_TOKEN:
+                args = (SomeArgs)msg.obj;
+                try {
+                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
+                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+            case MSG_CREATE_SESSION: {
+                args = (SomeArgs)msg.obj;
+                IInputMethod method = (IInputMethod)args.arg1;
+                InputChannel channel = (InputChannel)args.arg2;
+                try {
+                    method.createSession(channel, (IInputSessionCallback)args.arg3);
+                } catch (RemoteException e) {
+                } finally {
+                    // Dispose the channel if the input method is not local to this process
+                    // because the remote proxy will get its own copy when unparceled.
+                    if (channel != null && Binder.isProxy(method)) {
+                        channel.dispose();
+                    }
+                }
+                args.recycle();
+                return true;
+            }
+            // ---------------------------------------------------------
+
+            case MSG_START_INPUT:
+                args = (SomeArgs)msg.obj;
+                try {
+                    SessionState session = (SessionState)args.arg1;
+                    setEnabledSessionInMainThread(session);
+                    session.method.startInput((IInputContext)args.arg2,
+                            (EditorInfo)args.arg3);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+            case MSG_RESTART_INPUT:
+                args = (SomeArgs)msg.obj;
+                try {
+                    SessionState session = (SessionState)args.arg1;
+                    setEnabledSessionInMainThread(session);
+                    session.method.restartInput((IInputContext)args.arg2,
+                            (EditorInfo)args.arg3);
+                } catch (RemoteException e) {
+                }
+                args.recycle();
+                return true;
+
+            // ---------------------------------------------------------
+
+            case MSG_UNBIND_METHOD:
+                try {
+                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
+                } catch (RemoteException e) {
+                    // There is nothing interesting about the last client dying.
+                }
+                return true;
+            case MSG_BIND_METHOD: {
+                args = (SomeArgs)msg.obj;
+                IInputMethodClient client = (IInputMethodClient)args.arg1;
+                InputBindResult res = (InputBindResult)args.arg2;
+                try {
+                    client.onBindMethod(res);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
+                } finally {
+                    // Dispose the channel if the input method is not local to this process
+                    // because the remote proxy will get its own copy when unparceled.
+                    if (res.channel != null && Binder.isProxy(client)) {
+                        res.channel.dispose();
+                    }
+                }
+                args.recycle();
+                return true;
+            }
+            case MSG_SET_ACTIVE:
+                try {
+                    ((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
+                            + ((ClientState)msg.obj).pid + " uid "
+                            + ((ClientState)msg.obj).uid);
+                }
+                return true;
+
+            // --------------------------------------------------------------
+            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
+                mHardKeyboardListener.handleHardKeyboardStatusChange(
+                        msg.arg1 == 1, msg.arg2 == 1);
+                return true;
+        }
+        return false;
+    }
+
+    private boolean chooseNewDefaultIMELocked() {
+        final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
+                mSettings.getEnabledInputMethodListLocked());
+        if (imi != null) {
+            if (DEBUG) {
+                Slog.d(TAG, "New default IME was selected: " + imi.getId());
+            }
+            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
+            return true;
+        }
+
+        return false;
+    }
+
+    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
+            HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
+        if (DEBUG) {
+            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+                    + " \n ------ \n" + InputMethodUtils.getStackTrace());
+        }
+        list.clear();
+        map.clear();
+
+        // Use for queryIntentServicesAsUser
+        final PackageManager pm = mContext.getPackageManager();
+        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
+        if (disabledSysImes == null) disabledSysImes = "";
+
+        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                new Intent(InputMethod.SERVICE_INTERFACE),
+                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
+                mSettings.getCurrentUserId());
+
+        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
+                mFileManager.getAllAdditionalInputMethodSubtypes();
+        for (int i = 0; i < services.size(); ++i) {
+            ResolveInfo ri = services.get(i);
+            ServiceInfo si = ri.serviceInfo;
+            ComponentName compName = new ComponentName(si.packageName, si.name);
+            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
+                    si.permission)) {
+                Slog.w(TAG, "Skipping input method " + compName
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_INPUT_METHOD);
+                continue;
+            }
+
+            if (DEBUG) Slog.d(TAG, "Checking " + compName);
+
+            try {
+                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
+                list.add(p);
+                final String id = p.getId();
+                map.put(id, p);
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Found an input method " + p);
+                }
+
+            } catch (XmlPullParserException e) {
+                Slog.w(TAG, "Unable to load input method " + compName, e);
+            } catch (IOException e) {
+                Slog.w(TAG, "Unable to load input method " + compName, e);
+            }
+        }
+
+        if (resetDefaultEnabledIme) {
+            final ArrayList<InputMethodInfo> defaultEnabledIme =
+                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
+            for (int i = 0; i < defaultEnabledIme.size(); ++i) {
+                final InputMethodInfo imi =  defaultEnabledIme.get(i);
+                if (DEBUG) {
+                    Slog.d(TAG, "--- enable ime = " + imi);
+                }
+                setInputMethodEnabledLocked(imi.getId(), true);
+            }
+        }
+
+        final String defaultImiId = mSettings.getSelectedInputMethod();
+        if (!TextUtils.isEmpty(defaultImiId)) {
+            if (!map.containsKey(defaultImiId)) {
+                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
+                if (chooseNewDefaultIMELocked()) {
+                    updateFromSettingsLocked(true);
+                }
+            } else {
+                // Double check that the default IME is certainly enabled.
+                setInputMethodEnabledLocked(defaultImiId, true);
+            }
+        }
+    }
+
+    // ----------------------------------------------------------------------
+
+    private void showInputMethodMenu() {
+        showInputMethodMenuInternal(false);
+    }
+
+    private void showInputMethodSubtypeMenu() {
+        showInputMethodMenuInternal(true);
+    }
+
+    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
+        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        if (!TextUtils.isEmpty(inputMethodId)) {
+            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
+        }
+        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
+    }
+
+    private void showConfigureInputMethods() {
+        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
+    }
+
+    private boolean isScreenLocked() {
+        return mKeyguardManager != null
+                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
+    }
+    private void showInputMethodMenuInternal(boolean showSubtypes) {
+        if (DEBUG) Slog.v(TAG, "Show switching menu");
+
+        final Context context = mContext;
+        final boolean isScreenLocked = isScreenLocked();
+
+        final String lastInputMethodId = mSettings.getSelectedInputMethod();
+        int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
+
+        synchronized (mMethodMap) {
+            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
+                    getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
+            if (immis == null || immis.size() == 0) {
+                return;
+            }
+
+            hideInputMethodMenuLocked();
+
+            final List<ImeSubtypeListItem> imList =
+                    mImListManager.getSortedInputMethodAndSubtypeList(
+                            showSubtypes, mInputShown, isScreenLocked);
+
+            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
+                if (currentSubtype != null) {
+                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
+                    lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+                            currentImi, currentSubtype.hashCode());
+                }
+            }
+
+            final int N = imList.size();
+            mIms = new InputMethodInfo[N];
+            mSubtypeIds = new int[N];
+            int checkedItem = 0;
+            for (int i = 0; i < N; ++i) {
+                final ImeSubtypeListItem item = imList.get(i);
+                mIms[i] = item.mImi;
+                mSubtypeIds[i] = item.mSubtypeId;
+                if (mIms[i].getId().equals(lastInputMethodId)) {
+                    int subtypeId = mSubtypeIds[i];
+                    if ((subtypeId == NOT_A_SUBTYPE_ID)
+                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
+                            || (subtypeId == lastInputMethodSubtypeId)) {
+                        checkedItem = i;
+                    }
+                }
+            }
+            final TypedArray a = context.obtainStyledAttributes(null,
+                    com.android.internal.R.styleable.DialogPreference,
+                    com.android.internal.R.attr.alertDialogStyle, 0);
+            mDialogBuilder = new AlertDialog.Builder(context)
+                    .setOnCancelListener(new OnCancelListener() {
+                        @Override
+                        public void onCancel(DialogInterface dialog) {
+                            hideInputMethodMenu();
+                        }
+                    })
+                    .setIcon(a.getDrawable(
+                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
+            a.recycle();
+            final LayoutInflater inflater =
+                    (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            final View tv = inflater.inflate(
+                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
+            mDialogBuilder.setCustomTitle(tv);
+
+            // Setup layout for a toggle switch of the hardware keyboard
+            mSwitchingDialogTitleView = tv;
+            mSwitchingDialogTitleView.findViewById(
+                    com.android.internal.R.id.hard_keyboard_section).setVisibility(
+                            mWindowManagerService.isHardKeyboardAvailable() ?
+                                    View.VISIBLE : View.GONE);
+            final Switch hardKeySwitch =  ((Switch)mSwitchingDialogTitleView.findViewById(
+                    com.android.internal.R.id.hard_keyboard_switch));
+            hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled());
+            hardKeySwitch.setOnCheckedChangeListener(
+                    new OnCheckedChangeListener() {
+                        @Override
+                        public void onCheckedChanged(
+                                CompoundButton buttonView, boolean isChecked) {
+                            mWindowManagerService.setHardKeyboardEnabled(isChecked);
+                            // Ensure that the input method dialog is dismissed when changing
+                            // the hardware keyboard state.
+                            hideInputMethodMenu();
+                        }
+                    });
+
+            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
+                    com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
+                    checkedItem);
+
+            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
+                    new AlertDialog.OnClickListener() {
+                        @Override
+                        public void onClick(DialogInterface dialog, int which) {
+                            synchronized (mMethodMap) {
+                                if (mIms == null || mIms.length <= which
+                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
+                                    return;
+                                }
+                                InputMethodInfo im = mIms[which];
+                                int subtypeId = mSubtypeIds[which];
+                                adapter.mCheckedItem = which;
+                                adapter.notifyDataSetChanged();
+                                hideInputMethodMenu();
+                                if (im != null) {
+                                    if ((subtypeId < 0)
+                                            || (subtypeId >= im.getSubtypeCount())) {
+                                        subtypeId = NOT_A_SUBTYPE_ID;
+                                    }
+                                    setInputMethodLocked(im.getId(), subtypeId);
+                                }
+                            }
+                        }
+                    });
+
+            if (showSubtypes && !isScreenLocked) {
+                mDialogBuilder.setPositiveButton(
+                        com.android.internal.R.string.configure_input_methods,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int whichButton) {
+                                showConfigureInputMethods();
+                            }
+                        });
+            }
+            mSwitchingDialog = mDialogBuilder.create();
+            mSwitchingDialog.setCanceledOnTouchOutside(true);
+            mSwitchingDialog.getWindow().setType(
+                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+            mSwitchingDialog.getWindow().getAttributes().privateFlags |=
+                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
+            mSwitchingDialog.show();
+        }
+    }
+
+    private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
+        public final CharSequence mImeName;
+        public final CharSequence mSubtypeName;
+        public final InputMethodInfo mImi;
+        public final int mSubtypeId;
+        private final boolean mIsSystemLocale;
+        private final boolean mIsSystemLanguage;
+
+        public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
+                InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
+            mImeName = imeName;
+            mSubtypeName = subtypeName;
+            mImi = imi;
+            mSubtypeId = subtypeId;
+            if (TextUtils.isEmpty(subtypeLocale)) {
+                mIsSystemLocale = false;
+                mIsSystemLanguage = false;
+            } else {
+                mIsSystemLocale = subtypeLocale.equals(systemLocale);
+                mIsSystemLanguage = mIsSystemLocale
+                        || subtypeLocale.startsWith(systemLocale.substring(0, 2));
+            }
+        }
+
+        @Override
+        public int compareTo(ImeSubtypeListItem other) {
+            if (TextUtils.isEmpty(mImeName)) {
+                return 1;
+            }
+            if (TextUtils.isEmpty(other.mImeName)) {
+                return -1;
+            }
+            if (!TextUtils.equals(mImeName, other.mImeName)) {
+                return mImeName.toString().compareTo(other.mImeName.toString());
+            }
+            if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
+                return 0;
+            }
+            if (mIsSystemLocale) {
+                return -1;
+            }
+            if (other.mIsSystemLocale) {
+                return 1;
+            }
+            if (mIsSystemLanguage) {
+                return -1;
+            }
+            if (other.mIsSystemLanguage) {
+                return 1;
+            }
+            if (TextUtils.isEmpty(mSubtypeName)) {
+                return 1;
+            }
+            if (TextUtils.isEmpty(other.mSubtypeName)) {
+                return -1;
+            }
+            return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
+        }
+    }
+
+    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
+        private final LayoutInflater mInflater;
+        private final int mTextViewResourceId;
+        private final List<ImeSubtypeListItem> mItemsList;
+        public int mCheckedItem;
+        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
+                List<ImeSubtypeListItem> itemsList, int checkedItem) {
+            super(context, textViewResourceId, itemsList);
+            mTextViewResourceId = textViewResourceId;
+            mItemsList = itemsList;
+            mCheckedItem = checkedItem;
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            final View view = convertView != null ? convertView
+                    : mInflater.inflate(mTextViewResourceId, null);
+            if (position < 0 || position >= mItemsList.size()) return view;
+            final ImeSubtypeListItem item = mItemsList.get(position);
+            final CharSequence imeName = item.mImeName;
+            final CharSequence subtypeName = item.mSubtypeName;
+            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
+            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
+            if (TextUtils.isEmpty(subtypeName)) {
+                firstTextView.setText(imeName);
+                secondTextView.setVisibility(View.GONE);
+            } else {
+                firstTextView.setText(subtypeName);
+                secondTextView.setText(imeName);
+                secondTextView.setVisibility(View.VISIBLE);
+            }
+            final RadioButton radioButton =
+                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
+            radioButton.setChecked(position == mCheckedItem);
+            return view;
+        }
+    }
+
+    void hideInputMethodMenu() {
+        synchronized (mMethodMap) {
+            hideInputMethodMenuLocked();
+        }
+    }
+
+    void hideInputMethodMenuLocked() {
+        if (DEBUG) Slog.v(TAG, "Hide switching menu");
+
+        if (mSwitchingDialog != null) {
+            mSwitchingDialog.dismiss();
+            mSwitchingDialog = null;
+        }
+
+        mDialogBuilder = null;
+        mIms = null;
+    }
+
+    // ----------------------------------------------------------------------
+
+    @Override
+    public boolean setInputMethodEnabled(String id, boolean enabled) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Requires permission "
+                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
+            }
+            
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return setInputMethodEnabledLocked(id, enabled);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
+        // Make sure this is a valid input method.
+        InputMethodInfo imm = mMethodMap.get(id);
+        if (imm == null) {
+            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
+        }
+
+        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
+                .getEnabledInputMethodsAndSubtypeListLocked();
+
+        if (enabled) {
+            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
+                if (pair.first.equals(id)) {
+                    // We are enabling this input method, but it is already enabled.
+                    // Nothing to do. The previous state was enabled.
+                    return true;
+                }
+            }
+            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
+            // Previous state was disabled.
+            return false;
+        } else {
+            StringBuilder builder = new StringBuilder();
+            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+                    builder, enabledInputMethodsList, id)) {
+                // Disabled input method is currently selected, switch to another one.
+                final String selId = mSettings.getSelectedInputMethod();
+                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
+                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
+                    resetSelectedInputMethodAndSubtypeLocked("");
+                }
+                // Previous state was enabled.
+                return true;
+            } else {
+                // We are disabling the input method but it is already disabled.
+                // Nothing to do.  The previous state was disabled.
+                return false;
+            }
+        }
+    }
+
+    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
+            boolean setSubtypeOnly) {
+        // Update the history of InputMethod and Subtype
+        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
+
+        // Set Subtype here
+        if (imi == null || subtypeId < 0) {
+            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+            mCurrentSubtype = null;
+        } else {
+            if (subtypeId < imi.getSubtypeCount()) {
+                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
+                mSettings.putSelectedSubtype(subtype.hashCode());
+                mCurrentSubtype = subtype;
+            } else {
+                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+                // If the subtype is not specified, choose the most applicable one
+                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
+            }
+        }
+
+        // Workaround.
+        // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
+        // IMEs are not recognized and considered uninstalled.
+        // Actually, we can't move everything after SystemReady because
+        // IMMS needs to run in the encryption lock screen. So, we just skip changing
+        // the default IME here and try cheking the default IME again in systemReady().
+        // TODO: Do nothing before system ready and implement a separated logic for
+        // the encryption lock screen.
+        // TODO: ASEC should be ready before IMMS is instantiated.
+        if (mSystemReady && !setSubtypeOnly) {
+            // Set InputMethod here
+            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
+        }
+    }
+
+    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
+        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
+        int lastSubtypeId = NOT_A_SUBTYPE_ID;
+        // newDefaultIme is empty when there is no candidate for the selected IME.
+        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
+            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
+            if (subtypeHashCode != null) {
+                try {
+                    lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
+                            imi, Integer.valueOf(subtypeHashCode));
+                } catch (NumberFormatException e) {
+                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
+                }
+            }
+        }
+        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
+    }
+
+    // If there are no selected shortcuts, tries finding the most applicable ones.
+    private Pair<InputMethodInfo, InputMethodSubtype>
+            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
+        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
+        InputMethodInfo mostApplicableIMI = null;
+        InputMethodSubtype mostApplicableSubtype = null;
+        boolean foundInSystemIME = false;
+
+        // Search applicable subtype for each InputMethodInfo
+        for (InputMethodInfo imi: imis) {
+            final String imiId = imi.getId();
+            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
+                continue;
+            }
+            InputMethodSubtype subtype = null;
+            final List<InputMethodSubtype> enabledSubtypes =
+                    mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+            // 1. Search by the current subtype's locale from enabledSubtypes.
+            if (mCurrentSubtype != null) {
+                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
+            }
+            // 2. Search by the system locale from enabledSubtypes.
+            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
+            if (subtype == null) {
+                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                        mRes, enabledSubtypes, mode, null, true);
+            }
+            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
+                    InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
+            final ArrayList<InputMethodSubtype> subtypesForSearch =
+                    overridingImplicitlyEnabledSubtypes.isEmpty()
+                            ? InputMethodUtils.getSubtypes(imi)
+                            : overridingImplicitlyEnabledSubtypes;
+            // 4. Search by the current subtype's locale from all subtypes.
+            if (subtype == null && mCurrentSubtype != null) {
+                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
+            }
+            // 5. Search by the system locale from all subtypes.
+            // 6. Search the first enabled subtype matched with mode from all subtypes.
+            if (subtype == null) {
+                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                        mRes, subtypesForSearch, mode, null, true);
+            }
+            if (subtype != null) {
+                if (imiId.equals(mCurMethodId)) {
+                    // The current input method is the most applicable IME.
+                    mostApplicableIMI = imi;
+                    mostApplicableSubtype = subtype;
+                    break;
+                } else if (!foundInSystemIME) {
+                    // The system input method is 2nd applicable IME.
+                    mostApplicableIMI = imi;
+                    mostApplicableSubtype = subtype;
+                    if ((imi.getServiceInfo().applicationInfo.flags
+                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        foundInSystemIME = true;
+                    }
+                }
+            }
+        }
+        if (DEBUG) {
+            if (mostApplicableIMI != null) {
+                Slog.w(TAG, "Most applicable shortcut input method was:"
+                        + mostApplicableIMI.getId());
+                if (mostApplicableSubtype != null) {
+                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
+                            + "," + mostApplicableSubtype.getMode() + ","
+                            + mostApplicableSubtype.getLocale());
+                }
+            }
+        }
+        if (mostApplicableIMI != null) {
+            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
+                    mostApplicableSubtype);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @return Return the current subtype of this input method.
+     */
+    @Override
+    public InputMethodSubtype getCurrentInputMethodSubtype() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mMethodMap) {
+            return getCurrentInputMethodSubtypeLocked();
+        }
+    }
+
+    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
+        if (mCurMethodId == null) {
+            return null;
+        }
+        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
+        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
+        }
+        if (!subtypeIsSelected || mCurrentSubtype == null
+                || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
+            if (subtypeId == NOT_A_SUBTYPE_ID) {
+                // If there are no selected subtypes, the framework will try to find
+                // the most applicable subtype from explicitly or implicitly enabled
+                // subtypes.
+                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
+                // If there is only one explicitly or implicitly enabled subtype,
+                // just returns it.
+                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
+                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+                    mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
+                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
+                    if (mCurrentSubtype == null) {
+                        mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
+                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
+                                true);
+                    }
+                }
+            } else {
+                mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
+            }
+        }
+        return mCurrentSubtype;
+    }
+
+    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
+            InputMethodSubtype subtype) {
+        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
+            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
+        } else {
+            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+            subtypes.add(subtype);
+            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
+        }
+    }
+
+    // TODO: We should change the return type from List to List<Parcelable>
+    @SuppressWarnings("rawtypes")
+    @Override
+    public List getShortcutInputMethodsAndSubtypes() {
+        synchronized (mMethodMap) {
+            ArrayList<Object> ret = new ArrayList<Object>();
+            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
+                // If there are no selected shortcut subtypes, the framework will try to find
+                // the most applicable subtype from all subtypes whose mode is
+                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
+                Pair<InputMethodInfo, InputMethodSubtype> info =
+                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
+                            InputMethodUtils.SUBTYPE_MODE_VOICE);
+                if (info != null) {
+                    ret.add(info.first);
+                    ret.add(info.second);
+                }
+                return ret;
+            }
+            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
+                ret.add(imi);
+                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
+                    ret.add(subtype);
+                }
+            }
+            return ret;
+        }
+    }
+
+    @Override
+    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized (mMethodMap) {
+            if (subtype != null && mCurMethodId != null) {
+                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
+                if (subtypeId != NOT_A_SUBTYPE_ID) {
+                    setInputMethodLocked(mCurMethodId, subtypeId);
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private static class InputMethodAndSubtypeListManager {
+        private final Context mContext;
+        // Used to load label
+        private final PackageManager mPm;
+        private final InputMethodManagerService mImms;
+        private final String mSystemLocaleStr;
+        public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) {
+            mContext = context;
+            mPm = context.getPackageManager();
+            mImms = imms;
+            final Locale locale = context.getResources().getConfiguration().locale;
+            mSystemLocaleStr = locale != null ? locale.toString() : "";
+        }
+
+        private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
+                new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
+                        new Comparator<InputMethodInfo>() {
+                            @Override
+                            public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
+                                if (imi2 == null) return 0;
+                                if (imi1 == null) return 1;
+                                if (mPm == null) {
+                                    return imi1.getId().compareTo(imi2.getId());
+                                }
+                                CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
+                                CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
+                                return imiId1.toString().compareTo(imiId2.toString());
+                            }
+                        });
+
+        public ImeSubtypeListItem getNextInputMethod(
+                boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
+            if (imi == null) {
+                return null;
+            }
+            final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList();
+            if (imList.size() <= 1) {
+                return null;
+            }
+            final int N = imList.size();
+            final int currentSubtypeId = subtype != null
+                    ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
+                    : NOT_A_SUBTYPE_ID;
+            for (int i = 0; i < N; ++i) {
+                final ImeSubtypeListItem isli = imList.get(i);
+                if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
+                    if (!onlyCurrentIme) {
+                        return imList.get((i + 1) % N);
+                    }
+                    for (int j = 0; j < N - 1; ++j) {
+                        final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
+                        if (candidate.mImi.equals(imi)) {
+                            return candidate;
+                        }
+                    }
+                    return null;
+                }
+            }
+            return null;
+        }
+
+        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
+            return getSortedInputMethodAndSubtypeList(true, false, false);
+        }
+
+        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
+                boolean inputShown, boolean isScreenLocked) {
+            final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
+            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
+                    mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
+            if (immis == null || immis.size() == 0) {
+                return Collections.emptyList();
+            }
+            mSortedImmis.clear();
+            mSortedImmis.putAll(immis);
+            for (InputMethodInfo imi : mSortedImmis.keySet()) {
+                if (imi == null) continue;
+                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
+                HashSet<String> enabledSubtypeSet = new HashSet<String>();
+                for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
+                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
+                }
+                final CharSequence imeLabel = imi.loadLabel(mPm);
+                if (showSubtypes && enabledSubtypeSet.size() > 0) {
+                    final int subtypeCount = imi.getSubtypeCount();
+                    if (DEBUG) {
+                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
+                    }
+                    for (int j = 0; j < subtypeCount; ++j) {
+                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
+                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
+                        // We show all enabled IMEs and subtypes when an IME is shown.
+                        if (enabledSubtypeSet.contains(subtypeHashCode)
+                                && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
+                            final CharSequence subtypeLabel =
+                                    subtype.overridesImplicitlyEnabledSubtype() ? null
+                                            : subtype.getDisplayName(mContext, imi.getPackageName(),
+                                                    imi.getServiceInfo().applicationInfo);
+                            imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j,
+                                    subtype.getLocale(), mSystemLocaleStr));
+
+                            // Removing this subtype from enabledSubtypeSet because we no longer
+                            // need to add an entry of this subtype to imList to avoid duplicated
+                            // entries.
+                            enabledSubtypeSet.remove(subtypeHashCode);
+                        }
+                    }
+                } else {
+                    imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID,
+                            null, mSystemLocaleStr));
+                }
+            }
+            Collections.sort(imList);
+            return imList;
+        }
+    }
+
+    // TODO: Cache the state for each user and reset when the cached user is removed.
+    private static class InputMethodFileManager {
+        private static final String SYSTEM_PATH = "system";
+        private static final String INPUT_METHOD_PATH = "inputmethod";
+        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
+        private static final String NODE_SUBTYPES = "subtypes";
+        private static final String NODE_SUBTYPE = "subtype";
+        private static final String NODE_IMI = "imi";
+        private static final String ATTR_ID = "id";
+        private static final String ATTR_LABEL = "label";
+        private static final String ATTR_ICON = "icon";
+        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
+        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
+        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
+        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
+        private final AtomicFile mAdditionalInputMethodSubtypeFile;
+        private final HashMap<String, InputMethodInfo> mMethodMap;
+        private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
+                new HashMap<String, List<InputMethodSubtype>>();
+        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
+            if (methodMap == null) {
+                throw new NullPointerException("methodMap is null");
+            }
+            mMethodMap = methodMap;
+            final File systemDir = userId == UserHandle.USER_OWNER
+                    ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
+                    : Environment.getUserSystemDirectory(userId);
+            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
+            if (!inputMethodDir.mkdirs()) {
+                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
+            }
+            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
+            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
+            if (!subtypeFile.exists()) {
+                // If "subtypes.xml" doesn't exist, create a blank file.
+                writeAdditionalInputMethodSubtypes(
+                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
+            } else {
+                readAdditionalInputMethodSubtypes(
+                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
+            }
+        }
+
+        private void deleteAllInputMethodSubtypes(String imiId) {
+            synchronized (mMethodMap) {
+                mAdditionalSubtypesMap.remove(imiId);
+                writeAdditionalInputMethodSubtypes(
+                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
+            }
+        }
+
+        public void addInputMethodSubtypes(
+                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
+            synchronized (mMethodMap) {
+                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+                final int N = additionalSubtypes.length;
+                for (int i = 0; i < N; ++i) {
+                    final InputMethodSubtype subtype = additionalSubtypes[i];
+                    if (!subtypes.contains(subtype)) {
+                        subtypes.add(subtype);
+                    } else {
+                        Slog.w(TAG, "Duplicated subtype definition found: "
+                                + subtype.getLocale() + ", " + subtype.getMode());
+                    }
+                }
+                mAdditionalSubtypesMap.put(imi.getId(), subtypes);
+                writeAdditionalInputMethodSubtypes(
+                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
+            }
+        }
+
+        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
+            synchronized (mMethodMap) {
+                return mAdditionalSubtypesMap;
+            }
+        }
+
+        private static void writeAdditionalInputMethodSubtypes(
+                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
+                HashMap<String, InputMethodInfo> methodMap) {
+            // Safety net for the case that this function is called before methodMap is set.
+            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
+            FileOutputStream fos = null;
+            try {
+                fos = subtypesFile.startWrite();
+                final XmlSerializer out = new FastXmlSerializer();
+                out.setOutput(fos, "utf-8");
+                out.startDocument(null, true);
+                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                out.startTag(null, NODE_SUBTYPES);
+                for (String imiId : allSubtypes.keySet()) {
+                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
+                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
+                        continue;
+                    }
+                    out.startTag(null, NODE_IMI);
+                    out.attribute(null, ATTR_ID, imiId);
+                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
+                    final int N = subtypesList.size();
+                    for (int i = 0; i < N; ++i) {
+                        final InputMethodSubtype subtype = subtypesList.get(i);
+                        out.startTag(null, NODE_SUBTYPE);
+                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
+                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
+                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
+                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
+                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
+                        out.attribute(null, ATTR_IS_AUXILIARY,
+                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
+                        out.endTag(null, NODE_SUBTYPE);
+                    }
+                    out.endTag(null, NODE_IMI);
+                }
+                out.endTag(null, NODE_SUBTYPES);
+                out.endDocument();
+                subtypesFile.finishWrite(fos);
+            } catch (java.io.IOException e) {
+                Slog.w(TAG, "Error writing subtypes", e);
+                if (fos != null) {
+                    subtypesFile.failWrite(fos);
+                }
+            }
+        }
+
+        private static void readAdditionalInputMethodSubtypes(
+                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
+            if (allSubtypes == null || subtypesFile == null) return;
+            allSubtypes.clear();
+            FileInputStream fis = null;
+            try {
+                fis = subtypesFile.openRead();
+                final XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(fis, null);
+                int type = parser.getEventType();
+                // Skip parsing until START_TAG
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {}
+                String firstNodeName = parser.getName();
+                if (!NODE_SUBTYPES.equals(firstNodeName)) {
+                    throw new XmlPullParserException("Xml doesn't start with subtypes");
+                }
+                final int depth =parser.getDepth();
+                String currentImiId = null;
+                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
+                while (((type = parser.next()) != XmlPullParser.END_TAG
+                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                    if (type != XmlPullParser.START_TAG)
+                        continue;
+                    final String nodeName = parser.getName();
+                    if (NODE_IMI.equals(nodeName)) {
+                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
+                        if (TextUtils.isEmpty(currentImiId)) {
+                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
+                            continue;
+                        }
+                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
+                        allSubtypes.put(currentImiId, tempSubtypesArray);
+                    } else if (NODE_SUBTYPE.equals(nodeName)) {
+                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
+                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
+                            continue;
+                        }
+                        final int icon = Integer.valueOf(
+                                parser.getAttributeValue(null, ATTR_ICON));
+                        final int label = Integer.valueOf(
+                                parser.getAttributeValue(null, ATTR_LABEL));
+                        final String imeSubtypeLocale =
+                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
+                        final String imeSubtypeMode =
+                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
+                        final String imeSubtypeExtraValue =
+                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
+                        final boolean isAuxiliary = "1".equals(String.valueOf(
+                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
+                        final InputMethodSubtype subtype =
+                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
+                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
+                        tempSubtypesArray.add(subtype);
+                    }
+                }
+            } catch (XmlPullParserException e) {
+                Slog.w(TAG, "Error reading subtypes: " + e);
+                return;
+            } catch (java.io.IOException e) {
+                Slog.w(TAG, "Error reading subtypes: " + e);
+                return;
+            } catch (NumberFormatException e) {
+                Slog.w(TAG, "Error reading subtypes: " + e);
+                return;
+            } finally {
+                if (fis != null) {
+                    try {
+                        fis.close();
+                    } catch (java.io.IOException e1) {
+                        Slog.w(TAG, "Failed to close.");
+                    }
+                }
+            }
+        }
+    }
+
+    @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 InputMethodManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        IInputMethod method;
+        ClientState client;
+
+        final Printer p = new PrintWriterPrinter(pw);
+
+        synchronized (mMethodMap) {
+            p.println("Current Input Method Manager state:");
+            int N = mMethodList.size();
+            p.println("  Input Methods:");
+            for (int i=0; i<N; i++) {
+                InputMethodInfo info = mMethodList.get(i);
+                p.println("  InputMethod #" + i + ":");
+                info.dump(p, "    ");
+            }
+            p.println("  Clients:");
+            for (ClientState ci : mClients.values()) {
+                p.println("  Client " + ci + ":");
+                p.println("    client=" + ci.client);
+                p.println("    inputContext=" + ci.inputContext);
+                p.println("    sessionRequested=" + ci.sessionRequested);
+                p.println("    curSession=" + ci.curSession);
+            }
+            p.println("  mCurMethodId=" + mCurMethodId);
+            client = mCurClient;
+            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
+            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
+            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
+                    + " mBoundToMethod=" + mBoundToMethod);
+            p.println("  mCurToken=" + mCurToken);
+            p.println("  mCurIntent=" + mCurIntent);
+            method = mCurMethod;
+            p.println("  mCurMethod=" + mCurMethod);
+            p.println("  mEnabledSession=" + mEnabledSession);
+            p.println("  mShowRequested=" + mShowRequested
+                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
+                    + " mShowForced=" + mShowForced
+                    + " mInputShown=" + mInputShown);
+            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
+        }
+
+        p.println(" ");
+        if (client != null) {
+            pw.flush();
+            try {
+                client.client.asBinder().dump(fd, args);
+            } catch (RemoteException e) {
+                p.println("Input method client dead: " + e);
+            }
+        } else {
+            p.println("No input method client.");
+        }
+
+        p.println(" ");
+        if (method != null) {
+            pw.flush();
+            try {
+                method.asBinder().dump(fd, args);
+            } catch (RemoteException e) {
+                p.println("Input method service dead: " + e);
+            }
+        } else {
+            p.println("No input method service.");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/IntentResolver.java b/services/core/java/com/android/server/IntentResolver.java
new file mode 100644
index 0000000..64b0487
--- /dev/null
+++ b/services/core/java/com/android/server/IntentResolver.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (C) 2006 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 java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.net.Uri;
+import android.util.FastImmutableArraySet;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.LogPrinter;
+import android.util.Printer;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import com.android.internal.util.FastPrintWriter;
+
+/**
+ * {@hide}
+ */
+public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
+    final private static String TAG = "IntentResolver";
+    final private static boolean DEBUG = false;
+    final private static boolean localLOGV = DEBUG || false;
+
+    public void addFilter(F f) {
+        if (localLOGV) {
+            Slog.v(TAG, "Adding filter: " + f);
+            f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "      ");
+            Slog.v(TAG, "    Building Lookup Maps:");
+        }
+
+        mFilters.add(f);
+        int numS = register_intent_filter(f, f.schemesIterator(),
+                mSchemeToFilter, "      Scheme: ");
+        int numT = register_mime_types(f, "      Type: ");
+        if (numS == 0 && numT == 0) {
+            register_intent_filter(f, f.actionsIterator(),
+                    mActionToFilter, "      Action: ");
+        }
+        if (numT != 0) {
+            register_intent_filter(f, f.actionsIterator(),
+                    mTypedActionToFilter, "      TypedAction: ");
+        }
+    }
+
+    public void removeFilter(F f) {
+        removeFilterInternal(f);
+        mFilters.remove(f);
+    }
+
+    void removeFilterInternal(F f) {
+        if (localLOGV) {
+            Slog.v(TAG, "Removing filter: " + f);
+            f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "      ");
+            Slog.v(TAG, "    Cleaning Lookup Maps:");
+        }
+
+        int numS = unregister_intent_filter(f, f.schemesIterator(),
+                mSchemeToFilter, "      Scheme: ");
+        int numT = unregister_mime_types(f, "      Type: ");
+        if (numS == 0 && numT == 0) {
+            unregister_intent_filter(f, f.actionsIterator(),
+                    mActionToFilter, "      Action: ");
+        }
+        if (numT != 0) {
+            unregister_intent_filter(f, f.actionsIterator(),
+                    mTypedActionToFilter, "      TypedAction: ");
+        }
+    }
+
+    boolean dumpMap(PrintWriter out, String titlePrefix, String title,
+            String prefix, Map<String, F[]> map, String packageName,
+            boolean printFilter) {
+        String eprefix = prefix + "  ";
+        String fprefix = prefix + "    ";
+        boolean printedSomething = false;
+        Printer printer = null;
+        for (Map.Entry<String, F[]> e : map.entrySet()) {
+            F[] a = e.getValue();
+            final int N = a.length;
+            boolean printedHeader = false;
+            F filter;
+            for (int i=0; i<N && (filter=a[i]) != null; i++) {
+                if (packageName != null && !isPackageForFilter(packageName, filter)) {
+                    continue;
+                }
+                if (title != null) {
+                    out.print(titlePrefix); out.println(title);
+                    title = null;
+                }
+                if (!printedHeader) {
+                    out.print(eprefix); out.print(e.getKey()); out.println(":");
+                    printedHeader = true;
+                }
+                printedSomething = true;
+                dumpFilter(out, fprefix, filter);
+                if (printFilter) {
+                    if (printer == null) {
+                        printer = new PrintWriterPrinter(out);
+                    }
+                    filter.dump(printer, fprefix + "  ");
+                }
+            }
+        }
+        return printedSomething;
+    }
+
+    public boolean dump(PrintWriter out, String title, String prefix, String packageName,
+            boolean printFilter) {
+        String innerPrefix = prefix + "  ";
+        String sepPrefix = "\n" + prefix;
+        String curPrefix = title + "\n" + prefix;
+        if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix,
+                mTypeToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix,
+                mBaseTypeToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix,
+                mWildTypeToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        if (dumpMap(out, curPrefix, "Schemes:", innerPrefix,
+                mSchemeToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix,
+                mActionToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix,
+                mTypedActionToFilter, packageName, printFilter)) {
+            curPrefix = sepPrefix;
+        }
+        return curPrefix == sepPrefix;
+    }
+
+    private class IteratorWrapper implements Iterator<F> {
+        private final Iterator<F> mI;
+        private F mCur;
+
+        IteratorWrapper(Iterator<F> it) {
+            mI = it;
+        }
+
+        public boolean hasNext() {
+            return mI.hasNext();
+        }
+
+        public F next() {
+            return (mCur = mI.next());
+        }
+
+        public void remove() {
+            if (mCur != null) {
+                removeFilterInternal(mCur);
+            }
+            mI.remove();
+        }
+
+    }
+
+    /**
+     * Returns an iterator allowing filters to be removed.
+     */
+    public Iterator<F> filterIterator() {
+        return new IteratorWrapper(mFilters.iterator());
+    }
+
+    /**
+     * Returns a read-only set of the filters.
+     */
+    public Set<F> filterSet() {
+        return Collections.unmodifiableSet(mFilters);
+    }
+
+    public List<R> queryIntentFromList(Intent intent, String resolvedType, 
+            boolean defaultOnly, ArrayList<F[]> listCut, int userId) {
+        ArrayList<R> resultList = new ArrayList<R>();
+
+        final boolean debug = localLOGV ||
+                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+        final String scheme = intent.getScheme();
+        int N = listCut.size();
+        for (int i = 0; i < N; ++i) {
+            buildResolveList(intent, categories, debug, defaultOnly,
+                    resolvedType, scheme, listCut.get(i), resultList, userId);
+        }
+        sortResults(resultList);
+        return resultList;
+    }
+
+    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
+            int userId) {
+        String scheme = intent.getScheme();
+
+        ArrayList<R> finalList = new ArrayList<R>();
+
+        final boolean debug = localLOGV ||
+                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+        if (debug) Slog.v(
+            TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
+            + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);
+
+        F[] firstTypeCut = null;
+        F[] secondTypeCut = null;
+        F[] thirdTypeCut = null;
+        F[] schemeCut = null;
+
+        // If the intent includes a MIME type, then we want to collect all of
+        // the filters that match that MIME type.
+        if (resolvedType != null) {
+            int slashpos = resolvedType.indexOf('/');
+            if (slashpos > 0) {
+                final String baseType = resolvedType.substring(0, slashpos);
+                if (!baseType.equals("*")) {
+                    if (resolvedType.length() != slashpos+2
+                            || resolvedType.charAt(slashpos+1) != '*') {
+                        // Not a wild card, so we can just look for all filters that
+                        // completely match or wildcards whose base type matches.
+                        firstTypeCut = mTypeToFilter.get(resolvedType);
+                        if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
+                        secondTypeCut = mWildTypeToFilter.get(baseType);
+                        if (debug) Slog.v(TAG, "Second type cut: "
+                                + Arrays.toString(secondTypeCut));
+                    } else {
+                        // We can match anything with our base type.
+                        firstTypeCut = mBaseTypeToFilter.get(baseType);
+                        if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
+                        secondTypeCut = mWildTypeToFilter.get(baseType);
+                        if (debug) Slog.v(TAG, "Second type cut: "
+                                + Arrays.toString(secondTypeCut));
+                    }
+                    // Any */* types always apply, but we only need to do this
+                    // if the intent type was not already */*.
+                    thirdTypeCut = mWildTypeToFilter.get("*");
+                    if (debug) Slog.v(TAG, "Third type cut: " + Arrays.toString(thirdTypeCut));
+                } else if (intent.getAction() != null) {
+                    // The intent specified any type ({@literal *}/*).  This
+                    // can be a whole heck of a lot of things, so as a first
+                    // cut let's use the action instead.
+                    firstTypeCut = mTypedActionToFilter.get(intent.getAction());
+                    if (debug) Slog.v(TAG, "Typed Action list: " + Arrays.toString(firstTypeCut));
+                }
+            }
+        }
+
+        // If the intent includes a data URI, then we want to collect all of
+        // the filters that match its scheme (we will further refine matches
+        // on the authority and path by directly matching each resulting filter).
+        if (scheme != null) {
+            schemeCut = mSchemeToFilter.get(scheme);
+            if (debug) Slog.v(TAG, "Scheme list: " + Arrays.toString(schemeCut));
+        }
+
+        // If the intent does not specify any data -- either a MIME type or
+        // a URI -- then we will only be looking for matches against empty
+        // data.
+        if (resolvedType == null && scheme == null && intent.getAction() != null) {
+            firstTypeCut = mActionToFilter.get(intent.getAction());
+            if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut));
+        }
+
+        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
+        if (firstTypeCut != null) {
+            buildResolveList(intent, categories, debug, defaultOnly,
+                    resolvedType, scheme, firstTypeCut, finalList, userId);
+        }
+        if (secondTypeCut != null) {
+            buildResolveList(intent, categories, debug, defaultOnly,
+                    resolvedType, scheme, secondTypeCut, finalList, userId);
+        }
+        if (thirdTypeCut != null) {
+            buildResolveList(intent, categories, debug, defaultOnly,
+                    resolvedType, scheme, thirdTypeCut, finalList, userId);
+        }
+        if (schemeCut != null) {
+            buildResolveList(intent, categories, debug, defaultOnly,
+                    resolvedType, scheme, schemeCut, finalList, userId);
+        }
+        sortResults(finalList);
+
+        if (debug) {
+            Slog.v(TAG, "Final result list:");
+            for (int i=0; i<finalList.size(); i++) {
+                Slog.v(TAG, "  " + finalList.get(i));
+            }
+        }
+        return finalList;
+    }
+
+    /**
+     * Control whether the given filter is allowed to go into the result
+     * list.  Mainly intended to prevent adding multiple filters for the
+     * same target object.
+     */
+    protected boolean allowFilterResult(F filter, List<R> dest) {
+        return true;
+    }
+
+    /**
+     * Returns whether the object associated with the given filter is
+     * "stopped," that is whether it should not be included in the result
+     * if the intent requests to excluded stopped objects.
+     */
+    protected boolean isFilterStopped(F filter, int userId) {
+        return false;
+    }
+
+    /**
+     * Returns whether this filter is owned by this package. This must be
+     * implemented to provide correct filtering of Intents that have
+     * specified a package name they are to be delivered to.
+     */
+    protected abstract boolean isPackageForFilter(String packageName, F filter);
+
+    protected abstract F[] newArray(int size);
+
+    @SuppressWarnings("unchecked")
+    protected R newResult(F filter, int match, int userId) {
+        return (R)filter;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected void sortResults(List<R> results) {
+        Collections.sort(results, mResolvePrioritySorter);
+    }
+
+    protected void dumpFilter(PrintWriter out, String prefix, F filter) {
+        out.print(prefix); out.println(filter);
+    }
+
+    private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {
+        F[] array = map.get(name);
+        if (array == null) {
+            array = newArray(2);
+            map.put(name,  array);
+            array[0] = filter;
+        } else {
+            final int N = array.length;
+            int i = N;
+            while (i > 0 && array[i-1] == null) {
+                i--;
+            }
+            if (i < N) {
+                array[i] = filter;
+            } else {
+                F[] newa = newArray((N*3)/2);
+                System.arraycopy(array, 0, newa, 0, N);
+                newa[N] = filter;
+                map.put(name, newa);
+            }
+        }
+    }
+
+    private final int register_mime_types(F filter, String prefix) {
+        final Iterator<String> i = filter.typesIterator();
+        if (i == null) {
+            return 0;
+        }
+
+        int num = 0;
+        while (i.hasNext()) {
+            String name = i.next();
+            num++;
+            if (localLOGV) Slog.v(TAG, prefix + name);
+            String baseName = name;
+            final int slashpos = name.indexOf('/');
+            if (slashpos > 0) {
+                baseName = name.substring(0, slashpos).intern();
+            } else {
+                name = name + "/*";
+            }
+
+            addFilter(mTypeToFilter, name, filter);
+
+            if (slashpos > 0) {
+                addFilter(mBaseTypeToFilter, baseName, filter);
+            } else {
+                addFilter(mWildTypeToFilter, baseName, filter);
+            }
+        }
+
+        return num;
+    }
+
+    private final int unregister_mime_types(F filter, String prefix) {
+        final Iterator<String> i = filter.typesIterator();
+        if (i == null) {
+            return 0;
+        }
+
+        int num = 0;
+        while (i.hasNext()) {
+            String name = i.next();
+            num++;
+            if (localLOGV) Slog.v(TAG, prefix + name);
+            String baseName = name;
+            final int slashpos = name.indexOf('/');
+            if (slashpos > 0) {
+                baseName = name.substring(0, slashpos).intern();
+            } else {
+                name = name + "/*";
+            }
+
+            remove_all_objects(mTypeToFilter, name, filter);
+
+            if (slashpos > 0) {
+                remove_all_objects(mBaseTypeToFilter, baseName, filter);
+            } else {
+                remove_all_objects(mWildTypeToFilter, baseName, filter);
+            }
+        }
+        return num;
+    }
+
+    private final int register_intent_filter(F filter, Iterator<String> i,
+            ArrayMap<String, F[]> dest, String prefix) {
+        if (i == null) {
+            return 0;
+        }
+
+        int num = 0;
+        while (i.hasNext()) {
+            String name = i.next();
+            num++;
+            if (localLOGV) Slog.v(TAG, prefix + name);
+            addFilter(dest, name, filter);
+        }
+        return num;
+    }
+
+    private final int unregister_intent_filter(F filter, Iterator<String> i,
+            ArrayMap<String, F[]> dest, String prefix) {
+        if (i == null) {
+            return 0;
+        }
+
+        int num = 0;
+        while (i.hasNext()) {
+            String name = i.next();
+            num++;
+            if (localLOGV) Slog.v(TAG, prefix + name);
+            remove_all_objects(dest, name, filter);
+        }
+        return num;
+    }
+
+    private final void remove_all_objects(ArrayMap<String, F[]> map, String name,
+            Object object) {
+        F[] array = map.get(name);
+        if (array != null) {
+            int LAST = array.length-1;
+            while (LAST >= 0 && array[LAST] == null) {
+                LAST--;
+            }
+            for (int idx=LAST; idx>=0; idx--) {
+                if (array[idx] == object) {
+                    final int remain = LAST - idx;
+                    if (remain > 0) {
+                        System.arraycopy(array, idx+1, array, idx, remain);
+                    }
+                    array[LAST] = null;
+                    LAST--;
+                }
+            }
+            if (LAST < 0) {
+                map.remove(name);
+            } else if (LAST < (array.length/2)) {
+                F[] newa = newArray(LAST+2);
+                System.arraycopy(array, 0, newa, 0, LAST+1);
+                map.put(name, newa);
+            }
+        }
+    }
+
+    private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) {
+        final Set<String> categories = intent.getCategories();
+        if (categories == null) {
+            return null;
+        }
+        return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()]));
+    }
+
+    private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories,
+            boolean debug, boolean defaultOnly,
+            String resolvedType, String scheme, F[] src, List<R> dest, int userId) {
+        final String action = intent.getAction();
+        final Uri data = intent.getData();
+        final String packageName = intent.getPackage();
+
+        final boolean excludingStopped = intent.isExcludingStopped();
+
+        final Printer logPrinter;
+        final PrintWriter logPrintWriter;
+        if (debug) {
+            logPrinter = new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM);
+            logPrintWriter = new FastPrintWriter(logPrinter);
+        } else {
+            logPrinter = null;
+            logPrintWriter = null;
+        }
+
+        final int N = src != null ? src.length : 0;
+        boolean hasNonDefaults = false;
+        int i;
+        F filter;
+        for (i=0; i<N && (filter=src[i]) != null; i++) {
+            int match;
+            if (debug) Slog.v(TAG, "Matching against filter " + filter);
+
+            if (excludingStopped && isFilterStopped(filter, userId)) {
+                if (debug) {
+                    Slog.v(TAG, "  Filter's target is stopped; skipping");
+                }
+                continue;
+            }
+
+            // Is delivery being limited to filters owned by a particular package?
+            if (packageName != null && !isPackageForFilter(packageName, filter)) {
+                if (debug) {
+                    Slog.v(TAG, "  Filter is not from package " + packageName + "; skipping");
+                }
+                continue;
+            }
+
+            // Do we already have this one?
+            if (!allowFilterResult(filter, dest)) {
+                if (debug) {
+                    Slog.v(TAG, "  Filter's target already added");
+                }
+                continue;
+            }
+
+            match = filter.match(action, resolvedType, scheme, data, categories, TAG);
+            if (match >= 0) {
+                if (debug) Slog.v(TAG, "  Filter matched!  match=0x" +
+                        Integer.toHexString(match) + " hasDefault="
+                        + filter.hasCategory(Intent.CATEGORY_DEFAULT));
+                if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) {
+                    final R oneResult = newResult(filter, match, userId);
+                    if (oneResult != null) {
+                        dest.add(oneResult);
+                        if (debug) {
+                            dumpFilter(logPrintWriter, "    ", filter);
+                            logPrintWriter.flush();
+                            filter.dump(logPrinter, "    ");
+                        }
+                    }
+                } else {
+                    hasNonDefaults = true;
+                }
+            } else {
+                if (debug) {
+                    String reason;
+                    switch (match) {
+                        case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
+                        case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
+                        case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
+                        case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
+                        default: reason = "unknown reason"; break;
+                    }
+                    Slog.v(TAG, "  Filter did not match: " + reason);
+                }
+            }
+        }
+
+        if (hasNonDefaults) {
+            if (dest.size() == 0) {
+                Slog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT");
+            } else if (dest.size() > 1) {
+                Slog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT");
+            }
+        }
+    }
+
+    // Sorts a List of IntentFilter objects into descending priority order.
+    @SuppressWarnings("rawtypes")
+    private static final Comparator mResolvePrioritySorter = new Comparator() {
+        public int compare(Object o1, Object o2) {
+            final int q1 = ((IntentFilter) o1).getPriority();
+            final int q2 = ((IntentFilter) o2).getPriority();
+            return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0);
+        }
+    };
+
+    /**
+     * All filters that have been registered.
+     */
+    private final HashSet<F> mFilters = new HashSet<F>();
+
+    /**
+     * All of the MIME types that have been registered, such as "image/jpeg",
+     * "image/*", or "{@literal *}/*".
+     */
+    private final ArrayMap<String, F[]> mTypeToFilter = new ArrayMap<String, F[]>();
+
+    /**
+     * The base names of all of all fully qualified MIME types that have been
+     * registered, such as "image" or "*".  Wild card MIME types such as
+     * "image/*" will not be here.
+     */
+    private final ArrayMap<String, F[]> mBaseTypeToFilter = new ArrayMap<String, F[]>();
+
+    /**
+     * The base names of all of the MIME types with a sub-type wildcard that
+     * have been registered.  For example, a filter with "image/*" will be
+     * included here as "image" but one with "image/jpeg" will not be
+     * included here.  This also includes the "*" for the "{@literal *}/*"
+     * MIME type.
+     */
+    private final ArrayMap<String, F[]> mWildTypeToFilter = new ArrayMap<String, F[]>();
+
+    /**
+     * All of the URI schemes (such as http) that have been registered.
+     */
+    private final ArrayMap<String, F[]> mSchemeToFilter = new ArrayMap<String, F[]>();
+
+    /**
+     * All of the actions that have been registered, but only those that did
+     * not specify data.
+     */
+    private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();
+
+    /**
+     * All of the actions that have been registered and specified a MIME type.
+     */
+    private final ArrayMap<String, F[]> mTypedActionToFilter = new ArrayMap<String, F[]>();
+}
diff --git a/services/core/java/com/android/server/IoThread.java b/services/core/java/com/android/server/IoThread.java
new file mode 100644
index 0000000..09f2af7
--- /dev/null
+++ b/services/core/java/com/android/server/IoThread.java
@@ -0,0 +1,62 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * Shared singleton I/O thread for the system.  This is a thread for non-background
+ * service operations that can potential block briefly on network IO operations
+ * (not waiting for data itself, but communicating with network daemons).
+ */
+public final class IoThread extends HandlerThread {
+    private static IoThread sInstance;
+    private static Handler sHandler;
+
+    private IoThread() {
+        super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new IoThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    android.os.Process.setCanSelfBackground(false);
+                }
+            });
+        }
+    }
+
+    public static IoThread get() {
+        synchronized (IoThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (IoThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/LocalServices.java b/services/core/java/com/android/server/LocalServices.java
new file mode 100644
index 0000000..deff79dc
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
new file mode 100644
index 0000000..8f480dd
--- /dev/null
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -0,0 +1,2411 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.location.Address;
+import android.location.Criteria;
+import android.location.GeocoderParams;
+import android.location.Geofence;
+import android.location.IGpsStatusListener;
+import android.location.IGpsStatusProvider;
+import android.location.ILocationListener;
+import android.location.ILocationManager;
+import android.location.INetInitiatedListener;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationRequest;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.os.BackgroundThread;
+import com.android.server.location.FlpHardwareProvider;
+import com.android.server.location.FusedProxy;
+import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GeofenceProxy;
+import com.android.server.location.GeofenceManager;
+import com.android.server.location.GpsLocationProvider;
+import com.android.server.location.LocationBlacklist;
+import com.android.server.location.LocationFudger;
+import com.android.server.location.LocationProviderInterface;
+import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.MockProvider;
+import com.android.server.location.PassiveProvider;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The service class that manages LocationProviders and issues location
+ * updates and alerts.
+ */
+public class LocationManagerService extends ILocationManager.Stub {
+    private static final String TAG = "LocationManagerService";
+    public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final String WAKELOCK_KEY = TAG;
+
+    // Location resolution level: no location data whatsoever
+    private static final int RESOLUTION_LEVEL_NONE = 0;
+    // Location resolution level: coarse location data only
+    private static final int RESOLUTION_LEVEL_COARSE = 1;
+    // Location resolution level: fine location data
+    private static final int RESOLUTION_LEVEL_FINE = 2;
+
+    private static final String ACCESS_MOCK_LOCATION =
+            android.Manifest.permission.ACCESS_MOCK_LOCATION;
+    private static final String ACCESS_LOCATION_EXTRA_COMMANDS =
+            android.Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS;
+    private static final String INSTALL_LOCATION_PROVIDER =
+            android.Manifest.permission.INSTALL_LOCATION_PROVIDER;
+
+    private static final String NETWORK_LOCATION_SERVICE_ACTION =
+            "com.android.location.service.v3.NetworkLocationProvider";
+    private static final String FUSED_LOCATION_SERVICE_ACTION =
+            "com.android.location.service.FusedLocationProvider";
+
+    private static final int MSG_LOCATION_CHANGED = 1;
+
+    private static final long NANOS_PER_MILLI = 1000000L;
+
+    // The maximum interval a location request can have and still be considered "high power".
+    private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000;
+
+    // Location Providers may sometimes deliver location updates
+    // slightly faster that requested - provide grace period so
+    // we don't unnecessarily filter events that are otherwise on
+    // time
+    private static final int MAX_PROVIDER_SCHEDULING_JITTER_MS = 100;
+
+    private static final LocationRequest DEFAULT_LOCATION_REQUEST = new LocationRequest();
+
+    private final Context mContext;
+    private final AppOpsManager mAppOps;
+
+    // used internally for synchronization
+    private final Object mLock = new Object();
+
+    // --- fields below are final after systemReady() ---
+    private LocationFudger mLocationFudger;
+    private GeofenceManager mGeofenceManager;
+    private PackageManager mPackageManager;
+    private PowerManager mPowerManager;
+    private GeocoderProxy mGeocodeProvider;
+    private IGpsStatusProvider mGpsStatusProvider;
+    private INetInitiatedListener mNetInitiatedListener;
+    private LocationWorkerHandler mLocationHandler;
+    private PassiveProvider mPassiveProvider;  // track passive provider for special cases
+    private LocationBlacklist mBlacklist;
+
+    // --- fields below are protected by mLock ---
+    // Set of providers that are explicitly enabled
+    private final Set<String> mEnabledProviders = new HashSet<String>();
+
+    // Set of providers that are explicitly disabled
+    private final Set<String> mDisabledProviders = new HashSet<String>();
+
+    // Mock (test) providers
+    private final HashMap<String, MockProvider> mMockProviders =
+            new HashMap<String, MockProvider>();
+
+    // all receivers
+    private final HashMap<Object, Receiver> mReceivers = new HashMap<Object, Receiver>();
+
+    // currently installed providers (with mocks replacing real providers)
+    private final ArrayList<LocationProviderInterface> mProviders =
+            new ArrayList<LocationProviderInterface>();
+
+    // real providers, saved here when mocked out
+    private final HashMap<String, LocationProviderInterface> mRealProviders =
+            new HashMap<String, LocationProviderInterface>();
+
+    // mapping from provider name to provider
+    private final HashMap<String, LocationProviderInterface> mProvidersByName =
+            new HashMap<String, LocationProviderInterface>();
+
+    // mapping from provider name to all its UpdateRecords
+    private final HashMap<String, ArrayList<UpdateRecord>> mRecordsByProvider =
+            new HashMap<String, ArrayList<UpdateRecord>>();
+
+    // mapping from provider name to last known location
+    private final HashMap<String, Location> mLastLocation = new HashMap<String, Location>();
+
+    // same as mLastLocation, but is not updated faster than LocationFudger.FASTEST_INTERVAL_MS.
+    // locations stored here are not fudged for coarse permissions.
+    private final HashMap<String, Location> mLastLocationCoarseInterval =
+            new HashMap<String, Location>();
+
+    // all providers that operate over proxy, for authorizing incoming location
+    private final ArrayList<LocationProviderProxy> mProxyProviders =
+            new ArrayList<LocationProviderProxy>();
+
+    // current active user on the device - other users are denied location data
+    private int mCurrentUserId = UserHandle.USER_OWNER;
+
+    public LocationManagerService(Context context) {
+        super();
+        mContext = context;
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+        if (D) Log.d(TAG, "Constructed");
+
+        // most startup is deferred until systemReady()
+    }
+
+    public void systemRunning() {
+        synchronized (mLock) {
+            if (D) Log.d(TAG, "systemReady()");
+
+            // fetch package manager
+            mPackageManager = mContext.getPackageManager();
+
+            // fetch power manager
+            mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
+            // prepare worker thread
+            mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
+
+            // prepare mLocationHandler's dependents
+            mLocationFudger = new LocationFudger(mContext, mLocationHandler);
+            mBlacklist = new LocationBlacklist(mContext, mLocationHandler);
+            mBlacklist.init();
+            mGeofenceManager = new GeofenceManager(mContext, mBlacklist);
+
+            // Monitor for app ops mode changes.
+            AppOpsManager.OnOpChangedListener callback
+                    = new AppOpsManager.OnOpChangedInternalListener() {
+                public void onOpChanged(int op, String packageName) {
+                    synchronized (mLock) {
+                        for (Receiver receiver : mReceivers.values()) {
+                            receiver.updateMonitoring(true);
+                        }
+                        applyAllProviderRequirementsLocked();
+                    }
+                }
+            };
+            mAppOps.startWatchingMode(AppOpsManager.OP_COARSE_LOCATION, null, callback);
+
+            // prepare providers
+            loadProvidersLocked();
+            updateProvidersLocked();
+        }
+
+        // listen for settings changes
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.LOCATION_PROVIDERS_ALLOWED), true,
+                new ContentObserver(mLocationHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        synchronized (mLock) {
+                            updateProvidersLocked();
+                        }
+                    }
+                }, UserHandle.USER_ALL);
+        mPackageMonitor.register(mContext, mLocationHandler.getLooper(), true);
+
+        // listen for user change
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, mLocationHandler);
+    }
+
+    private void ensureFallbackFusedProviderPresentLocked(ArrayList<String> pkgs) {
+        PackageManager pm = mContext.getPackageManager();
+        String systemPackageName = mContext.getPackageName();
+        ArrayList<HashSet<Signature>> sigSets = ServiceWatcher.getSignatureSets(mContext, pkgs);
+
+        List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
+                new Intent(FUSED_LOCATION_SERVICE_ACTION),
+                PackageManager.GET_META_DATA, mCurrentUserId);
+        for (ResolveInfo rInfo : rInfos) {
+            String packageName = rInfo.serviceInfo.packageName;
+
+            // Check that the signature is in the list of supported sigs. If it's not in
+            // this list the standard provider binding logic won't bind to it.
+            try {
+                PackageInfo pInfo;
+                pInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+                if (!ServiceWatcher.isSignatureMatch(pInfo.signatures, sigSets)) {
+                    Log.w(TAG, packageName + " resolves service " + FUSED_LOCATION_SERVICE_ACTION +
+                            ", but has wrong signature, ignoring");
+                    continue;
+                }
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "missing package: " + packageName);
+                continue;
+            }
+
+            // Get the version info
+            if (rInfo.serviceInfo.metaData == null) {
+                Log.w(TAG, "Found fused provider without metadata: " + packageName);
+                continue;
+            }
+
+            int version = rInfo.serviceInfo.metaData.getInt(
+                    ServiceWatcher.EXTRA_SERVICE_VERSION, -1);
+            if (version == 0) {
+                // This should be the fallback fused location provider.
+
+                // Make sure it's in the system partition.
+                if ((rInfo.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    if (D) Log.d(TAG, "Fallback candidate not in /system: " + packageName);
+                    continue;
+                }
+
+                // Check that the fallback is signed the same as the OS
+                // as a proxy for coreApp="true"
+                if (pm.checkSignatures(systemPackageName, packageName)
+                        != PackageManager.SIGNATURE_MATCH) {
+                    if (D) Log.d(TAG, "Fallback candidate not signed the same as system: "
+                            + packageName);
+                    continue;
+                }
+
+                // Found a valid fallback.
+                if (D) Log.d(TAG, "Found fallback provider: " + packageName);
+                return;
+            } else {
+                if (D) Log.d(TAG, "Fallback candidate not version 0: " + packageName);
+            }
+        }
+
+        throw new IllegalStateException("Unable to find a fused location provider that is in the "
+                + "system partition with version 0 and signed with the platform certificate. "
+                + "Such a package is needed to provide a default fused location provider in the "
+                + "event that no other fused location provider has been installed or is currently "
+                + "available. For example, coreOnly boot mode when decrypting the data "
+                + "partition. The fallback must also be marked coreApp=\"true\" in the manifest");
+    }
+
+    private void loadProvidersLocked() {
+        // create a passive location provider, which is always enabled
+        PassiveProvider passiveProvider = new PassiveProvider(this);
+        addProviderLocked(passiveProvider);
+        mEnabledProviders.add(passiveProvider.getName());
+        mPassiveProvider = passiveProvider;
+        // Create a gps location provider
+        GpsLocationProvider gpsProvider = new GpsLocationProvider(mContext, this,
+                mLocationHandler.getLooper());
+
+        if (GpsLocationProvider.isSupported()) {
+            mGpsStatusProvider = gpsProvider.getGpsStatusProvider();
+            mNetInitiatedListener = gpsProvider.getNetInitiatedListener();
+            addProviderLocked(gpsProvider);
+            mRealProviders.put(LocationManager.GPS_PROVIDER, gpsProvider);
+        }
+
+        /*
+        Load package name(s) containing location provider support.
+        These packages can contain services implementing location providers:
+        Geocoder Provider, Network Location Provider, and
+        Fused Location Provider. They will each be searched for
+        service components implementing these providers.
+        The location framework also has support for installation
+        of new location providers at run-time. The new package does not
+        have to be explicitly listed here, however it must have a signature
+        that matches the signature of at least one package on this list.
+        */
+        Resources resources = mContext.getResources();
+        ArrayList<String> providerPackageNames = new ArrayList<String>();
+        String[] pkgs = resources.getStringArray(
+                com.android.internal.R.array.config_locationProviderPackageNames);
+        if (D) Log.d(TAG, "certificates for location providers pulled from: " +
+                Arrays.toString(pkgs));
+        if (pkgs != null) providerPackageNames.addAll(Arrays.asList(pkgs));
+
+        ensureFallbackFusedProviderPresentLocked(providerPackageNames);
+
+        // bind to network provider
+        LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
+                mContext,
+                LocationManager.NETWORK_PROVIDER,
+                NETWORK_LOCATION_SERVICE_ACTION,
+                com.android.internal.R.bool.config_enableNetworkLocationOverlay,
+                com.android.internal.R.string.config_networkLocationProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler);
+        if (networkProvider != null) {
+            mRealProviders.put(LocationManager.NETWORK_PROVIDER, networkProvider);
+            mProxyProviders.add(networkProvider);
+            addProviderLocked(networkProvider);
+        } else {
+            Slog.w(TAG,  "no network location provider found");
+        }
+
+        // bind to fused provider
+        LocationProviderProxy fusedLocationProvider = LocationProviderProxy.createAndBind(
+                mContext,
+                LocationManager.FUSED_PROVIDER,
+                FUSED_LOCATION_SERVICE_ACTION,
+                com.android.internal.R.bool.config_enableFusedLocationOverlay,
+                com.android.internal.R.string.config_fusedLocationProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler);
+        if (fusedLocationProvider != null) {
+            addProviderLocked(fusedLocationProvider);
+            mProxyProviders.add(fusedLocationProvider);
+            mEnabledProviders.add(fusedLocationProvider.getName());
+            mRealProviders.put(LocationManager.FUSED_PROVIDER, fusedLocationProvider);
+        } else {
+            Slog.e(TAG, "no fused location provider found",
+                    new IllegalStateException("Location service needs a fused location provider"));
+        }
+
+        // bind to geocoder provider
+        mGeocodeProvider = GeocoderProxy.createAndBind(mContext,
+                com.android.internal.R.bool.config_enableGeocoderOverlay,
+                com.android.internal.R.string.config_geocoderProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler);
+        if (mGeocodeProvider == null) {
+            Slog.e(TAG,  "no geocoder provider found");
+        }
+
+        // bind to fused provider
+        FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext);
+        FusedProxy fusedProxy = FusedProxy.createAndBind(
+                mContext,
+                mLocationHandler,
+                flpHardwareProvider.getLocationHardware(),
+                com.android.internal.R.bool.config_enableFusedLocationOverlay,
+                com.android.internal.R.string.config_fusedLocationProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames);
+        if(fusedProxy == null) {
+            Slog.e(TAG, "No FusedProvider found.");
+        }
+
+        // bind to geofence provider
+        GeofenceProxy provider = GeofenceProxy.createAndBind(mContext,
+                com.android.internal.R.bool.config_enableGeofenceOverlay,
+                com.android.internal.R.string.config_geofenceProviderPackageName,
+                com.android.internal.R.array.config_locationProviderPackageNames,
+                mLocationHandler,
+                gpsProvider.getGpsGeofenceProxy(),
+                flpHardwareProvider.getGeofenceHardware());
+        if (provider == null) {
+            Slog.e(TAG,  "no geofence provider found");
+        }
+    }
+
+    /**
+     * Called when the device's active user changes.
+     * @param userId the new active user's UserId
+     */
+    private void switchUser(int userId) {
+        mBlacklist.switchUser(userId);
+        mLocationHandler.removeMessages(MSG_LOCATION_CHANGED);
+        synchronized (mLock) {
+            mLastLocation.clear();
+            mLastLocationCoarseInterval.clear();
+            for (LocationProviderInterface p : mProviders) {
+                updateProviderListenersLocked(p.getName(), false, mCurrentUserId);
+            }
+            mCurrentUserId = userId;
+            updateProvidersLocked();
+        }
+    }
+
+    /**
+     * A wrapper class holding either an ILocationListener or a PendingIntent to receive
+     * location updates.
+     */
+    private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished {
+        final int mUid;  // uid of receiver
+        final int mPid;  // pid of receiver
+        final String mPackageName;  // package name of receiver
+        final int mAllowedResolutionLevel;  // resolution level allowed to receiver
+
+        final ILocationListener mListener;
+        final PendingIntent mPendingIntent;
+        final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
+        final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
+        final Object mKey;
+
+        final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+
+        // True if app ops has started monitoring this receiver for locations.
+        boolean mOpMonitoring;
+        // True if app ops has started monitoring this receiver for high power (gps) locations.
+        boolean mOpHighPowerMonitoring;
+        int mPendingBroadcasts;
+        PowerManager.WakeLock mWakeLock;
+
+        Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
+                String packageName, WorkSource workSource, boolean hideFromAppOps) {
+            mListener = listener;
+            mPendingIntent = intent;
+            if (listener != null) {
+                mKey = listener.asBinder();
+            } else {
+                mKey = intent;
+            }
+            mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
+            mUid = uid;
+            mPid = pid;
+            mPackageName = packageName;
+            if (workSource != null && workSource.size() <= 0) {
+                workSource = null;
+            }
+            mWorkSource = workSource;
+            mHideFromAppOps = hideFromAppOps;
+
+            updateMonitoring(true);
+
+            // construct/configure wakelock
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+            if (workSource == null) {
+                workSource = new WorkSource(mUid, mPackageName);
+            }
+            mWakeLock.setWorkSource(workSource);
+        }
+
+        @Override
+        public boolean equals(Object otherObj) {
+            if (otherObj instanceof Receiver) {
+                return mKey.equals(((Receiver)otherObj).mKey);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return mKey.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder s = new StringBuilder();
+            s.append("Reciever[");
+            s.append(Integer.toHexString(System.identityHashCode(this)));
+            if (mListener != null) {
+                s.append(" listener");
+            } else {
+                s.append(" intent");
+            }
+            for (String p : mUpdateRecords.keySet()) {
+                s.append(" ").append(mUpdateRecords.get(p).toString());
+            }
+            s.append("]");
+            return s.toString();
+        }
+
+        /**
+         * Update AppOp monitoring for this receiver.
+         *
+         * @param allow If true receiver is currently active, if false it's been removed.
+         */
+        public void updateMonitoring(boolean allow) {
+            if (mHideFromAppOps) {
+                return;
+            }
+
+            boolean requestingLocation = false;
+            boolean requestingHighPowerLocation = false;
+            if (allow) {
+                // See if receiver has any enabled update records.  Also note if any update records
+                // are high power (has a high power provider with an interval under a threshold).
+                for (UpdateRecord updateRecord : mUpdateRecords.values()) {
+                    if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) {
+                        requestingLocation = true;
+                        LocationProviderInterface locationProvider
+                            = mProvidersByName.get(updateRecord.mProvider);
+                        ProviderProperties properties = locationProvider != null
+                                ? locationProvider.getProperties() : null;
+                        if (properties != null
+                                && properties.mPowerRequirement == Criteria.POWER_HIGH
+                                && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
+                            requestingHighPowerLocation = true;
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // First update monitoring of any location request (including high power).
+            mOpMonitoring = updateMonitoring(
+                    requestingLocation,
+                    mOpMonitoring,
+                    AppOpsManager.OP_MONITOR_LOCATION);
+
+            // Now update monitoring of high power requests only.
+            boolean wasHighPowerMonitoring = mOpHighPowerMonitoring;
+            mOpHighPowerMonitoring = updateMonitoring(
+                    requestingHighPowerLocation,
+                    mOpHighPowerMonitoring,
+                    AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION);
+            if (mOpHighPowerMonitoring != wasHighPowerMonitoring) {
+                // Send an intent to notify that a high power request has been added/removed.
+                Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        }
+
+        /**
+         * Update AppOps monitoring for a single location request and op type.
+         *
+         * @param allowMonitoring True if monitoring is allowed for this request/op.
+         * @param currentlyMonitoring True if AppOps is currently monitoring this request/op.
+         * @param op AppOps code for the op to update.
+         * @return True if monitoring is on for this request/op after updating.
+         */
+        private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring,
+                int op) {
+            if (!currentlyMonitoring) {
+                if (allowMonitoring) {
+                    return mAppOps.startOpNoThrow(op, mUid, mPackageName)
+                            == AppOpsManager.MODE_ALLOWED;
+                }
+            } else {
+                if (!allowMonitoring || mAppOps.checkOpNoThrow(op, mUid, mPackageName)
+                        != AppOpsManager.MODE_ALLOWED) {
+                    mAppOps.finishOp(op, mUid, mPackageName);
+                    return false;
+                }
+            }
+
+            return currentlyMonitoring;
+        }
+
+        public boolean isListener() {
+            return mListener != null;
+        }
+
+        public boolean isPendingIntent() {
+            return mPendingIntent != null;
+        }
+
+        public ILocationListener getListener() {
+            if (mListener != null) {
+                return mListener;
+            }
+            throw new IllegalStateException("Request for non-existent listener");
+        }
+
+        public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
+            if (mListener != null) {
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mListener.onStatusChanged(provider, status, extras);
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (RemoteException e) {
+                    return false;
+                }
+            } else {
+                Intent statusChanged = new Intent();
+                statusChanged.putExtras(new Bundle(extras));
+                statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
+                                getResolutionPermission(mAllowedResolutionLevel));
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (PendingIntent.CanceledException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public boolean callLocationChangedLocked(Location location) {
+            if (mListener != null) {
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mListener.onLocationChanged(new Location(location));
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (RemoteException e) {
+                    return false;
+                }
+            } else {
+                Intent locationChanged = new Intent();
+                locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, new Location(location));
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
+                                getResolutionPermission(mAllowedResolutionLevel));
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (PendingIntent.CanceledException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public boolean callProviderEnabledLocked(String provider, boolean enabled) {
+            // First update AppOp monitoring.
+            // An app may get/lose location access as providers are enabled/disabled.
+            updateMonitoring(true);
+
+            if (mListener != null) {
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        if (enabled) {
+                            mListener.onProviderEnabled(provider);
+                        } else {
+                            mListener.onProviderDisabled(provider);
+                        }
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (RemoteException e) {
+                    return false;
+                }
+            } else {
+                Intent providerIntent = new Intent();
+                providerIntent.putExtra(LocationManager.KEY_PROVIDER_ENABLED, enabled);
+                try {
+                    synchronized (this) {
+                        // synchronize to ensure incrementPendingBroadcastsLocked()
+                        // is called before decrementPendingBroadcasts()
+                        mPendingIntent.send(mContext, 0, providerIntent, this, mLocationHandler,
+                                getResolutionPermission(mAllowedResolutionLevel));
+                        // call this after broadcasting so we do not increment
+                        // if we throw an exeption.
+                        incrementPendingBroadcastsLocked();
+                    }
+                } catch (PendingIntent.CanceledException e) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public void binderDied() {
+            if (D) Log.d(TAG, "Location listener died");
+
+            synchronized (mLock) {
+                removeUpdatesLocked(this);
+            }
+            synchronized (this) {
+                clearPendingBroadcastsLocked();
+            }
+        }
+
+        @Override
+        public void onSendFinished(PendingIntent pendingIntent, Intent intent,
+                int resultCode, String resultData, Bundle resultExtras) {
+            synchronized (this) {
+                decrementPendingBroadcastsLocked();
+            }
+        }
+
+        // this must be called while synchronized by caller in a synchronized block
+        // containing the sending of the broadcaset
+        private void incrementPendingBroadcastsLocked() {
+            if (mPendingBroadcasts++ == 0) {
+                mWakeLock.acquire();
+            }
+        }
+
+        private void decrementPendingBroadcastsLocked() {
+            if (--mPendingBroadcasts == 0) {
+                if (mWakeLock.isHeld()) {
+                    mWakeLock.release();
+                }
+            }
+        }
+
+        public void clearPendingBroadcastsLocked() {
+            if (mPendingBroadcasts > 0) {
+                mPendingBroadcasts = 0;
+                if (mWakeLock.isHeld()) {
+                    mWakeLock.release();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void locationCallbackFinished(ILocationListener listener) {
+        //Do not use getReceiverLocked here as that will add the ILocationListener to
+        //the receiver list if it is not found.  If it is not found then the
+        //LocationListener was removed when it had a pending broadcast and should
+        //not be added back.
+        synchronized (mLock) {
+            IBinder binder = listener.asBinder();
+            Receiver receiver = mReceivers.get(binder);
+            if (receiver != null) {
+                synchronized (receiver) {
+                    // so wakelock calls will succeed
+                    long identity = Binder.clearCallingIdentity();
+                    receiver.decrementPendingBroadcastsLocked();
+                    Binder.restoreCallingIdentity(identity);
+               }
+            }
+        }
+    }
+
+    private void addProviderLocked(LocationProviderInterface provider) {
+        mProviders.add(provider);
+        mProvidersByName.put(provider.getName(), provider);
+    }
+
+    private void removeProviderLocked(LocationProviderInterface provider) {
+        provider.disable();
+        mProviders.remove(provider);
+        mProvidersByName.remove(provider.getName());
+    }
+
+    /**
+     * Returns "true" if access to the specified location provider is allowed by the current
+     * user's settings. Access to all location providers is forbidden to non-location-provider
+     * processes belonging to background users.
+     *
+     * @param provider the name of the location provider
+     * @return
+     */
+    private boolean isAllowedByCurrentUserSettingsLocked(String provider) {
+        if (mEnabledProviders.contains(provider)) {
+            return true;
+        }
+        if (mDisabledProviders.contains(provider)) {
+            return false;
+        }
+        // Use system settings
+        ContentResolver resolver = mContext.getContentResolver();
+
+        return Settings.Secure.isLocationProviderEnabledForUser(resolver, provider, mCurrentUserId);
+    }
+
+    /**
+     * Returns "true" if access to the specified location provider is allowed by the specified
+     * user's settings. Access to all location providers is forbidden to non-location-provider
+     * processes belonging to background users.
+     *
+     * @param provider the name of the location provider
+     * @param uid the requestor's UID
+     * @return
+     */
+    private boolean isAllowedByUserSettingsLocked(String provider, int uid) {
+        if (UserHandle.getUserId(uid) != mCurrentUserId && !isUidALocationProvider(uid)) {
+            return false;
+        }
+        return isAllowedByCurrentUserSettingsLocked(provider);
+    }
+
+    /**
+     * Returns the permission string associated with the specified resolution level.
+     *
+     * @param resolutionLevel the resolution level
+     * @return the permission string
+     */
+    private String getResolutionPermission(int resolutionLevel) {
+        switch (resolutionLevel) {
+            case RESOLUTION_LEVEL_FINE:
+                return android.Manifest.permission.ACCESS_FINE_LOCATION;
+            case RESOLUTION_LEVEL_COARSE:
+                return android.Manifest.permission.ACCESS_COARSE_LOCATION;
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * Returns the resolution level allowed to the given PID/UID pair.
+     *
+     * @param pid the PID
+     * @param uid the UID
+     * @return resolution level allowed to the pid/uid pair
+     */
+    private int getAllowedResolutionLevel(int pid, int uid) {
+        if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
+                pid, uid) == PackageManager.PERMISSION_GRANTED) {
+            return RESOLUTION_LEVEL_FINE;
+        } else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+                pid, uid) == PackageManager.PERMISSION_GRANTED) {
+            return RESOLUTION_LEVEL_COARSE;
+        } else {
+            return RESOLUTION_LEVEL_NONE;
+        }
+    }
+
+    /**
+     * Returns the resolution level allowed to the caller
+     *
+     * @return resolution level allowed to caller
+     */
+    private int getCallerAllowedResolutionLevel() {
+        return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
+    }
+
+    /**
+     * Throw SecurityException if specified resolution level is insufficient to use geofences.
+     *
+     * @param allowedResolutionLevel resolution level allowed to caller
+     */
+    private void checkResolutionLevelIsSufficientForGeofenceUse(int allowedResolutionLevel) {
+        if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+            throw new SecurityException("Geofence usage requires ACCESS_FINE_LOCATION permission");
+        }
+    }
+
+    /**
+     * Return the minimum resolution level required to use the specified location provider.
+     *
+     * @param provider the name of the location provider
+     * @return minimum resolution level required for provider
+     */
+    private int getMinimumResolutionLevelForProviderUse(String provider) {
+        if (LocationManager.GPS_PROVIDER.equals(provider) ||
+                LocationManager.PASSIVE_PROVIDER.equals(provider)) {
+            // gps and passive providers require FINE permission
+            return RESOLUTION_LEVEL_FINE;
+        } else if (LocationManager.NETWORK_PROVIDER.equals(provider) ||
+                LocationManager.FUSED_PROVIDER.equals(provider)) {
+            // network and fused providers are ok with COARSE or FINE
+            return RESOLUTION_LEVEL_COARSE;
+        } else {
+            // mock providers
+            LocationProviderInterface lp = mMockProviders.get(provider);
+            if (lp != null) {
+                ProviderProperties properties = lp.getProperties();
+                if (properties != null) {
+                    if (properties.mRequiresSatellite) {
+                        // provider requiring satellites require FINE permission
+                        return RESOLUTION_LEVEL_FINE;
+                    } else if (properties.mRequiresNetwork || properties.mRequiresCell) {
+                        // provider requiring network and or cell require COARSE or FINE
+                        return RESOLUTION_LEVEL_COARSE;
+                    }
+                }
+            }
+        }
+        return RESOLUTION_LEVEL_FINE; // if in doubt, require FINE
+    }
+
+    /**
+     * Throw SecurityException if specified resolution level is insufficient to use the named
+     * location provider.
+     *
+     * @param allowedResolutionLevel resolution level allowed to caller
+     * @param providerName the name of the location provider
+     */
+    private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
+            String providerName) {
+        int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName);
+        if (allowedResolutionLevel < requiredResolutionLevel) {
+            switch (requiredResolutionLevel) {
+                case RESOLUTION_LEVEL_FINE:
+                    throw new SecurityException("\"" + providerName + "\" location provider " +
+                            "requires ACCESS_FINE_LOCATION permission.");
+                case RESOLUTION_LEVEL_COARSE:
+                    throw new SecurityException("\"" + providerName + "\" location provider " +
+                            "requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
+                default:
+                    throw new SecurityException("Insufficient permission for \"" + providerName +
+                            "\" location provider.");
+            }
+        }
+    }
+
+    /**
+     * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
+     * for battery).
+     */
+    private void checkDeviceStatsAllowed() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_DEVICE_STATS, null);
+    }
+
+    private void checkUpdateAppOpsAllowed() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
+    }
+
+    public static int resolutionLevelToOp(int allowedResolutionLevel) {
+        if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) {
+            if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) {
+                return AppOpsManager.OP_COARSE_LOCATION;
+            } else {
+                return AppOpsManager.OP_FINE_LOCATION;
+            }
+        }
+        return -1;
+    }
+
+    boolean reportLocationAccessNoThrow(int uid, String packageName, int allowedResolutionLevel) {
+        int op = resolutionLevelToOp(allowedResolutionLevel);
+        if (op >= 0) {
+            if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    boolean checkLocationAccess(int uid, String packageName, int allowedResolutionLevel) {
+        int op = resolutionLevelToOp(allowedResolutionLevel);
+        if (op >= 0) {
+            if (mAppOps.checkOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns all providers by name, including passive, but excluding
+     * fused, also including ones that are not permitted to
+     * be accessed by the calling activity or are currently disabled.
+     */
+    @Override
+    public List<String> getAllProviders() {
+        ArrayList<String> out;
+        synchronized (mLock) {
+            out = new ArrayList<String>(mProviders.size());
+            for (LocationProviderInterface provider : mProviders) {
+                String name = provider.getName();
+                if (LocationManager.FUSED_PROVIDER.equals(name)) {
+                    continue;
+                }
+                out.add(name);
+            }
+        }
+
+        if (D) Log.d(TAG, "getAllProviders()=" + out);
+        return out;
+    }
+
+    /**
+     * Return all providers by name, that match criteria and are optionally
+     * enabled.
+     * Can return passive provider, but never returns fused provider.
+     */
+    @Override
+    public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        ArrayList<String> out;
+        int uid = Binder.getCallingUid();;
+        long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                out = new ArrayList<String>(mProviders.size());
+                for (LocationProviderInterface provider : mProviders) {
+                    String name = provider.getName();
+                    if (LocationManager.FUSED_PROVIDER.equals(name)) {
+                        continue;
+                    }
+                    if (allowedResolutionLevel >= getMinimumResolutionLevelForProviderUse(name)) {
+                        if (enabledOnly && !isAllowedByUserSettingsLocked(name, uid)) {
+                            continue;
+                        }
+                        if (criteria != null && !LocationProvider.propertiesMeetCriteria(
+                                name, provider.getProperties(), criteria)) {
+                            continue;
+                        }
+                        out.add(name);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+
+        if (D) Log.d(TAG, "getProviders()=" + out);
+        return out;
+    }
+
+    /**
+     * Return the name of the best provider given a Criteria object.
+     * This method has been deprecated from the public API,
+     * and the whole LocationProvider (including #meetsCriteria)
+     * has been deprecated as well. So this method now uses
+     * some simplified logic.
+     */
+    @Override
+    public String getBestProvider(Criteria criteria, boolean enabledOnly) {
+        String result = null;
+
+        List<String> providers = getProviders(criteria, enabledOnly);
+        if (!providers.isEmpty()) {
+            result = pickBest(providers);
+            if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
+            return result;
+        }
+        providers = getProviders(null, enabledOnly);
+        if (!providers.isEmpty()) {
+            result = pickBest(providers);
+            if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
+            return result;
+        }
+
+        if (D) Log.d(TAG, "getBestProvider(" + criteria + ", " + enabledOnly + ")=" + result);
+        return null;
+    }
+
+    private String pickBest(List<String> providers) {
+        if (providers.contains(LocationManager.GPS_PROVIDER)) {
+            return LocationManager.GPS_PROVIDER;
+        } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
+            return LocationManager.NETWORK_PROVIDER;
+        } else {
+            return providers.get(0);
+        }
+    }
+
+    @Override
+    public boolean providerMeetsCriteria(String provider, Criteria criteria) {
+        LocationProviderInterface p = mProvidersByName.get(provider);
+        if (p == null) {
+            throw new IllegalArgumentException("provider=" + provider);
+        }
+
+        boolean result = LocationProvider.propertiesMeetCriteria(
+                p.getName(), p.getProperties(), criteria);
+        if (D) Log.d(TAG, "providerMeetsCriteria(" + provider + ", " + criteria + ")=" + result);
+        return result;
+    }
+
+    private void updateProvidersLocked() {
+        boolean changesMade = false;
+        for (int i = mProviders.size() - 1; i >= 0; i--) {
+            LocationProviderInterface p = mProviders.get(i);
+            boolean isEnabled = p.isEnabled();
+            String name = p.getName();
+            boolean shouldBeEnabled = isAllowedByCurrentUserSettingsLocked(name);
+            if (isEnabled && !shouldBeEnabled) {
+                updateProviderListenersLocked(name, false, mCurrentUserId);
+                changesMade = true;
+            } else if (!isEnabled && shouldBeEnabled) {
+                updateProviderListenersLocked(name, true, mCurrentUserId);
+                changesMade = true;
+            }
+        }
+        if (changesMade) {
+            mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION),
+                    UserHandle.ALL);
+            mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION),
+                    UserHandle.ALL);
+        }
+    }
+
+    private void updateProviderListenersLocked(String provider, boolean enabled, int userId) {
+        int listeners = 0;
+
+        LocationProviderInterface p = mProvidersByName.get(provider);
+        if (p == null) return;
+
+        ArrayList<Receiver> deadReceivers = null;
+
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        if (records != null) {
+            final int N = records.size();
+            for (int i = 0; i < N; i++) {
+                UpdateRecord record = records.get(i);
+                if (UserHandle.getUserId(record.mReceiver.mUid) == userId) {
+                    // Sends a notification message to the receiver
+                    if (!record.mReceiver.callProviderEnabledLocked(provider, enabled)) {
+                        if (deadReceivers == null) {
+                            deadReceivers = new ArrayList<Receiver>();
+                        }
+                        deadReceivers.add(record.mReceiver);
+                    }
+                    listeners++;
+                }
+            }
+        }
+
+        if (deadReceivers != null) {
+            for (int i = deadReceivers.size() - 1; i >= 0; i--) {
+                removeUpdatesLocked(deadReceivers.get(i));
+            }
+        }
+
+        if (enabled) {
+            p.enable();
+            if (listeners > 0) {
+                applyRequirementsLocked(provider);
+            }
+        } else {
+            p.disable();
+        }
+    }
+
+    private void applyRequirementsLocked(String provider) {
+        LocationProviderInterface p = mProvidersByName.get(provider);
+        if (p == null) return;
+
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        WorkSource worksource = new WorkSource();
+        ProviderRequest providerRequest = new ProviderRequest();
+
+        if (records != null) {
+            for (UpdateRecord record : records) {
+                if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
+                    if (checkLocationAccess(record.mReceiver.mUid, record.mReceiver.mPackageName,
+                            record.mReceiver.mAllowedResolutionLevel)) {
+                        LocationRequest locationRequest = record.mRequest;
+                        providerRequest.locationRequests.add(locationRequest);
+                        if (locationRequest.getInterval() < providerRequest.interval) {
+                            providerRequest.reportLocation = true;
+                            providerRequest.interval = locationRequest.getInterval();
+                        }
+                    }
+                }
+            }
+
+            if (providerRequest.reportLocation) {
+                // calculate who to blame for power
+                // This is somewhat arbitrary. We pick a threshold interval
+                // that is slightly higher that the minimum interval, and
+                // spread the blame across all applications with a request
+                // under that threshold.
+                long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
+                for (UpdateRecord record : records) {
+                    if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) {
+                        LocationRequest locationRequest = record.mRequest;
+                        if (locationRequest.getInterval() <= thresholdInterval) {
+                            if (record.mReceiver.mWorkSource != null
+                                    && record.mReceiver.mWorkSource.size() > 0
+                                    && record.mReceiver.mWorkSource.getName(0) != null) {
+                                // Assign blame to another work source.
+                                // Can only assign blame if the WorkSource contains names.
+                                worksource.add(record.mReceiver.mWorkSource);
+                            } else {
+                                // Assign blame to caller.
+                                worksource.add(
+                                        record.mReceiver.mUid,
+                                        record.mReceiver.mPackageName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
+        p.setRequest(providerRequest, worksource);
+    }
+
+    private class UpdateRecord {
+        final String mProvider;
+        final LocationRequest mRequest;
+        final Receiver mReceiver;
+        Location mLastFixBroadcast;
+        long mLastStatusBroadcast;
+
+        /**
+         * Note: must be constructed with lock held.
+         */
+        UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
+            mProvider = provider;
+            mRequest = request;
+            mReceiver = receiver;
+
+            ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+            if (records == null) {
+                records = new ArrayList<UpdateRecord>();
+                mRecordsByProvider.put(provider, records);
+            }
+            if (!records.contains(this)) {
+                records.add(this);
+            }
+        }
+
+        /**
+         * Method to be called when a record will no longer be used.  Calling this multiple times
+         * must have the same effect as calling it once.
+         */
+        void disposeLocked(boolean removeReceiver) {
+            // remove from mRecordsByProvider
+            ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
+            if (globalRecords != null) {
+                globalRecords.remove(this);
+            }
+
+            if (!removeReceiver) return;  // the caller will handle the rest
+
+            // remove from Receiver#mUpdateRecords
+            HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
+            if (receiverRecords != null) {
+                receiverRecords.remove(this.mProvider);
+
+                // and also remove the Receiver if it has no more update records
+                if (removeReceiver && receiverRecords.size() == 0) {
+                    removeUpdatesLocked(mReceiver);
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder s = new StringBuilder();
+            s.append("UpdateRecord[");
+            s.append(mProvider);
+            s.append(' ').append(mReceiver.mPackageName).append('(');
+            s.append(mReceiver.mUid).append(')');
+            s.append(' ').append(mRequest);
+            s.append(']');
+            return s.toString();
+        }
+    }
+
+    private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
+            String packageName, WorkSource workSource, boolean hideFromAppOps) {
+        IBinder binder = listener.asBinder();
+        Receiver receiver = mReceivers.get(binder);
+        if (receiver == null) {
+            receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
+                    hideFromAppOps);
+            mReceivers.put(binder, receiver);
+
+            try {
+                receiver.getListener().asBinder().linkToDeath(receiver, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "linkToDeath failed:", e);
+                return null;
+            }
+        }
+        return receiver;
+    }
+
+    private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
+            WorkSource workSource, boolean hideFromAppOps) {
+        Receiver receiver = mReceivers.get(intent);
+        if (receiver == null) {
+            receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
+                    hideFromAppOps);
+            mReceivers.put(intent, receiver);
+        }
+        return receiver;
+    }
+
+    /**
+     * Creates a LocationRequest based upon the supplied LocationRequest that to meets resolution
+     * and consistency requirements.
+     *
+     * @param request the LocationRequest from which to create a sanitized version
+     * @return a version of request that meets the given resolution and consistency requirements
+     * @hide
+     */
+    private LocationRequest createSanitizedRequest(LocationRequest request, int resolutionLevel) {
+        LocationRequest sanitizedRequest = new LocationRequest(request);
+        if (resolutionLevel < RESOLUTION_LEVEL_FINE) {
+            switch (sanitizedRequest.getQuality()) {
+                case LocationRequest.ACCURACY_FINE:
+                    sanitizedRequest.setQuality(LocationRequest.ACCURACY_BLOCK);
+                    break;
+                case LocationRequest.POWER_HIGH:
+                    sanitizedRequest.setQuality(LocationRequest.POWER_LOW);
+                    break;
+            }
+            // throttle
+            if (sanitizedRequest.getInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
+                sanitizedRequest.setInterval(LocationFudger.FASTEST_INTERVAL_MS);
+            }
+            if (sanitizedRequest.getFastestInterval() < LocationFudger.FASTEST_INTERVAL_MS) {
+                sanitizedRequest.setFastestInterval(LocationFudger.FASTEST_INTERVAL_MS);
+            }
+        }
+        // make getFastestInterval() the minimum of interval and fastest interval
+        if (sanitizedRequest.getFastestInterval() > sanitizedRequest.getInterval()) {
+            request.setFastestInterval(request.getInterval());
+        }
+        return sanitizedRequest;
+    }
+
+    private void checkPackageName(String packageName) {
+        if (packageName == null) {
+            throw new SecurityException("invalid package name: " + packageName);
+        }
+        int uid = Binder.getCallingUid();
+        String[] packages = mPackageManager.getPackagesForUid(uid);
+        if (packages == null) {
+            throw new SecurityException("invalid UID " + uid);
+        }
+        for (String pkg : packages) {
+            if (packageName.equals(pkg)) return;
+        }
+        throw new SecurityException("invalid package name: " + packageName);
+    }
+
+    private void checkPendingIntent(PendingIntent intent) {
+        if (intent == null) {
+            throw new IllegalArgumentException("invalid pending intent: " + intent);
+        }
+    }
+
+    private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
+            int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
+        if (intent == null && listener == null) {
+            throw new IllegalArgumentException("need either listener or intent");
+        } else if (intent != null && listener != null) {
+            throw new IllegalArgumentException("cannot register both listener and intent");
+        } else if (intent != null) {
+            checkPendingIntent(intent);
+            return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
+        } else {
+            return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
+        }
+    }
+
+    @Override
+    public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
+            PendingIntent intent, String packageName) {
+        if (request == null) request = DEFAULT_LOCATION_REQUEST;
+        checkPackageName(packageName);
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
+                request.getProvider());
+        WorkSource workSource = request.getWorkSource();
+        if (workSource != null && workSource.size() > 0) {
+            checkDeviceStatsAllowed();
+        }
+        boolean hideFromAppOps = request.getHideFromAppOps();
+        if (hideFromAppOps) {
+            checkUpdateAppOpsAllowed();
+        }
+        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel);
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        // providers may use public location API's, need to clear identity
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // We don't check for MODE_IGNORED here; we will do that when we go to deliver
+            // a location.
+            checkLocationAccess(uid, packageName, allowedResolutionLevel);
+
+            synchronized (mLock) {
+                Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
+                        packageName, workSource, hideFromAppOps);
+                requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
+            int pid, int uid, String packageName) {
+        // Figure out the provider. Either its explicitly request (legacy use cases), or
+        // use the fused provider
+        if (request == null) request = DEFAULT_LOCATION_REQUEST;
+        String name = request.getProvider();
+        if (name == null) {
+            throw new IllegalArgumentException("provider name must not be null");
+        }
+
+        if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+                + " " + name + " " + request + " from " + packageName + "(" + uid + ")");
+        LocationProviderInterface provider = mProvidersByName.get(name);
+        if (provider == null) {
+            throw new IllegalArgumentException("provider doesn't exist: " + name);
+        }
+
+        UpdateRecord record = new UpdateRecord(name, request, receiver);
+        UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
+        if (oldRecord != null) {
+            oldRecord.disposeLocked(false);
+        }
+
+        boolean isProviderEnabled = isAllowedByUserSettingsLocked(name, uid);
+        if (isProviderEnabled) {
+            applyRequirementsLocked(name);
+        } else {
+            // Notify the listener that updates are currently disabled
+            receiver.callProviderEnabledLocked(name, false);
+        }
+        // Update the monitoring here just in case multiple location requests were added to the
+        // same receiver (this request may be high power and the initial might not have been).
+        receiver.updateMonitoring(true);
+    }
+
+    @Override
+    public void removeUpdates(ILocationListener listener, PendingIntent intent,
+            String packageName) {
+        checkPackageName(packageName);
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+
+        synchronized (mLock) {
+            WorkSource workSource = null;
+            boolean hideFromAppOps = false;
+            Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid,
+                    packageName, workSource, hideFromAppOps);
+
+            // providers may use public location API's, need to clear identity
+            long identity = Binder.clearCallingIdentity();
+            try {
+                removeUpdatesLocked(receiver);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    private void removeUpdatesLocked(Receiver receiver) {
+        if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
+
+        if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
+            receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
+            synchronized (receiver) {
+                receiver.clearPendingBroadcastsLocked();
+            }
+        }
+
+        receiver.updateMonitoring(false);
+
+        // Record which providers were associated with this listener
+        HashSet<String> providers = new HashSet<String>();
+        HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
+        if (oldRecords != null) {
+            // Call dispose() on the obsolete update records.
+            for (UpdateRecord record : oldRecords.values()) {
+                record.disposeLocked(false);
+            }
+            // Accumulate providers
+            providers.addAll(oldRecords.keySet());
+        }
+
+        // update provider
+        for (String provider : providers) {
+            // If provider is already disabled, don't need to do anything
+            if (!isAllowedByCurrentUserSettingsLocked(provider)) {
+                continue;
+            }
+
+            applyRequirementsLocked(provider);
+        }
+    }
+
+    private void applyAllProviderRequirementsLocked() {
+        for (LocationProviderInterface p : mProviders) {
+            // If provider is already disabled, don't need to do anything
+            if (!isAllowedByCurrentUserSettingsLocked(p.getName())) {
+                continue;
+            }
+
+            applyRequirementsLocked(p.getName());
+        }
+    }
+
+    @Override
+    public Location getLastLocation(LocationRequest request, String packageName) {
+        if (D) Log.d(TAG, "getLastLocation: " + request);
+        if (request == null) request = DEFAULT_LOCATION_REQUEST;
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        checkPackageName(packageName);
+        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
+                request.getProvider());
+        // no need to sanitize this request, as only the provider name is used
+
+        final int uid = Binder.getCallingUid();
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (mBlacklist.isBlacklisted(packageName)) {
+                if (D) Log.d(TAG, "not returning last loc for blacklisted app: " +
+                        packageName);
+                return null;
+            }
+
+            if (!reportLocationAccessNoThrow(uid, packageName, allowedResolutionLevel)) {
+                if (D) Log.d(TAG, "not returning last loc for no op app: " +
+                        packageName);
+                return null;
+            }
+
+            synchronized (mLock) {
+                // Figure out the provider. Either its explicitly request (deprecated API's),
+                // or use the fused provider
+                String name = request.getProvider();
+                if (name == null) name = LocationManager.FUSED_PROVIDER;
+                LocationProviderInterface provider = mProvidersByName.get(name);
+                if (provider == null) return null;
+
+                if (!isAllowedByUserSettingsLocked(name, uid)) return null;
+
+                Location location;
+                if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+                    // Make sure that an app with coarse permissions can't get frequent location
+                    // updates by calling LocationManager.getLastKnownLocation repeatedly.
+                    location = mLastLocationCoarseInterval.get(name);
+                } else {
+                    location = mLastLocation.get(name);
+                }
+                if (location == null) {
+                    return null;
+                }
+                if (allowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+                    Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+                    if (noGPSLocation != null) {
+                        return new Location(mLocationFudger.getOrCreate(noGPSLocation));
+                    }
+                } else {
+                    return new Location(location);
+                }
+            }
+            return null;
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
+            String packageName) {
+        if (request == null) request = DEFAULT_LOCATION_REQUEST;
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
+        checkPendingIntent(intent);
+        checkPackageName(packageName);
+        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
+                request.getProvider());
+        LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel);
+
+        if (D) Log.d(TAG, "requestGeofence: " + sanitizedRequest + " " + geofence + " " + intent);
+
+        // geo-fence manager uses the public location API, need to clear identity
+        int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != UserHandle.USER_OWNER) {
+            // temporary measure until geofences work for secondary users
+            Log.w(TAG, "proximity alerts are currently available only to the primary user");
+            return;
+        }
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+                    uid, packageName);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void removeGeofence(Geofence geofence, PendingIntent intent, String packageName) {
+        checkResolutionLevelIsSufficientForGeofenceUse(getCallerAllowedResolutionLevel());
+        checkPendingIntent(intent);
+        checkPackageName(packageName);
+
+        if (D) Log.d(TAG, "removeGeofence: " + geofence + " " + intent);
+
+        // geo-fence manager uses the public location API, need to clear identity
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mGeofenceManager.removeFence(geofence, intent);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+
+    @Override
+    public boolean addGpsStatusListener(IGpsStatusListener listener, String packageName) {
+        if (mGpsStatusProvider == null) {
+            return false;
+        }
+        int allowedResolutionLevel = getCallerAllowedResolutionLevel();
+        checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
+                LocationManager.GPS_PROVIDER);
+
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (!checkLocationAccess(uid, packageName, allowedResolutionLevel)) {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        try {
+            mGpsStatusProvider.addGpsStatusListener(listener);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "mGpsStatusProvider.addGpsStatusListener failed", e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void removeGpsStatusListener(IGpsStatusListener listener) {
+        synchronized (mLock) {
+            try {
+                mGpsStatusProvider.removeGpsStatusListener(listener);
+            } catch (Exception e) {
+                Slog.e(TAG, "mGpsStatusProvider.removeGpsStatusListener failed", e);
+            }
+        }
+    }
+
+    @Override
+    public boolean sendExtraCommand(String provider, String command, Bundle extras) {
+        if (provider == null) {
+            // throw NullPointerException to remain compatible with previous implementation
+            throw new NullPointerException();
+        }
+        checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
+                provider);
+
+        // and check for ACCESS_LOCATION_EXTRA_COMMANDS
+        if ((mContext.checkCallingOrSelfPermission(ACCESS_LOCATION_EXTRA_COMMANDS)
+                != PackageManager.PERMISSION_GRANTED)) {
+            throw new SecurityException("Requires ACCESS_LOCATION_EXTRA_COMMANDS permission");
+        }
+
+        synchronized (mLock) {
+            LocationProviderInterface p = mProvidersByName.get(provider);
+            if (p == null) return false;
+
+            return p.sendExtraCommand(command, extras);
+        }
+    }
+
+    @Override
+    public boolean sendNiResponse(int notifId, int userResponse) {
+        if (Binder.getCallingUid() != Process.myUid()) {
+            throw new SecurityException(
+                    "calling sendNiResponse from outside of the system is not allowed");
+        }
+        try {
+            return mNetInitiatedListener.sendNiResponse(notifId, userResponse);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException in LocationManagerService.sendNiResponse");
+            return false;
+        }
+    }
+
+    /**
+     * @return null if the provider does not exist
+     * @throws SecurityException if the provider is not allowed to be
+     * accessed by the caller
+     */
+    @Override
+    public ProviderProperties getProviderProperties(String provider) {
+        if (mProvidersByName.get(provider) == null) {
+          return null;
+        }
+
+        checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
+                provider);
+
+        LocationProviderInterface p;
+        synchronized (mLock) {
+            p = mProvidersByName.get(provider);
+        }
+
+        if (p == null) return null;
+        return p.getProperties();
+    }
+
+    @Override
+    public boolean isProviderEnabled(String provider) {
+        // TODO: remove this check in next release, see b/10696351
+        checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(),
+                provider);
+
+        // Fused provider is accessed indirectly via criteria rather than the provider-based APIs,
+        // so we discourage its use
+        if (LocationManager.FUSED_PROVIDER.equals(provider)) return false;
+
+        int uid = Binder.getCallingUid();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                LocationProviderInterface p = mProvidersByName.get(provider);
+                if (p == null) return false;
+
+                return isAllowedByUserSettingsLocked(provider, uid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * Returns "true" if the UID belongs to a bound location provider.
+     *
+     * @param uid the uid
+     * @return true if uid belongs to a bound location provider
+     */
+    private boolean isUidALocationProvider(int uid) {
+        if (uid == Process.SYSTEM_UID) {
+            return true;
+        }
+        if (mGeocodeProvider != null) {
+            if (doesPackageHaveUid(uid, mGeocodeProvider.getConnectedPackageName())) return true;
+        }
+        for (LocationProviderProxy proxy : mProxyProviders) {
+            if (doesPackageHaveUid(uid, proxy.getConnectedPackageName())) return true;
+        }
+        return false;
+    }
+
+    private void checkCallerIsProvider() {
+        if (mContext.checkCallingOrSelfPermission(INSTALL_LOCATION_PROVIDER)
+                == PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+
+        // Previously we only used the INSTALL_LOCATION_PROVIDER
+        // check. But that is system or signature
+        // protection level which is not flexible enough for
+        // providers installed oustide the system image. So
+        // also allow providers with a UID matching the
+        // currently bound package name
+
+        if (isUidALocationProvider(Binder.getCallingUid())) {
+            return;
+        }
+
+        throw new SecurityException("need INSTALL_LOCATION_PROVIDER permission, " +
+                "or UID of a currently bound location provider");
+    }
+
+    private boolean doesPackageHaveUid(int uid, String packageName) {
+        if (packageName == null) {
+            return false;
+        }
+        try {
+            ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0);
+            if (appInfo.uid != uid) {
+                return false;
+            }
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void reportLocation(Location location, boolean passive) {
+        checkCallerIsProvider();
+
+        if (!location.isComplete()) {
+            Log.w(TAG, "Dropping incomplete location: " + location);
+            return;
+        }
+
+        mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
+        Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
+        m.arg1 = (passive ? 1 : 0);
+        mLocationHandler.sendMessageAtFrontOfQueue(m);
+    }
+
+
+    private static boolean shouldBroadcastSafe(
+            Location loc, Location lastLoc, UpdateRecord record, long now) {
+        // Always broadcast the first update
+        if (lastLoc == null) {
+            return true;
+        }
+
+        // Check whether sufficient time has passed
+        long minTime = record.mRequest.getFastestInterval();
+        long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos())
+                / NANOS_PER_MILLI;
+        if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
+            return false;
+        }
+
+        // Check whether sufficient distance has been traveled
+        double minDistance = record.mRequest.getSmallestDisplacement();
+        if (minDistance > 0.0) {
+            if (loc.distanceTo(lastLoc) <= minDistance) {
+                return false;
+            }
+        }
+
+        // Check whether sufficient number of udpates is left
+        if (record.mRequest.getNumUpdates() <= 0) {
+            return false;
+        }
+
+        // Check whether the expiry date has passed
+        if (record.mRequest.getExpireAt() < now) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void handleLocationChangedLocked(Location location, boolean passive) {
+        if (D) Log.d(TAG, "incoming location: " + location);
+
+        long now = SystemClock.elapsedRealtime();
+        String provider = (passive ? LocationManager.PASSIVE_PROVIDER : location.getProvider());
+
+        // Skip if the provider is unknown.
+        LocationProviderInterface p = mProvidersByName.get(provider);
+        if (p == null) return;
+
+        // Update last known locations
+        Location noGPSLocation = location.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+        Location lastNoGPSLocation = null;
+        Location lastLocation = mLastLocation.get(provider);
+        if (lastLocation == null) {
+            lastLocation = new Location(provider);
+            mLastLocation.put(provider, lastLocation);
+        } else {
+            lastNoGPSLocation = lastLocation.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+            if (noGPSLocation == null && lastNoGPSLocation != null) {
+                // New location has no no-GPS location: adopt last no-GPS location. This is set
+                // directly into location because we do not want to notify COARSE clients.
+                location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, lastNoGPSLocation);
+            }
+        }
+        lastLocation.set(location);
+
+        // Update last known coarse interval location if enough time has passed.
+        Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(provider);
+        if (lastLocationCoarseInterval == null) {
+            lastLocationCoarseInterval = new Location(location);
+            mLastLocationCoarseInterval.put(provider, lastLocationCoarseInterval);
+        }
+        long timeDiffNanos = location.getElapsedRealtimeNanos()
+                - lastLocationCoarseInterval.getElapsedRealtimeNanos();
+        if (timeDiffNanos > LocationFudger.FASTEST_INTERVAL_MS * NANOS_PER_MILLI) {
+            lastLocationCoarseInterval.set(location);
+        }
+        // Don't ever return a coarse location that is more recent than the allowed update
+        // interval (i.e. don't allow an app to keep registering and unregistering for
+        // location updates to overcome the minimum interval).
+        noGPSLocation =
+                lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
+
+        // Skip if there are no UpdateRecords for this provider.
+        ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
+        if (records == null || records.size() == 0) return;
+
+        // Fetch coarse location
+        Location coarseLocation = null;
+        if (noGPSLocation != null) {
+            coarseLocation = mLocationFudger.getOrCreate(noGPSLocation);
+        }
+
+        // Fetch latest status update time
+        long newStatusUpdateTime = p.getStatusUpdateTime();
+
+       // Get latest status
+        Bundle extras = new Bundle();
+        int status = p.getStatus(extras);
+
+        ArrayList<Receiver> deadReceivers = null;
+        ArrayList<UpdateRecord> deadUpdateRecords = null;
+
+        // Broadcast location or status to all listeners
+        for (UpdateRecord r : records) {
+            Receiver receiver = r.mReceiver;
+            boolean receiverDead = false;
+
+            int receiverUserId = UserHandle.getUserId(receiver.mUid);
+            if (receiverUserId != mCurrentUserId && !isUidALocationProvider(receiver.mUid)) {
+                if (D) {
+                    Log.d(TAG, "skipping loc update for background user " + receiverUserId +
+                            " (current user: " + mCurrentUserId + ", app: " +
+                            receiver.mPackageName + ")");
+                }
+                continue;
+            }
+
+            if (mBlacklist.isBlacklisted(receiver.mPackageName)) {
+                if (D) Log.d(TAG, "skipping loc update for blacklisted app: " +
+                        receiver.mPackageName);
+                continue;
+            }
+
+            if (!reportLocationAccessNoThrow(receiver.mUid, receiver.mPackageName,
+                    receiver.mAllowedResolutionLevel)) {
+                if (D) Log.d(TAG, "skipping loc update for no op app: " +
+                        receiver.mPackageName);
+                continue;
+            }
+
+            Location notifyLocation = null;
+            if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
+                notifyLocation = coarseLocation;  // use coarse location
+            } else {
+                notifyLocation = lastLocation;  // use fine location
+            }
+            if (notifyLocation != null) {
+                Location lastLoc = r.mLastFixBroadcast;
+                if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
+                    if (lastLoc == null) {
+                        lastLoc = new Location(notifyLocation);
+                        r.mLastFixBroadcast = lastLoc;
+                    } else {
+                        lastLoc.set(notifyLocation);
+                    }
+                    if (!receiver.callLocationChangedLocked(notifyLocation)) {
+                        Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
+                        receiverDead = true;
+                    }
+                    r.mRequest.decrementNumUpdates();
+                }
+            }
+
+            long prevStatusUpdateTime = r.mLastStatusBroadcast;
+            if ((newStatusUpdateTime > prevStatusUpdateTime) &&
+                    (prevStatusUpdateTime != 0 || status != LocationProvider.AVAILABLE)) {
+
+                r.mLastStatusBroadcast = newStatusUpdateTime;
+                if (!receiver.callStatusChangedLocked(provider, status, extras)) {
+                    receiverDead = true;
+                    Slog.w(TAG, "RemoteException calling onStatusChanged on " + receiver);
+                }
+            }
+
+            // track expired records
+            if (r.mRequest.getNumUpdates() <= 0 || r.mRequest.getExpireAt() < now) {
+                if (deadUpdateRecords == null) {
+                    deadUpdateRecords = new ArrayList<UpdateRecord>();
+                }
+                deadUpdateRecords.add(r);
+            }
+            // track dead receivers
+            if (receiverDead) {
+                if (deadReceivers == null) {
+                    deadReceivers = new ArrayList<Receiver>();
+                }
+                if (!deadReceivers.contains(receiver)) {
+                    deadReceivers.add(receiver);
+                }
+            }
+        }
+
+        // remove dead records and receivers outside the loop
+        if (deadReceivers != null) {
+            for (Receiver receiver : deadReceivers) {
+                removeUpdatesLocked(receiver);
+            }
+        }
+        if (deadUpdateRecords != null) {
+            for (UpdateRecord r : deadUpdateRecords) {
+                r.disposeLocked(true);
+            }
+            applyRequirementsLocked(provider);
+        }
+    }
+
+    private class LocationWorkerHandler extends Handler {
+        public LocationWorkerHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_LOCATION_CHANGED:
+                    handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
+                    break;
+            }
+        }
+    }
+
+    private boolean isMockProvider(String provider) {
+        synchronized (mLock) {
+            return mMockProviders.containsKey(provider);
+        }
+    }
+
+    private void handleLocationChanged(Location location, boolean passive) {
+        // create a working copy of the incoming Location so that the service can modify it without
+        // disturbing the caller's copy
+        Location myLocation = new Location(location);
+        String provider = myLocation.getProvider();
+
+        // set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
+        // bit if location did not come from a mock provider because passive/fused providers can
+        // forward locations from mock providers, and should not grant them legitimacy in doing so.
+        if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
+            myLocation.setIsFromMockProvider(true);
+        }
+
+        synchronized (mLock) {
+            if (isAllowedByCurrentUserSettingsLocked(provider)) {
+                if (!passive) {
+                    // notify passive provider of the new location
+                    mPassiveProvider.updateLocation(myLocation);
+                }
+                handleLocationChangedLocked(myLocation, passive);
+            }
+        }
+    }
+
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageDisappeared(String packageName, int reason) {
+            // remove all receivers associated with this package name
+            synchronized (mLock) {
+                ArrayList<Receiver> deadReceivers = null;
+
+                for (Receiver receiver : mReceivers.values()) {
+                    if (receiver.mPackageName.equals(packageName)) {
+                        if (deadReceivers == null) {
+                            deadReceivers = new ArrayList<Receiver>();
+                        }
+                        deadReceivers.add(receiver);
+                    }
+                }
+
+                // perform removal outside of mReceivers loop
+                if (deadReceivers != null) {
+                    for (Receiver receiver : deadReceivers) {
+                        removeUpdatesLocked(receiver);
+                    }
+                }
+            }
+        }
+    };
+
+    // Geocoder
+
+    @Override
+    public boolean geocoderIsPresent() {
+        return mGeocodeProvider != null;
+    }
+
+    @Override
+    public String getFromLocation(double latitude, double longitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+        if (mGeocodeProvider != null) {
+            return mGeocodeProvider.getFromLocation(latitude, longitude, maxResults,
+                    params, addrs);
+        }
+        return null;
+    }
+
+
+    @Override
+    public String getFromLocationName(String locationName,
+            double lowerLeftLatitude, double lowerLeftLongitude,
+            double upperRightLatitude, double upperRightLongitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+
+        if (mGeocodeProvider != null) {
+            return mGeocodeProvider.getFromLocationName(locationName, lowerLeftLatitude,
+                    lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+                    maxResults, params, addrs);
+        }
+        return null;
+    }
+
+    // Mock Providers
+
+    private void checkMockPermissionsSafe() {
+        boolean allowMocks = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 1;
+        if (!allowMocks) {
+            throw new SecurityException("Requires ACCESS_MOCK_LOCATION secure setting");
+        }
+
+        if (mContext.checkCallingPermission(ACCESS_MOCK_LOCATION) !=
+            PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires ACCESS_MOCK_LOCATION permission");
+        }
+    }
+
+    @Override
+    public void addTestProvider(String name, ProviderProperties properties) {
+        checkMockPermissionsSafe();
+
+        if (LocationManager.PASSIVE_PROVIDER.equals(name)) {
+            throw new IllegalArgumentException("Cannot mock the passive location provider");
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            MockProvider provider = new MockProvider(name, this, properties);
+            // remove the real provider if we are replacing GPS or network provider
+            if (LocationManager.GPS_PROVIDER.equals(name)
+                    || LocationManager.NETWORK_PROVIDER.equals(name)
+                    || LocationManager.FUSED_PROVIDER.equals(name)) {
+                LocationProviderInterface p = mProvidersByName.get(name);
+                if (p != null) {
+                    removeProviderLocked(p);
+                }
+            }
+            if (mProvidersByName.get(name) != null) {
+                throw new IllegalArgumentException("Provider \"" + name + "\" already exists");
+            }
+            addProviderLocked(provider);
+            mMockProviders.put(name, provider);
+            mLastLocation.put(name, null);
+            mLastLocationCoarseInterval.put(name, null);
+            updateProvidersLocked();
+        }
+        Binder.restoreCallingIdentity(identity);
+    }
+
+    @Override
+    public void removeTestProvider(String provider) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.remove(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            long identity = Binder.clearCallingIdentity();
+            removeProviderLocked(mProvidersByName.get(provider));
+
+            // reinstate real provider if available
+            LocationProviderInterface realProvider = mRealProviders.get(provider);
+            if (realProvider != null) {
+                addProviderLocked(realProvider);
+            }
+            mLastLocation.put(provider, null);
+            mLastLocationCoarseInterval.put(provider, null);
+            updateProvidersLocked();
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void setTestProviderLocation(String provider, Location loc) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required
+            long identity = Binder.clearCallingIdentity();
+            mockProvider.setLocation(loc);
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void clearTestProviderLocation(String provider) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            mockProvider.clearLocation();
+        }
+    }
+
+    @Override
+    public void setTestProviderEnabled(String provider, boolean enabled) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            long identity = Binder.clearCallingIdentity();
+            if (enabled) {
+                mockProvider.enable();
+                mEnabledProviders.add(provider);
+                mDisabledProviders.remove(provider);
+            } else {
+                mockProvider.disable();
+                mEnabledProviders.remove(provider);
+                mDisabledProviders.add(provider);
+            }
+            updateProvidersLocked();
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void clearTestProviderEnabled(String provider) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            long identity = Binder.clearCallingIdentity();
+            mEnabledProviders.remove(provider);
+            mDisabledProviders.remove(provider);
+            updateProvidersLocked();
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void setTestProviderStatus(String provider, int status, Bundle extras, long updateTime) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            mockProvider.setStatus(status, extras, updateTime);
+        }
+    }
+
+    @Override
+    public void clearTestProviderStatus(String provider) {
+        checkMockPermissionsSafe();
+        synchronized (mLock) {
+            MockProvider mockProvider = mMockProviders.get(provider);
+            if (mockProvider == null) {
+                throw new IllegalArgumentException("Provider \"" + provider + "\" unknown");
+            }
+            mockProvider.clearStatus();
+        }
+    }
+
+    private void log(String log) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Slog.d(TAG, log);
+        }
+    }
+
+    @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 LocationManagerService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("Current Location Manager state:");
+            pw.println("  Location Listeners:");
+            for (Receiver receiver : mReceivers.values()) {
+                pw.println("    " + receiver);
+            }
+            pw.println("  Records by Provider:");
+            for (Map.Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+                pw.println("    " + entry.getKey() + ":");
+                for (UpdateRecord record : entry.getValue()) {
+                    pw.println("      " + record);
+                }
+            }
+            pw.println("  Last Known Locations:");
+            for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
+                String provider = entry.getKey();
+                Location location = entry.getValue();
+                pw.println("    " + provider + ": " + location);
+            }
+
+            pw.println("  Last Known Locations Coarse Intervals:");
+            for (Map.Entry<String, Location> entry : mLastLocationCoarseInterval.entrySet()) {
+                String provider = entry.getKey();
+                Location location = entry.getValue();
+                pw.println("    " + provider + ": " + location);
+            }
+
+            mGeofenceManager.dump(pw);
+
+            if (mEnabledProviders.size() > 0) {
+                pw.println("  Enabled Providers:");
+                for (String i : mEnabledProviders) {
+                    pw.println("    " + i);
+                }
+
+            }
+            if (mDisabledProviders.size() > 0) {
+                pw.println("  Disabled Providers:");
+                for (String i : mDisabledProviders) {
+                    pw.println("    " + i);
+                }
+            }
+            pw.append("  ");
+            mBlacklist.dump(pw);
+            if (mMockProviders.size() > 0) {
+                pw.println("  Mock Providers:");
+                for (Map.Entry<String, MockProvider> i : mMockProviders.entrySet()) {
+                    i.getValue().dump(pw, "      ");
+                }
+            }
+
+            pw.append("  fudger: ");
+            mLocationFudger.dump(fd, pw,  args);
+
+            if (args.length > 0 && "short".equals(args[0])) {
+                return;
+            }
+            for (LocationProviderInterface provider: mProviders) {
+                pw.print(provider.getName() + " Internal State");
+                if (provider instanceof LocationProviderProxy) {
+                    LocationProviderProxy proxy = (LocationProviderProxy) provider;
+                    pw.print(" (" + proxy.getConnectedPackageName() + ")");
+                }
+                pw.println(":");
+                provider.dump(fd, pw, args);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
new file mode 100644
index 0000000..35e7afa
--- /dev/null
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManagerNative;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+
+import static android.content.Context.USER_SERVICE;
+import static android.Manifest.permission.READ_PROFILE;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteStatement;
+import android.media.AudioManager;
+import android.media.AudioService;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.widget.ILockSettings;
+import com.android.internal.widget.LockPatternUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Keeps the lock pattern/password data and related settings for each user.
+ * Used by LockPatternUtils. Needs to be a service because Settings app also needs
+ * to be able to save lockscreen information for secondary users.
+ * @hide
+ */
+public class LockSettingsService extends ILockSettings.Stub {
+
+    private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE";
+    private final DatabaseHelper mOpenHelper;
+    private static final String TAG = "LockSettingsService";
+
+    private static final String TABLE = "locksettings";
+    private static final String COLUMN_KEY = "name";
+    private static final String COLUMN_USERID = "user";
+    private static final String COLUMN_VALUE = "value";
+
+    private static final String[] COLUMNS_FOR_QUERY = {
+        COLUMN_VALUE
+    };
+
+    private static final String SYSTEM_DIRECTORY = "/system/";
+    private static final String LOCK_PATTERN_FILE = "gesture.key";
+    private static final String LOCK_PASSWORD_FILE = "password.key";
+
+    private final Context mContext;
+    private LockPatternUtils mLockPatternUtils;
+
+    public LockSettingsService(Context context) {
+        mContext = context;
+        // Open the database
+        mOpenHelper = new DatabaseHelper(mContext);
+
+        mLockPatternUtils = new LockPatternUtils(context);
+    }
+
+    public void systemReady() {
+        migrateOldData();
+    }
+
+    private void migrateOldData() {
+        try {
+            // These Settings moved before multi-user was enabled, so we only have to do it for the
+            // root user.
+            if (getString("migrated", null, 0) == null) {
+                final ContentResolver cr = mContext.getContentResolver();
+                for (String validSetting : VALID_SETTINGS) {
+                    String value = Settings.Secure.getString(cr, validSetting);
+                    if (value != null) {
+                        setString(validSetting, value, 0);
+                    }
+                }
+                // No need to move the password / pattern files. They're already in the right place.
+                setString("migrated", "true", 0);
+                Slog.i(TAG, "Migrated lock settings to new location");
+            }
+
+            // These Settings changed after multi-user was enabled, hence need to be moved per user.
+            if (getString("migrated_user_specific", null, 0) == null) {
+                final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+                final ContentResolver cr = mContext.getContentResolver();
+                List<UserInfo> users = um.getUsers();
+                for (int user = 0; user < users.size(); user++) {
+                    // Migrate owner info
+                    final int userId = users.get(user).id;
+                    final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+                    String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+                    if (ownerInfo != null) {
+                        setString(OWNER_INFO, ownerInfo, userId);
+                        Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
+                    }
+
+                    // Migrate owner info enabled.  Note there was a bug where older platforms only
+                    // stored this value if the checkbox was toggled at least once. The code detects
+                    // this case by handling the exception.
+                    final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+                    boolean enabled;
+                    try {
+                        int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+                        enabled = ivalue != 0;
+                        setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+                    } catch (SettingNotFoundException e) {
+                        // Setting was never stored. Store it if the string is not empty.
+                        if (!TextUtils.isEmpty(ownerInfo)) {
+                            setLong(OWNER_INFO_ENABLED, 1, userId);
+                        }
+                    }
+                    Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+                }
+                // No need to move the password / pattern files. They're already in the right place.
+                setString("migrated_user_specific", "true", 0);
+                Slog.i(TAG, "Migrated per-user lock settings to new location");
+            }
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Unable to migrate old data", re);
+        }
+    }
+
+    private final void checkWritePermission(int userId) {
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsWrite");
+    }
+
+    private final void checkPasswordReadPermission(int userId) {
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "LockSettingsRead");
+    }
+
+    private final void checkReadPermission(String requestedKey, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        for (int i = 0; i < READ_PROFILE_PROTECTED_SETTINGS.length; i++) {
+            String key = READ_PROFILE_PROTECTED_SETTINGS[i];
+            if (key.equals(requestedKey) && mContext.checkCallingOrSelfPermission(READ_PROFILE)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("uid=" + callingUid
+                        + " needs permission " + READ_PROFILE + " to read "
+                        + requestedKey + " for user " + userId);
+            }
+        }
+    }
+
+    @Override
+    public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, value ? "1" : "0", userId);
+    }
+
+    @Override
+    public void setLong(String key, long value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, Long.toString(value), userId);
+    }
+
+    @Override
+    public void setString(String key, String value, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        writeToDb(key, value, userId);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException {
+        checkReadPermission(key, userId);
+
+        String value = readFromDb(key, null, userId);
+        return TextUtils.isEmpty(value) ?
+                defaultValue : (value.equals("1") || value.equals("true"));
+    }
+
+    @Override
+    public long getLong(String key, long defaultValue, int userId) throws RemoteException {
+        checkReadPermission(key, userId);
+
+        String value = readFromDb(key, null, userId);
+        return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
+    }
+
+    @Override
+    public String getString(String key, String defaultValue, int userId) throws RemoteException {
+        checkReadPermission(key, userId);
+
+        return readFromDb(key, defaultValue, userId);
+    }
+
+    private String getLockPatternFilename(int userId) {
+        String dataSystemDirectory =
+                android.os.Environment.getDataDirectory().getAbsolutePath() +
+                SYSTEM_DIRECTORY;
+        if (userId == 0) {
+            // Leave it in the same place for user 0
+            return dataSystemDirectory + LOCK_PATTERN_FILE;
+        } else {
+            return  new File(Environment.getUserSystemDirectory(userId), LOCK_PATTERN_FILE)
+                    .getAbsolutePath();
+        }
+    }
+
+    private String getLockPasswordFilename(int userId) {
+        String dataSystemDirectory =
+                android.os.Environment.getDataDirectory().getAbsolutePath() +
+                SYSTEM_DIRECTORY;
+        if (userId == 0) {
+            // Leave it in the same place for user 0
+            return dataSystemDirectory + LOCK_PASSWORD_FILE;
+        } else {
+            return  new File(Environment.getUserSystemDirectory(userId), LOCK_PASSWORD_FILE)
+                    .getAbsolutePath();
+        }
+    }
+
+    @Override
+    public boolean havePassword(int userId) throws RemoteException {
+        // Do we need a permissions check here?
+
+        return new File(getLockPasswordFilename(userId)).length() > 0;
+    }
+
+    @Override
+    public boolean havePattern(int userId) throws RemoteException {
+        // Do we need a permissions check here?
+
+        return new File(getLockPatternFilename(userId)).length() > 0;
+    }
+
+    private void maybeUpdateKeystore(String password, int userId) {
+        if (userId == UserHandle.USER_OWNER) {
+            final KeyStore keyStore = KeyStore.getInstance();
+            // Conditionally reset the keystore if empty. If non-empty, we are just
+            // switching key guard type
+            if (TextUtils.isEmpty(password) && keyStore.isEmpty()) {
+                keyStore.reset();
+            } else {
+                // Update the keystore password
+                keyStore.password(password);
+            }
+        }
+    }
+
+    @Override
+    public void setLockPattern(String pattern, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        maybeUpdateKeystore(pattern, userId);
+
+        final byte[] hash = LockPatternUtils.patternToHash(
+                LockPatternUtils.stringToPattern(pattern));
+        writeFile(getLockPatternFilename(userId), hash);
+    }
+
+    @Override
+    public void setLockPassword(String password, int userId) throws RemoteException {
+        checkWritePermission(userId);
+
+        maybeUpdateKeystore(password, userId);
+
+        writeFile(getLockPasswordFilename(userId), mLockPatternUtils.passwordToHash(password));
+    }
+
+    @Override
+    public boolean checkPattern(String pattern, int userId) throws RemoteException {
+        checkPasswordReadPermission(userId);
+        try {
+            // Read all the bytes from the file
+            RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r");
+            final byte[] stored = new byte[(int) raf.length()];
+            int got = raf.read(stored, 0, stored.length);
+            raf.close();
+            if (got <= 0) {
+                return true;
+            }
+            // Compare the hash from the file with the entered pattern's hash
+            final byte[] hash = LockPatternUtils.patternToHash(
+                    LockPatternUtils.stringToPattern(pattern));
+            final boolean matched = Arrays.equals(stored, hash);
+            if (matched && !TextUtils.isEmpty(pattern)) {
+                maybeUpdateKeystore(pattern, userId);
+            }
+            return matched;
+        } catch (FileNotFoundException fnfe) {
+            Slog.e(TAG, "Cannot read file " + fnfe);
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Cannot read file " + ioe);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean checkPassword(String password, int userId) throws RemoteException {
+        checkPasswordReadPermission(userId);
+
+        try {
+            // Read all the bytes from the file
+            RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r");
+            final byte[] stored = new byte[(int) raf.length()];
+            int got = raf.read(stored, 0, stored.length);
+            raf.close();
+            if (got <= 0) {
+                return true;
+            }
+            // Compare the hash from the file with the entered password's hash
+            final byte[] hash = mLockPatternUtils.passwordToHash(password);
+            final boolean matched = Arrays.equals(stored, hash);
+            if (matched && !TextUtils.isEmpty(password)) {
+                maybeUpdateKeystore(password, userId);
+            }
+            return matched;
+        } catch (FileNotFoundException fnfe) {
+            Slog.e(TAG, "Cannot read file " + fnfe);
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Cannot read file " + ioe);
+        }
+        return true;
+    }
+
+    @Override
+    public void removeUser(int userId) {
+        checkWritePermission(userId);
+
+        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        try {
+            File file = new File(getLockPasswordFilename(userId));
+            if (file.exists()) {
+                file.delete();
+            }
+            file = new File(getLockPatternFilename(userId));
+            if (file.exists()) {
+                file.delete();
+            }
+
+            db.beginTransaction();
+            db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private void writeFile(String name, byte[] hash) {
+        try {
+            // Write the hash to file
+            RandomAccessFile raf = new RandomAccessFile(name, "rw");
+            // Truncate the file if pattern is null, to clear the lock
+            if (hash == null || hash.length == 0) {
+                raf.setLength(0);
+            } else {
+                raf.write(hash, 0, hash.length);
+            }
+            raf.close();
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Error writing to file " + ioe);
+        }
+    }
+
+    private void writeToDb(String key, String value, int userId) {
+        writeToDb(mOpenHelper.getWritableDatabase(), key, value, userId);
+    }
+
+    private void writeToDb(SQLiteDatabase db, String key, String value, int userId) {
+        ContentValues cv = new ContentValues();
+        cv.put(COLUMN_KEY, key);
+        cv.put(COLUMN_USERID, userId);
+        cv.put(COLUMN_VALUE, value);
+
+        db.beginTransaction();
+        try {
+            db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?",
+                    new String[] {key, Integer.toString(userId)});
+            db.insert(TABLE, null, cv);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+    }
+
+    private String readFromDb(String key, String defaultValue, int userId) {
+        Cursor cursor;
+        String result = defaultValue;
+        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY,
+                COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?",
+                new String[] { Integer.toString(userId), key },
+                null, null, null)) != null) {
+            if (cursor.moveToFirst()) {
+                result = cursor.getString(0);
+            }
+            cursor.close();
+        }
+        return result;
+    }
+
+    class DatabaseHelper extends SQLiteOpenHelper {
+        private static final String TAG = "LockSettingsDB";
+        private static final String DATABASE_NAME = "locksettings.db";
+
+        private static final int DATABASE_VERSION = 2;
+
+        public DatabaseHelper(Context context) {
+            super(context, DATABASE_NAME, null, DATABASE_VERSION);
+            setWriteAheadLoggingEnabled(true);
+        }
+
+        private void createTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE + " (" +
+                    "_id INTEGER PRIMARY KEY AUTOINCREMENT," +
+                    COLUMN_KEY + " TEXT," +
+                    COLUMN_USERID + " INTEGER," +
+                    COLUMN_VALUE + " TEXT" +
+                    ");");
+        }
+
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            createTable(db);
+            initializeDefaults(db);
+        }
+
+        private void initializeDefaults(SQLiteDatabase db) {
+            // Get the lockscreen default from a system property, if available
+            boolean lockScreenDisable = SystemProperties.getBoolean("ro.lockscreen.disable.default",
+                    false);
+            if (lockScreenDisable) {
+                writeToDb(db, LockPatternUtils.DISABLE_LOCKSCREEN_KEY, "1", 0);
+            }
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) {
+            int upgradeVersion = oldVersion;
+            if (upgradeVersion == 1) {
+                // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED}
+                // during upgrade based on whether each user previously had widgets in keyguard.
+                maybeEnableWidgetSettingForUsers(db);
+                upgradeVersion = 2;
+            }
+
+            if (upgradeVersion != DATABASE_VERSION) {
+                Log.w(TAG, "Failed to upgrade database!");
+            }
+        }
+
+        private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) {
+            final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE);
+            final ContentResolver cr = mContext.getContentResolver();
+            final List<UserInfo> users = um.getUsers();
+            for (int i = 0; i < users.size(); i++) {
+                final int userId = users.get(i).id;
+                final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId);
+                Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled="
+                        + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets());
+                loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled);
+            }
+        }
+
+        private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) {
+            SQLiteStatement stmt = null;
+            try {
+                stmt = db.compileStatement(
+                        "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);");
+                stmt.bindString(1, key);
+                stmt.bindLong(2, userId);
+                stmt.bindLong(3, value ? 1 : 0);
+                stmt.execute();
+            } finally {
+                if (stmt != null) stmt.close();
+            }
+        }
+    }
+
+    private static final String[] VALID_SETTINGS = new String[] {
+        LockPatternUtils.LOCKOUT_PERMANENT_KEY,
+        LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE,
+        LockPatternUtils.PATTERN_EVER_CHOSEN_KEY,
+        LockPatternUtils.PASSWORD_TYPE_KEY,
+        LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+        LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
+        LockPatternUtils.DISABLE_LOCKSCREEN_KEY,
+        LockPatternUtils.LOCKSCREEN_OPTIONS,
+        LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
+        LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY,
+        LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS,
+        LockPatternUtils.PASSWORD_HISTORY_KEY,
+        Secure.LOCK_PATTERN_ENABLED,
+        Secure.LOCK_BIOMETRIC_WEAK_FLAGS,
+        Secure.LOCK_PATTERN_VISIBLE,
+        Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED
+    };
+
+    // These are protected with a read permission
+    private static final String[] READ_PROFILE_PROTECTED_SETTINGS = new String[] {
+        Secure.LOCK_SCREEN_OWNER_INFO_ENABLED,
+        Secure.LOCK_SCREEN_OWNER_INFO
+    };
+}
diff --git a/services/core/java/com/android/server/MasterClearReceiver.java b/services/core/java/com/android/server/MasterClearReceiver.java
new file mode 100644
index 0000000..86f57d1
--- /dev/null
+++ b/services/core/java/com/android/server/MasterClearReceiver.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RecoverySystem;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.IOException;
+
+public class MasterClearReceiver extends BroadcastReceiver {
+    private static final String TAG = "MasterClear";
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (intent.getAction().equals(Intent.ACTION_REMOTE_INTENT)) {
+            if (!"google.com".equals(intent.getStringExtra("from"))) {
+                Slog.w(TAG, "Ignoring master clear request -- not from trusted server.");
+                return;
+            }
+        }
+
+        Slog.w(TAG, "!!! FACTORY RESET !!!");
+        // The reboot call is blocking, so we need to do it on another thread.
+        Thread thr = new Thread("Reboot") {
+            @Override
+            public void run() {
+                try {
+                    RecoverySystem.rebootWipeUserData(context);
+                    Log.wtf(TAG, "Still running after master clear?!");
+                } catch (IOException e) {
+                    Slog.e(TAG, "Can't perform master clear/factory reset", e);
+                }
+            }
+        };
+        thr.start();
+    }
+}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
new file mode 100644
index 0000000..e60231a
--- /dev/null
+++ b/services/core/java/com/android/server/MountService.java
@@ -0,0 +1,2833 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.ObbInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Environment.UserEnvironment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.storage.IMountService;
+import android.os.storage.IMountServiceListener;
+import android.os.storage.IMountShutdownObserver;
+import android.os.storage.IObbActionListener;
+import android.os.storage.OnObbStateChangeListener;
+import android.os.storage.StorageResultCode;
+import android.os.storage.StorageVolume;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.NativeDaemonConnector.SensitiveArg;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.UserManagerService;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.KeySpec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+
+/**
+ * MountService implements back-end services for platform storage
+ * management.
+ * @hide - Applications should use android.os.storage.StorageManager
+ * to access the MountService.
+ */
+class MountService extends IMountService.Stub
+        implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+
+    // TODO: listen for user creation/deletion
+
+    private static final boolean LOCAL_LOGD = false;
+    private static final boolean DEBUG_UNMOUNT = false;
+    private static final boolean DEBUG_EVENTS = false;
+    private static final boolean DEBUG_OBB = false;
+
+    // Disable this since it messes up long-running cryptfs operations.
+    private static final boolean WATCHDOG_ENABLE = false;
+
+    private static final String TAG = "MountService";
+
+    private static final String VOLD_TAG = "VoldConnector";
+
+    /** Maximum number of ASEC containers allowed to be mounted. */
+    private static final int MAX_CONTAINERS = 250;
+
+    /*
+     * Internal vold volume state constants
+     */
+    class VolumeState {
+        public static final int Init       = -1;
+        public static final int NoMedia    = 0;
+        public static final int Idle       = 1;
+        public static final int Pending    = 2;
+        public static final int Checking   = 3;
+        public static final int Mounted    = 4;
+        public static final int Unmounting = 5;
+        public static final int Formatting = 6;
+        public static final int Shared     = 7;
+        public static final int SharedMnt  = 8;
+    }
+
+    /*
+     * Internal vold response code constants
+     */
+    class VoldResponseCode {
+        /*
+         * 100 series - Requestion action was initiated; expect another reply
+         *              before proceeding with a new command.
+         */
+        public static final int VolumeListResult               = 110;
+        public static final int AsecListResult                 = 111;
+        public static final int StorageUsersListResult         = 112;
+
+        /*
+         * 200 series - Requestion action has been successfully completed.
+         */
+        public static final int ShareStatusResult              = 210;
+        public static final int AsecPathResult                 = 211;
+        public static final int ShareEnabledResult             = 212;
+
+        /*
+         * 400 series - Command was accepted, but the requested action
+         *              did not take place.
+         */
+        public static final int OpFailedNoMedia                = 401;
+        public static final int OpFailedMediaBlank             = 402;
+        public static final int OpFailedMediaCorrupt           = 403;
+        public static final int OpFailedVolNotMounted          = 404;
+        public static final int OpFailedStorageBusy            = 405;
+        public static final int OpFailedStorageNotFound        = 406;
+
+        /*
+         * 600 series - Unsolicited broadcasts.
+         */
+        public static final int VolumeStateChange              = 605;
+        public static final int VolumeUuidChange               = 613;
+        public static final int VolumeUserLabelChange          = 614;
+        public static final int VolumeDiskInserted             = 630;
+        public static final int VolumeDiskRemoved              = 631;
+        public static final int VolumeBadRemoval               = 632;
+
+        /*
+         * 700 series - fstrim
+         */
+        public static final int FstrimCompleted                = 700;
+    }
+
+    private Context mContext;
+    private NativeDaemonConnector mConnector;
+
+    private final Object mVolumesLock = new Object();
+
+    /** When defined, base template for user-specific {@link StorageVolume}. */
+    private StorageVolume mEmulatedTemplate;
+
+    // TODO: separate storage volumes on per-user basis
+
+    @GuardedBy("mVolumesLock")
+    private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
+    /** Map from path to {@link StorageVolume} */
+    @GuardedBy("mVolumesLock")
+    private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap();
+    /** Map from path to state */
+    @GuardedBy("mVolumesLock")
+    private final HashMap<String, String> mVolumeStates = Maps.newHashMap();
+
+    private volatile boolean mSystemReady = false;
+
+    private PackageManagerService                 mPms;
+    private boolean                               mUmsEnabling;
+    private boolean                               mUmsAvailable = false;
+    // Used as a lock for methods that register/unregister listeners.
+    final private ArrayList<MountServiceBinderListener> mListeners =
+            new ArrayList<MountServiceBinderListener>();
+    private final CountDownLatch mConnectedSignal = new CountDownLatch(1);
+    private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
+    private boolean                               mSendUmsConnectedOnBoot = false;
+
+    /**
+     * Private hash of currently mounted secure containers.
+     * Used as a lock in methods to manipulate secure containers.
+     */
+    final private HashSet<String> mAsecMountSet = new HashSet<String>();
+
+    /**
+     * The size of the crypto algorithm key in bits for OBB files. Currently
+     * Twofish is used which takes 128-bit keys.
+     */
+    private static final int CRYPTO_ALGORITHM_KEY_SIZE = 128;
+
+    /**
+     * The number of times to run SHA1 in the PBKDF2 function for OBB files.
+     * 1024 is reasonably secure and not too slow.
+     */
+    private static final int PBKDF2_HASH_ROUNDS = 1024;
+
+    /**
+     * Mounted OBB tracking information. Used to track the current state of all
+     * OBBs.
+     */
+    final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
+
+    /** Map from raw paths to {@link ObbState}. */
+    final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
+
+    class ObbState implements IBinder.DeathRecipient {
+        public ObbState(String rawPath, String canonicalPath, int callingUid,
+                IObbActionListener token, int nonce) {
+            this.rawPath = rawPath;
+            this.canonicalPath = canonicalPath.toString();
+
+            final int userId = UserHandle.getUserId(callingUid);
+            this.ownerPath = buildObbPath(canonicalPath, userId, false);
+            this.voldPath = buildObbPath(canonicalPath, userId, true);
+
+            this.ownerGid = UserHandle.getSharedAppGid(callingUid);
+            this.token = token;
+            this.nonce = nonce;
+        }
+
+        final String rawPath;
+        final String canonicalPath;
+        final String ownerPath;
+        final String voldPath;
+
+        final int ownerGid;
+
+        // Token of remote Binder caller
+        final IObbActionListener token;
+
+        // Identifier to pass back to the token
+        final int nonce;
+
+        public IBinder getBinder() {
+            return token.asBinder();
+        }
+
+        @Override
+        public void binderDied() {
+            ObbAction action = new UnmountObbAction(this, true);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+        }
+
+        public void link() throws RemoteException {
+            getBinder().linkToDeath(this, 0);
+        }
+
+        public void unlink() {
+            getBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder("ObbState{");
+            sb.append("rawPath=").append(rawPath);
+            sb.append(",canonicalPath=").append(canonicalPath);
+            sb.append(",ownerPath=").append(ownerPath);
+            sb.append(",voldPath=").append(voldPath);
+            sb.append(",ownerGid=").append(ownerGid);
+            sb.append(",token=").append(token);
+            sb.append(",binder=").append(getBinder());
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    // OBB Action Handler
+    final private ObbActionHandler mObbActionHandler;
+
+    // OBB action handler messages
+    private static final int OBB_RUN_ACTION = 1;
+    private static final int OBB_MCS_BOUND = 2;
+    private static final int OBB_MCS_UNBIND = 3;
+    private static final int OBB_MCS_RECONNECT = 4;
+    private static final int OBB_FLUSH_MOUNT_STATE = 5;
+
+    /*
+     * Default Container Service information
+     */
+    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
+            "com.android.defcontainer", "com.android.defcontainer.DefaultContainerService");
+
+    final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
+
+    class DefaultContainerConnection implements ServiceConnection {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "onServiceConnected");
+            IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "onServiceDisconnected");
+        }
+    };
+
+    // Used in the ObbActionHandler
+    private IMediaContainerService mContainerService = null;
+
+    // Handler messages
+    private static final int H_UNMOUNT_PM_UPDATE = 1;
+    private static final int H_UNMOUNT_PM_DONE = 2;
+    private static final int H_UNMOUNT_MS = 3;
+    private static final int H_SYSTEM_READY = 4;
+
+    private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
+    private static final int MAX_UNMOUNT_RETRIES = 4;
+
+    class UnmountCallBack {
+        final String path;
+        final boolean force;
+        final boolean removeEncryption;
+        int retries;
+
+        UnmountCallBack(String path, boolean force, boolean removeEncryption) {
+            retries = 0;
+            this.path = path;
+            this.force = force;
+            this.removeEncryption = removeEncryption;
+        }
+
+        void handleFinished() {
+            if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
+            doUnmountVolume(path, true, removeEncryption);
+        }
+    }
+
+    class UmsEnableCallBack extends UnmountCallBack {
+        final String method;
+
+        UmsEnableCallBack(String path, String method, boolean force) {
+            super(path, force, false);
+            this.method = method;
+        }
+
+        @Override
+        void handleFinished() {
+            super.handleFinished();
+            doShareUnshareVolume(path, method, true);
+        }
+    }
+
+    class ShutdownCallBack extends UnmountCallBack {
+        IMountShutdownObserver observer;
+        ShutdownCallBack(String path, IMountShutdownObserver observer) {
+            super(path, true, false);
+            this.observer = observer;
+        }
+
+        @Override
+        void handleFinished() {
+            int ret = doUnmountVolume(path, true, removeEncryption);
+            if (observer != null) {
+                try {
+                    observer.onShutDownComplete(ret);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "RemoteException when shutting down");
+                }
+            }
+        }
+    }
+
+    class MountServiceHandler extends Handler {
+        ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
+        boolean mUpdatingStatus = false;
+
+        MountServiceHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case H_UNMOUNT_PM_UPDATE: {
+                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
+                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
+                    mForceUnmounts.add(ucb);
+                    if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
+                    // Register only if needed.
+                    if (!mUpdatingStatus) {
+                        if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
+                        mUpdatingStatus = true;
+                        mPms.updateExternalMediaStatus(false, true);
+                    }
+                    break;
+                }
+                case H_UNMOUNT_PM_DONE: {
+                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
+                    if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
+                    mUpdatingStatus = false;
+                    int size = mForceUnmounts.size();
+                    int sizeArr[] = new int[size];
+                    int sizeArrN = 0;
+                    // Kill processes holding references first
+                    ActivityManagerService ams = (ActivityManagerService)
+                    ServiceManager.getService("activity");
+                    for (int i = 0; i < size; i++) {
+                        UnmountCallBack ucb = mForceUnmounts.get(i);
+                        String path = ucb.path;
+                        boolean done = false;
+                        if (!ucb.force) {
+                            done = true;
+                        } else {
+                            int pids[] = getStorageUsers(path);
+                            if (pids == null || pids.length == 0) {
+                                done = true;
+                            } else {
+                                // Eliminate system process here?
+                                ams.killPids(pids, "unmount media", true);
+                                // Confirm if file references have been freed.
+                                pids = getStorageUsers(path);
+                                if (pids == null || pids.length == 0) {
+                                    done = true;
+                                }
+                            }
+                        }
+                        if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
+                            // Retry again
+                            Slog.i(TAG, "Retrying to kill storage users again");
+                            mHandler.sendMessageDelayed(
+                                    mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
+                                            ucb.retries++),
+                                    RETRY_UNMOUNT_DELAY);
+                        } else {
+                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
+                                Slog.i(TAG, "Failed to unmount media inspite of " +
+                                        MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
+                            }
+                            sizeArr[sizeArrN++] = i;
+                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
+                                    ucb));
+                        }
+                    }
+                    // Remove already processed elements from list.
+                    for (int i = (sizeArrN-1); i >= 0; i--) {
+                        mForceUnmounts.remove(sizeArr[i]);
+                    }
+                    break;
+                }
+                case H_UNMOUNT_MS: {
+                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
+                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
+                    ucb.handleFinished();
+                    break;
+                }
+                case H_SYSTEM_READY: {
+                    try {
+                        handleSystemReady();
+                    } catch (Exception ex) {
+                        Slog.e(TAG, "Boot-time mount exception", ex);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+
+    private final Handler mHandler;
+
+    void waitForAsecScan() {
+        waitForLatch(mAsecsScanned);
+    }
+
+    private void waitForReady() {
+        waitForLatch(mConnectedSignal);
+    }
+
+    private void waitForLatch(CountDownLatch latch) {
+        for (;;) {
+            try {
+                if (latch.await(5000, TimeUnit.MILLISECONDS)) {
+                    return;
+                } else {
+                    Slog.w(TAG, "Thread " + Thread.currentThread().getName()
+                            + " still waiting for MountService ready...");
+                }
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
+            }
+        }
+    }
+
+    private void handleSystemReady() {
+        // Snapshot current volume states since it's not safe to call into vold
+        // while holding locks.
+        final HashMap<String, String> snapshot;
+        synchronized (mVolumesLock) {
+            snapshot = new HashMap<String, String>(mVolumeStates);
+        }
+
+        for (Map.Entry<String, String> entry : snapshot.entrySet()) {
+            final String path = entry.getKey();
+            final String state = entry.getValue();
+
+            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+                int rc = doMountVolume(path);
+                if (rc != StorageResultCode.OperationSucceeded) {
+                    Slog.e(TAG, String.format("Boot-time mount failed (%d)",
+                            rc));
+                }
+            } else if (state.equals(Environment.MEDIA_SHARED)) {
+                /*
+                 * Bootstrap UMS enabled state since vold indicates
+                 * the volume is shared (runtime restart while ums enabled)
+                 */
+                notifyVolumeStateChange(null, path, VolumeState.NoMedia,
+                        VolumeState.Shared);
+            }
+        }
+
+        // Push mounted state for all emulated storage
+        synchronized (mVolumesLock) {
+            for (StorageVolume volume : mVolumes) {
+                if (volume.isEmulated()) {
+                    updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
+                }
+            }
+        }
+
+        /*
+         * If UMS was connected on boot, send the connected event
+         * now that we're up.
+         */
+        if (mSendUmsConnectedOnBoot) {
+            sendUmsIntent(true);
+            mSendUmsConnectedOnBoot = false;
+        }
+    }
+
+    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+            final UserHandle user = new UserHandle(userId);
+
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
+                synchronized (mVolumesLock) {
+                    createEmulatedVolumeForUserLocked(user);
+                }
+
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                synchronized (mVolumesLock) {
+                    final List<StorageVolume> toRemove = Lists.newArrayList();
+                    for (StorageVolume volume : mVolumes) {
+                        if (user.equals(volume.getOwner())) {
+                            toRemove.add(volume);
+                        }
+                    }
+                    for (StorageVolume volume : toRemove) {
+                        removeVolumeLocked(volume);
+                    }
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
+                    intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
+            notifyShareAvailabilityChange(available);
+        }
+    };
+
+    private final BroadcastReceiver mIdleMaintenanceReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            waitForReady();
+            String action = intent.getAction();
+            // Since fstrim will be run on a daily basis we do not expect
+            // fstrim to be too long, so it is not interruptible. We will
+            // implement interruption only in case we see issues.
+            if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action)) {
+                try {
+                    // This method runs on the handler thread,
+                    // so it is safe to directly call into vold.
+                    mConnector.execute("fstrim", "dotrim");
+                    EventLogTags.writeFstrimStart(SystemClock.elapsedRealtime());
+                } catch (NativeDaemonConnectorException ndce) {
+                    Slog.e(TAG, "Failed to run fstrim!");
+                }
+            }
+        }
+    };
+
+    private final class MountServiceBinderListener implements IBinder.DeathRecipient {
+        final IMountServiceListener mListener;
+
+        MountServiceBinderListener(IMountServiceListener listener) {
+            mListener = listener;
+
+        }
+
+        public void binderDied() {
+            if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
+            synchronized (mListeners) {
+                mListeners.remove(this);
+                mListener.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+    }
+
+    private void doShareUnshareVolume(String path, String method, boolean enable) {
+        // TODO: Add support for multiple share methods
+        if (!method.equals("ums")) {
+            throw new IllegalArgumentException(String.format("Method %s not supported", method));
+        }
+
+        try {
+            mConnector.execute("volume", enable ? "share" : "unshare", path, method);
+        } catch (NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to share/unshare", e);
+        }
+    }
+
+    private void updatePublicVolumeState(StorageVolume volume, String state) {
+        final String path = volume.getPath();
+        final String oldState;
+        synchronized (mVolumesLock) {
+            oldState = mVolumeStates.put(path, state);
+            volume.setState(state);
+        }
+
+        if (state.equals(oldState)) {
+            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
+                    state, state, path));
+            return;
+        }
+
+        Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
+
+        // Tell PackageManager about changes to primary volume state, but only
+        // when not emulated.
+        if (volume.isPrimary() && !volume.isEmulated()) {
+            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(false, false);
+
+                /*
+                 * Some OBBs might have been unmounted when this volume was
+                 * unmounted, so send a message to the handler to let it know to
+                 * remove those from the list of mounted OBBS.
+                 */
+                mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+                        OBB_FLUSH_MOUNT_STATE, path));
+            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(true, false);
+            }
+        }
+
+        synchronized (mListeners) {
+            for (int i = mListeners.size() -1; i >= 0; i--) {
+                MountServiceBinderListener bl = mListeners.get(i);
+                try {
+                    bl.mListener.onStorageStateChanged(path, oldState, state);
+                } catch (RemoteException rex) {
+                    Slog.e(TAG, "Listener dead");
+                    mListeners.remove(i);
+                } catch (Exception ex) {
+                    Slog.e(TAG, "Listener failed", ex);
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback from NativeDaemonConnector
+     */
+    public void onDaemonConnected() {
+        /*
+         * Since we'll be calling back into the NativeDaemonConnector,
+         * we need to do our work in a new thread.
+         */
+        new Thread("MountService#onDaemonConnected") {
+            @Override
+            public void run() {
+                /**
+                 * Determine media state and UMS detection status
+                 */
+                try {
+                    final String[] vols = NativeDaemonEvent.filterMessageList(
+                            mConnector.executeForList("volume", "list"),
+                            VoldResponseCode.VolumeListResult);
+                    for (String volstr : vols) {
+                        String[] tok = volstr.split(" ");
+                        // FMT: <label> <mountpoint> <state>
+                        String path = tok[1];
+                        String state = Environment.MEDIA_REMOVED;
+
+                        final StorageVolume volume;
+                        synchronized (mVolumesLock) {
+                            volume = mVolumesByPath.get(path);
+                        }
+
+                        int st = Integer.parseInt(tok[2]);
+                        if (st == VolumeState.NoMedia) {
+                            state = Environment.MEDIA_REMOVED;
+                        } else if (st == VolumeState.Idle) {
+                            state = Environment.MEDIA_UNMOUNTED;
+                        } else if (st == VolumeState.Mounted) {
+                            state = Environment.MEDIA_MOUNTED;
+                            Slog.i(TAG, "Media already mounted on daemon connection");
+                        } else if (st == VolumeState.Shared) {
+                            state = Environment.MEDIA_SHARED;
+                            Slog.i(TAG, "Media shared on daemon connection");
+                        } else {
+                            throw new Exception(String.format("Unexpected state %d", st));
+                        }
+
+                        if (state != null) {
+                            if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
+                            updatePublicVolumeState(volume, state);
+                        }
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Error processing initial volume state", e);
+                    final StorageVolume primary = getPrimaryPhysicalVolume();
+                    if (primary != null) {
+                        updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
+                    }
+                }
+
+                /*
+                 * Now that we've done our initialization, release
+                 * the hounds!
+                 */
+                mConnectedSignal.countDown();
+
+                // Let package manager load internal ASECs.
+                mPms.scanAvailableAsecs();
+
+                // Notify people waiting for ASECs to be scanned that it's done.
+                mAsecsScanned.countDown();
+            }
+        }.start();
+    }
+
+    /**
+     * Callback from NativeDaemonConnector
+     */
+    public boolean onEvent(int code, String raw, String[] cooked) {
+        if (DEBUG_EVENTS) {
+            StringBuilder builder = new StringBuilder();
+            builder.append("onEvent::");
+            builder.append(" raw= " + raw);
+            if (cooked != null) {
+                builder.append(" cooked = " );
+                for (String str : cooked) {
+                    builder.append(" " + str);
+                }
+            }
+            Slog.i(TAG, builder.toString());
+        }
+        if (code == VoldResponseCode.VolumeStateChange) {
+            /*
+             * One of the volumes we're managing has changed state.
+             * Format: "NNN Volume <label> <path> state changed
+             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
+             */
+            notifyVolumeStateChange(
+                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
+                            Integer.parseInt(cooked[10]));
+        } else if (code == VoldResponseCode.VolumeUuidChange) {
+            // Format: nnn <label> <path> <uuid>
+            final String path = cooked[2];
+            final String uuid = (cooked.length > 3) ? cooked[3] : null;
+
+            final StorageVolume vol = mVolumesByPath.get(path);
+            if (vol != null) {
+                vol.setUuid(uuid);
+            }
+
+        } else if (code == VoldResponseCode.VolumeUserLabelChange) {
+            // Format: nnn <label> <path> <label>
+            final String path = cooked[2];
+            final String userLabel = (cooked.length > 3) ? cooked[3] : null;
+
+            final StorageVolume vol = mVolumesByPath.get(path);
+            if (vol != null) {
+                vol.setUserLabel(userLabel);
+            }
+
+        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
+                   (code == VoldResponseCode.VolumeDiskRemoved) ||
+                   (code == VoldResponseCode.VolumeBadRemoval)) {
+            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
+            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
+            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
+            String action = null;
+            final String label = cooked[2];
+            final String path = cooked[3];
+            int major = -1;
+            int minor = -1;
+
+            try {
+                String devComp = cooked[6].substring(1, cooked[6].length() -1);
+                String[] devTok = devComp.split(":");
+                major = Integer.parseInt(devTok[0]);
+                minor = Integer.parseInt(devTok[1]);
+            } catch (Exception ex) {
+                Slog.e(TAG, "Failed to parse major/minor", ex);
+            }
+
+            final StorageVolume volume;
+            final String state;
+            synchronized (mVolumesLock) {
+                volume = mVolumesByPath.get(path);
+                state = mVolumeStates.get(path);
+            }
+
+            if (code == VoldResponseCode.VolumeDiskInserted) {
+                new Thread("MountService#VolumeDiskInserted") {
+                    @Override
+                    public void run() {
+                        try {
+                            int rc;
+                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
+                                Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
+                            }
+                        } catch (Exception ex) {
+                            Slog.w(TAG, "Failed to mount media on insertion", ex);
+                        }
+                    }
+                }.start();
+            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
+                /*
+                 * This event gets trumped if we're already in BAD_REMOVAL state
+                 */
+                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
+                    return true;
+                }
+                /* Send the media unmounted event first */
+                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+                sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+
+                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
+                updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
+                action = Intent.ACTION_MEDIA_REMOVED;
+            } else if (code == VoldResponseCode.VolumeBadRemoval) {
+                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
+                /* Send the media unmounted event first */
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+                sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+
+                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
+                updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
+                action = Intent.ACTION_MEDIA_BAD_REMOVAL;
+            } else if (code == VoldResponseCode.FstrimCompleted) {
+                EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
+            } else {
+                Slog.e(TAG, String.format("Unknown code {%d}", code));
+            }
+
+            if (action != null) {
+                sendStorageIntent(action, volume, UserHandle.ALL);
+            }
+        } else {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
+        final StorageVolume volume;
+        final String state;
+        synchronized (mVolumesLock) {
+            volume = mVolumesByPath.get(path);
+            state = getVolumeState(path);
+        }
+
+        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
+
+        String action = null;
+
+        if (oldState == VolumeState.Shared && newState != oldState) {
+            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
+            sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
+        }
+
+        if (newState == VolumeState.Init) {
+        } else if (newState == VolumeState.NoMedia) {
+            // NoMedia is handled via Disk Remove events
+        } else if (newState == VolumeState.Idle) {
+            /*
+             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
+             * if we're in the process of enabling UMS
+             */
+            if (!state.equals(
+                    Environment.MEDIA_BAD_REMOVAL) && !state.equals(
+                            Environment.MEDIA_NOFS) && !state.equals(
+                                    Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
+                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+                action = Intent.ACTION_MEDIA_UNMOUNTED;
+            }
+        } else if (newState == VolumeState.Pending) {
+        } else if (newState == VolumeState.Checking) {
+            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
+            updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
+            action = Intent.ACTION_MEDIA_CHECKING;
+        } else if (newState == VolumeState.Mounted) {
+            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
+            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
+            action = Intent.ACTION_MEDIA_MOUNTED;
+        } else if (newState == VolumeState.Unmounting) {
+            action = Intent.ACTION_MEDIA_EJECT;
+        } else if (newState == VolumeState.Formatting) {
+        } else if (newState == VolumeState.Shared) {
+            if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
+            /* Send the media unmounted event first */
+            updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+            sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+
+            if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
+            updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
+            action = Intent.ACTION_MEDIA_SHARED;
+            if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
+        } else if (newState == VolumeState.SharedMnt) {
+            Slog.e(TAG, "Live shared mounts not supported yet!");
+            return;
+        } else {
+            Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
+        }
+
+        if (action != null) {
+            sendStorageIntent(action, volume, UserHandle.ALL);
+        }
+    }
+
+    private int doMountVolume(String path) {
+        int rc = StorageResultCode.OperationSucceeded;
+
+        final StorageVolume volume;
+        synchronized (mVolumesLock) {
+            volume = mVolumesByPath.get(path);
+        }
+
+        if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
+        try {
+            mConnector.execute("volume", "mount", path);
+        } catch (NativeDaemonConnectorException e) {
+            /*
+             * Mount failed for some reason
+             */
+            String action = null;
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedNoMedia) {
+                /*
+                 * Attempt to mount but no media inserted
+                 */
+                rc = StorageResultCode.OperationFailedNoMedia;
+            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
+                if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
+                /*
+                 * Media is blank or does not contain a supported filesystem
+                 */
+                updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
+                action = Intent.ACTION_MEDIA_NOFS;
+                rc = StorageResultCode.OperationFailedMediaBlank;
+            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
+                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
+                /*
+                 * Volume consistency check failed
+                 */
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
+                action = Intent.ACTION_MEDIA_UNMOUNTABLE;
+                rc = StorageResultCode.OperationFailedMediaCorrupt;
+            } else {
+                rc = StorageResultCode.OperationFailedInternalError;
+            }
+
+            /*
+             * Send broadcast intent (if required for the failure)
+             */
+            if (action != null) {
+                sendStorageIntent(action, volume, UserHandle.ALL);
+            }
+        }
+
+        return rc;
+    }
+
+    /*
+     * If force is not set, we do not unmount if there are
+     * processes holding references to the volume about to be unmounted.
+     * If force is set, all the processes holding references need to be
+     * killed via the ActivityManager before actually unmounting the volume.
+     * This might even take a while and might be retried after timed delays
+     * to make sure we dont end up in an instable state and kill some core
+     * processes.
+     * If removeEncryption is set, force is implied, and the system will remove any encryption
+     * mapping set on the volume when unmounting.
+     */
+    private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
+        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
+            return VoldResponseCode.OpFailedVolNotMounted;
+        }
+
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
+        // Redundant probably. But no harm in updating state again.
+        mPms.updateExternalMediaStatus(false, false);
+        try {
+            final Command cmd = new Command("volume", "unmount", path);
+            if (removeEncryption) {
+                cmd.appendArg("force_and_revert");
+            } else if (force) {
+                cmd.appendArg("force");
+            }
+            mConnector.execute(cmd);
+            // We unmounted the volume. None of the asec containers are available now.
+            synchronized (mAsecMountSet) {
+                mAsecMountSet.clear();
+            }
+            return StorageResultCode.OperationSucceeded;
+        } catch (NativeDaemonConnectorException e) {
+            // Don't worry about mismatch in PackageManager since the
+            // call back will handle the status changes any way.
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedVolNotMounted) {
+                return StorageResultCode.OperationFailedStorageNotMounted;
+            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
+                return StorageResultCode.OperationFailedStorageBusy;
+            } else {
+                return StorageResultCode.OperationFailedInternalError;
+            }
+        }
+    }
+
+    private int doFormatVolume(String path) {
+        try {
+            mConnector.execute("volume", "format", path);
+            return StorageResultCode.OperationSucceeded;
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedNoMedia) {
+                return StorageResultCode.OperationFailedNoMedia;
+            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
+                return StorageResultCode.OperationFailedMediaCorrupt;
+            } else {
+                return StorageResultCode.OperationFailedInternalError;
+            }
+        }
+    }
+
+    private boolean doGetVolumeShared(String path, String method) {
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("volume", "shared", path, method);
+        } catch (NativeDaemonConnectorException ex) {
+            Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
+            return false;
+        }
+
+        if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
+            return event.getMessage().endsWith("enabled");
+        } else {
+            return false;
+        }
+    }
+
+    private void notifyShareAvailabilityChange(final boolean avail) {
+        synchronized (mListeners) {
+            mUmsAvailable = avail;
+            for (int i = mListeners.size() -1; i >= 0; i--) {
+                MountServiceBinderListener bl = mListeners.get(i);
+                try {
+                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
+                } catch (RemoteException rex) {
+                    Slog.e(TAG, "Listener dead");
+                    mListeners.remove(i);
+                } catch (Exception ex) {
+                    Slog.e(TAG, "Listener failed", ex);
+                }
+            }
+        }
+
+        if (mSystemReady == true) {
+            sendUmsIntent(avail);
+        } else {
+            mSendUmsConnectedOnBoot = avail;
+        }
+
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (avail == false && primary != null
+                && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
+            final String path = primary.getPath();
+            /*
+             * USB mass storage disconnected while enabled
+             */
+            new Thread("MountService#AvailabilityChange") {
+                @Override
+                public void run() {
+                    try {
+                        int rc;
+                        Slog.w(TAG, "Disabling UMS after cable disconnect");
+                        doShareUnshareVolume(path, "ums", false);
+                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
+                            Slog.e(TAG, String.format(
+                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
+                                            path, rc));
+                        }
+                    } catch (Exception ex) {
+                        Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
+                    }
+                }
+            }.start();
+        }
+    }
+
+    private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) {
+        final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
+        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
+        Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
+        mContext.sendBroadcastAsUser(intent, user);
+    }
+
+    private void sendUmsIntent(boolean c) {
+        mContext.sendBroadcastAsUser(
+                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
+                UserHandle.ALL);
+    }
+
+    private void validatePermission(String perm) {
+        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(String.format("Requires %s permission", perm));
+        }
+    }
+
+    // Storage list XML tags
+    private static final String TAG_STORAGE_LIST = "StorageList";
+    private static final String TAG_STORAGE = "storage";
+
+    private void readStorageListLocked() {
+        mVolumes.clear();
+        mVolumeStates.clear();
+
+        Resources resources = mContext.getResources();
+
+        int id = com.android.internal.R.xml.storage_list;
+        XmlResourceParser parser = resources.getXml(id);
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+
+        try {
+            XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
+            while (true) {
+                XmlUtils.nextElement(parser);
+
+                String element = parser.getName();
+                if (element == null) break;
+
+                if (TAG_STORAGE.equals(element)) {
+                    TypedArray a = resources.obtainAttributes(attrs,
+                            com.android.internal.R.styleable.Storage);
+
+                    String path = a.getString(
+                            com.android.internal.R.styleable.Storage_mountPoint);
+                    int descriptionId = a.getResourceId(
+                            com.android.internal.R.styleable.Storage_storageDescription, -1);
+                    CharSequence description = a.getText(
+                            com.android.internal.R.styleable.Storage_storageDescription);
+                    boolean primary = a.getBoolean(
+                            com.android.internal.R.styleable.Storage_primary, false);
+                    boolean removable = a.getBoolean(
+                            com.android.internal.R.styleable.Storage_removable, false);
+                    boolean emulated = a.getBoolean(
+                            com.android.internal.R.styleable.Storage_emulated, false);
+                    int mtpReserve = a.getInt(
+                            com.android.internal.R.styleable.Storage_mtpReserve, 0);
+                    boolean allowMassStorage = a.getBoolean(
+                            com.android.internal.R.styleable.Storage_allowMassStorage, false);
+                    // resource parser does not support longs, so XML value is in megabytes
+                    long maxFileSize = a.getInt(
+                            com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
+
+                    Slog.d(TAG, "got storage path: " + path + " description: " + description +
+                            " primary: " + primary + " removable: " + removable +
+                            " emulated: " + emulated +  " mtpReserve: " + mtpReserve +
+                            " allowMassStorage: " + allowMassStorage +
+                            " maxFileSize: " + maxFileSize);
+
+                    if (emulated) {
+                        // For devices with emulated storage, we create separate
+                        // volumes for each known user.
+                        mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
+                                true, mtpReserve, false, maxFileSize, null);
+
+                        final UserManagerService userManager = UserManagerService.getInstance();
+                        for (UserInfo user : userManager.getUsers(false)) {
+                            createEmulatedVolumeForUserLocked(user.getUserHandle());
+                        }
+
+                    } else {
+                        if (path == null || description == null) {
+                            Slog.e(TAG, "Missing storage path or description in readStorageList");
+                        } else {
+                            final StorageVolume volume = new StorageVolume(new File(path),
+                                    descriptionId, primary, removable, emulated, mtpReserve,
+                                    allowMassStorage, maxFileSize, null);
+                            addVolumeLocked(volume);
+
+                            // Until we hear otherwise, treat as unmounted
+                            mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
+                            volume.setState(Environment.MEDIA_UNMOUNTED);
+                        }
+                    }
+
+                    a.recycle();
+                }
+            }
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        } finally {
+            // Compute storage ID for each physical volume; emulated storage is
+            // always 0 when defined.
+            int index = isExternalStorageEmulated() ? 1 : 0;
+            for (StorageVolume volume : mVolumes) {
+                if (!volume.isEmulated()) {
+                    volume.setStorageId(index++);
+                }
+            }
+            parser.close();
+        }
+    }
+
+    /**
+     * Create and add new {@link StorageVolume} for given {@link UserHandle}
+     * using {@link #mEmulatedTemplate} as template.
+     */
+    private void createEmulatedVolumeForUserLocked(UserHandle user) {
+        if (mEmulatedTemplate == null) {
+            throw new IllegalStateException("Missing emulated volume multi-user template");
+        }
+
+        final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
+        final File path = userEnv.getExternalStorageDirectory();
+        final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
+        volume.setStorageId(0);
+        addVolumeLocked(volume);
+
+        if (mSystemReady) {
+            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
+        } else {
+            // Place stub status for early callers to find
+            mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
+            volume.setState(Environment.MEDIA_MOUNTED);
+        }
+    }
+
+    private void addVolumeLocked(StorageVolume volume) {
+        Slog.d(TAG, "addVolumeLocked() " + volume);
+        mVolumes.add(volume);
+        final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
+        if (existing != null) {
+            throw new IllegalStateException(
+                    "Volume at " + volume.getPath() + " already exists: " + existing);
+        }
+    }
+
+    private void removeVolumeLocked(StorageVolume volume) {
+        Slog.d(TAG, "removeVolumeLocked() " + volume);
+        mVolumes.remove(volume);
+        mVolumesByPath.remove(volume.getPath());
+        mVolumeStates.remove(volume.getPath());
+    }
+
+    private StorageVolume getPrimaryPhysicalVolume() {
+        synchronized (mVolumesLock) {
+            for (StorageVolume volume : mVolumes) {
+                if (volume.isPrimary() && !volume.isEmulated()) {
+                    return volume;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Constructs a new MountService instance
+     *
+     * @param context  Binder context for this service
+     */
+    public MountService(Context context) {
+        mContext = context;
+
+        synchronized (mVolumesLock) {
+            readStorageListLocked();
+        }
+
+        // XXX: This will go away soon in favor of IMountServiceObserver
+        mPms = (PackageManagerService) ServiceManager.getService("package");
+
+        HandlerThread hthread = new HandlerThread(TAG);
+        hthread.start();
+        mHandler = new MountServiceHandler(hthread.getLooper());
+
+        // Watch for user changes
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_ADDED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+        // Watch for USB changes on primary volume
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null && primary.allowMassStorage()) {
+            mContext.registerReceiver(
+                    mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
+        }
+
+        // Watch for idle maintenance changes
+        IntentFilter idleMaintenanceFilter = new IntentFilter();
+        idleMaintenanceFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
+        mContext.registerReceiverAsUser(mIdleMaintenanceReceiver, UserHandle.ALL,
+                idleMaintenanceFilter, null, mHandler);
+
+        // Add OBB Action Handler to MountService thread.
+        mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
+
+        /*
+         * Create the connection to vold with a maximum queue of twice the
+         * amount of containers we'd ever expect to have. This keeps an
+         * "asec list" from blocking a thread repeatedly.
+         */
+        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25);
+
+        Thread thread = new Thread(mConnector, VOLD_TAG);
+        thread.start();
+
+        // Add ourself to the Watchdog monitors if enabled.
+        if (WATCHDOG_ENABLE) {
+            Watchdog.getInstance().addMonitor(this);
+        }
+    }
+
+    public void systemReady() {
+        mSystemReady = true;
+        mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
+    }
+
+    /**
+     * Exposed API calls below here
+     */
+
+    public void registerListener(IMountServiceListener listener) {
+        synchronized (mListeners) {
+            MountServiceBinderListener bl = new MountServiceBinderListener(listener);
+            try {
+                listener.asBinder().linkToDeath(bl, 0);
+                mListeners.add(bl);
+            } catch (RemoteException rex) {
+                Slog.e(TAG, "Failed to link to listener death");
+            }
+        }
+    }
+
+    public void unregisterListener(IMountServiceListener listener) {
+        synchronized (mListeners) {
+            for(MountServiceBinderListener bl : mListeners) {
+                if (bl.mListener == listener) {
+                    mListeners.remove(mListeners.indexOf(bl));
+                    listener.asBinder().unlinkToDeath(bl, 0);
+                    return;
+                }
+            }
+        }
+    }
+
+    public void shutdown(final IMountShutdownObserver observer) {
+        validatePermission(android.Manifest.permission.SHUTDOWN);
+
+        Slog.i(TAG, "Shutting down");
+        synchronized (mVolumesLock) {
+            for (String path : mVolumeStates.keySet()) {
+                String state = mVolumeStates.get(path);
+
+                if (state.equals(Environment.MEDIA_SHARED)) {
+                    /*
+                     * If the media is currently shared, unshare it.
+                     * XXX: This is still dangerous!. We should not
+                     * be rebooting at *all* if UMS is enabled, since
+                     * the UMS host could have dirty FAT cache entries
+                     * yet to flush.
+                     */
+                    setUsbMassStorageEnabled(false);
+                } else if (state.equals(Environment.MEDIA_CHECKING)) {
+                    /*
+                     * If the media is being checked, then we need to wait for
+                     * it to complete before being able to proceed.
+                     */
+                    // XXX: @hackbod - Should we disable the ANR timer here?
+                    int retries = 30;
+                    while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
+                        try {
+                            Thread.sleep(1000);
+                        } catch (InterruptedException iex) {
+                            Slog.e(TAG, "Interrupted while waiting for media", iex);
+                            break;
+                        }
+                        state = Environment.getExternalStorageState();
+                    }
+                    if (retries == 0) {
+                        Slog.e(TAG, "Timed out waiting for media to check");
+                    }
+                }
+
+                if (state.equals(Environment.MEDIA_MOUNTED)) {
+                    // Post a unmount message.
+                    ShutdownCallBack ucb = new ShutdownCallBack(path, observer);
+                    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
+                } else if (observer != null) {
+                    /*
+                     * Observer is waiting for onShutDownComplete when we are done.
+                     * Since nothing will be done send notification directly so shutdown
+                     * sequence can continue.
+                     */
+                    try {
+                        observer.onShutDownComplete(StorageResultCode.OperationSucceeded);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoteException when shutting down");
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean getUmsEnabling() {
+        synchronized (mListeners) {
+            return mUmsEnabling;
+        }
+    }
+
+    private void setUmsEnabling(boolean enable) {
+        synchronized (mListeners) {
+            mUmsEnabling = enable;
+        }
+    }
+
+    public boolean isUsbMassStorageConnected() {
+        waitForReady();
+
+        if (getUmsEnabling()) {
+            return true;
+        }
+        synchronized (mListeners) {
+            return mUmsAvailable;
+        }
+    }
+
+    public void setUsbMassStorageEnabled(boolean enable) {
+        waitForReady();
+        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary == null) return;
+
+        // TODO: Add support for multiple share methods
+
+        /*
+         * If the volume is mounted and we're enabling then unmount it
+         */
+        String path = primary.getPath();
+        String vs = getVolumeState(path);
+        String method = "ums";
+        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
+            // Override for isUsbMassStorageEnabled()
+            setUmsEnabling(enable);
+            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
+            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
+            // Clear override
+            setUmsEnabling(false);
+        }
+        /*
+         * If we disabled UMS then mount the volume
+         */
+        if (!enable) {
+            doShareUnshareVolume(path, method, enable);
+            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
+                Slog.e(TAG, "Failed to remount " + path +
+                        " after disabling share method " + method);
+                /*
+                 * Even though the mount failed, the unshare didn't so don't indicate an error.
+                 * The mountVolume() call will have set the storage state and sent the necessary
+                 * broadcasts.
+                 */
+            }
+        }
+    }
+
+    public boolean isUsbMassStorageEnabled() {
+        waitForReady();
+
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null) {
+            return doGetVolumeShared(primary.getPath(), "ums");
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @return state of the volume at the specified mount point
+     */
+    public String getVolumeState(String mountPoint) {
+        synchronized (mVolumesLock) {
+            String state = mVolumeStates.get(mountPoint);
+            if (state == null) {
+                Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
+                if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
+                    state = Environment.MEDIA_REMOVED;
+                } else {
+                    throw new IllegalArgumentException();
+                }
+            }
+
+            return state;
+        }
+    }
+
+    @Override
+    public boolean isExternalStorageEmulated() {
+        return mEmulatedTemplate != null;
+    }
+
+    public int mountVolume(String path) {
+        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+
+        waitForReady();
+        return doMountVolume(path);
+    }
+
+    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
+        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        waitForReady();
+
+        String volState = getVolumeState(path);
+        if (DEBUG_UNMOUNT) {
+            Slog.i(TAG, "Unmounting " + path
+                    + " force = " + force
+                    + " removeEncryption = " + removeEncryption);
+        }
+        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
+                Environment.MEDIA_REMOVED.equals(volState) ||
+                Environment.MEDIA_SHARED.equals(volState) ||
+                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
+            // Media already unmounted or cannot be unmounted.
+            // TODO return valid return code when adding observer call back.
+            return;
+        }
+        UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
+        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
+    }
+
+    public int formatVolume(String path) {
+        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+        waitForReady();
+
+        return doFormatVolume(path);
+    }
+
+    public int[] getStorageUsers(String path) {
+        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        waitForReady();
+        try {
+            final String[] r = NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("storage", "users", path),
+                    VoldResponseCode.StorageUsersListResult);
+
+            // FMT: <pid> <process name>
+            int[] data = new int[r.length];
+            for (int i = 0; i < r.length; i++) {
+                String[] tok = r[i].split(" ");
+                try {
+                    data[i] = Integer.parseInt(tok[0]);
+                } catch (NumberFormatException nfe) {
+                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
+                    return new int[0];
+                }
+            }
+            return data;
+        } catch (NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to retrieve storage users list", e);
+            return new int[0];
+        }
+    }
+
+    private void warnOnNotMounted() {
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null) {
+            boolean mounted = false;
+            try {
+                mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
+            } catch (IllegalArgumentException e) {
+            }
+
+            if (!mounted) {
+                Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
+            }
+        }
+    }
+
+    public String[] getSecureContainerList() {
+        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        waitForReady();
+        warnOnNotMounted();
+
+        try {
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
+        } catch (NativeDaemonConnectorException e) {
+            return new String[0];
+        }
+    }
+
+    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
+            int ownerUid, boolean external) {
+        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        waitForReady();
+        warnOnNotMounted();
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
+                    ownerUid, external ? "1" : "0");
+        } catch (NativeDaemonConnectorException e) {
+            rc = StorageResultCode.OperationFailedInternalError;
+        }
+
+        if (rc == StorageResultCode.OperationSucceeded) {
+            synchronized (mAsecMountSet) {
+                mAsecMountSet.add(id);
+            }
+        }
+        return rc;
+    }
+
+    public int finalizeSecureContainer(String id) {
+        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        warnOnNotMounted();
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            mConnector.execute("asec", "finalize", id);
+            /*
+             * Finalization does a remount, so no need
+             * to update mAsecMountSet
+             */
+        } catch (NativeDaemonConnectorException e) {
+            rc = StorageResultCode.OperationFailedInternalError;
+        }
+        return rc;
+    }
+
+    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
+        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        warnOnNotMounted();
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            mConnector.execute("asec", "fixperms", id, gid, filename);
+            /*
+             * Fix permissions does a remount, so no need to update
+             * mAsecMountSet
+             */
+        } catch (NativeDaemonConnectorException e) {
+            rc = StorageResultCode.OperationFailedInternalError;
+        }
+        return rc;
+    }
+
+    public int destroySecureContainer(String id, boolean force) {
+        validatePermission(android.Manifest.permission.ASEC_DESTROY);
+        waitForReady();
+        warnOnNotMounted();
+
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            final Command cmd = new Command("asec", "destroy", id);
+            if (force) {
+                cmd.appendArg("force");
+            }
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedStorageBusy) {
+                rc = StorageResultCode.OperationFailedStorageBusy;
+            } else {
+                rc = StorageResultCode.OperationFailedInternalError;
+            }
+        }
+
+        if (rc == StorageResultCode.OperationSucceeded) {
+            synchronized (mAsecMountSet) {
+                if (mAsecMountSet.contains(id)) {
+                    mAsecMountSet.remove(id);
+                }
+            }
+        }
+
+        return rc;
+    }
+
+    public int mountSecureContainer(String id, String key, int ownerUid) {
+        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+        waitForReady();
+        warnOnNotMounted();
+
+        synchronized (mAsecMountSet) {
+            if (mAsecMountSet.contains(id)) {
+                return StorageResultCode.OperationFailedStorageMounted;
+            }
+        }
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid);
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code != VoldResponseCode.OpFailedStorageBusy) {
+                rc = StorageResultCode.OperationFailedInternalError;
+            }
+        }
+
+        if (rc == StorageResultCode.OperationSucceeded) {
+            synchronized (mAsecMountSet) {
+                mAsecMountSet.add(id);
+            }
+        }
+        return rc;
+    }
+
+    public int unmountSecureContainer(String id, boolean force) {
+        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+        waitForReady();
+        warnOnNotMounted();
+
+        synchronized (mAsecMountSet) {
+            if (!mAsecMountSet.contains(id)) {
+                return StorageResultCode.OperationFailedStorageNotMounted;
+            }
+         }
+
+        /*
+         * Force a GC to make sure AssetManagers in other threads of the
+         * system_server are cleaned up. We have to do this since AssetManager
+         * instances are kept as a WeakReference and it's possible we have files
+         * open on the external storage.
+         */
+        Runtime.getRuntime().gc();
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            final Command cmd = new Command("asec", "unmount", id);
+            if (force) {
+                cmd.appendArg("force");
+            }
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedStorageBusy) {
+                rc = StorageResultCode.OperationFailedStorageBusy;
+            } else {
+                rc = StorageResultCode.OperationFailedInternalError;
+            }
+        }
+
+        if (rc == StorageResultCode.OperationSucceeded) {
+            synchronized (mAsecMountSet) {
+                mAsecMountSet.remove(id);
+            }
+        }
+        return rc;
+    }
+
+    public boolean isSecureContainerMounted(String id) {
+        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        waitForReady();
+        warnOnNotMounted();
+
+        synchronized (mAsecMountSet) {
+            return mAsecMountSet.contains(id);
+        }
+    }
+
+    public int renameSecureContainer(String oldId, String newId) {
+        validatePermission(android.Manifest.permission.ASEC_RENAME);
+        waitForReady();
+        warnOnNotMounted();
+
+        synchronized (mAsecMountSet) {
+            /*
+             * Because a mounted container has active internal state which cannot be
+             * changed while active, we must ensure both ids are not currently mounted.
+             */
+            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
+                return StorageResultCode.OperationFailedStorageMounted;
+            }
+        }
+
+        int rc = StorageResultCode.OperationSucceeded;
+        try {
+            mConnector.execute("asec", "rename", oldId, newId);
+        } catch (NativeDaemonConnectorException e) {
+            rc = StorageResultCode.OperationFailedInternalError;
+        }
+
+        return rc;
+    }
+
+    public String getSecureContainerPath(String id) {
+        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        waitForReady();
+        warnOnNotMounted();
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("asec", "path", id);
+            event.checkCode(VoldResponseCode.AsecPathResult);
+            return event.getMessage();
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedStorageNotFound) {
+                Slog.i(TAG, String.format("Container '%s' not found", id));
+                return null;
+            } else {
+                throw new IllegalStateException(String.format("Unexpected response code %d", code));
+            }
+        }
+    }
+
+    public String getSecureContainerFilesystemPath(String id) {
+        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        waitForReady();
+        warnOnNotMounted();
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("asec", "fspath", id);
+            event.checkCode(VoldResponseCode.AsecPathResult);
+            return event.getMessage();
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedStorageNotFound) {
+                Slog.i(TAG, String.format("Container '%s' not found", id));
+                return null;
+            } else {
+                throw new IllegalStateException(String.format("Unexpected response code %d", code));
+            }
+        }
+    }
+
+    public void finishMediaUpdate() {
+        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+    }
+
+    private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
+        if (callerUid == android.os.Process.SYSTEM_UID) {
+            return true;
+        }
+
+        if (packageName == null) {
+            return false;
+        }
+
+        final int packageUid = mPms.getPackageUid(packageName, UserHandle.getUserId(callerUid));
+
+        if (DEBUG_OBB) {
+            Slog.d(TAG, "packageName = " + packageName + ", packageUid = " +
+                    packageUid + ", callerUid = " + callerUid);
+        }
+
+        return callerUid == packageUid;
+    }
+
+    public String getMountedObbPath(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+        waitForReady();
+        warnOnNotMounted();
+
+        final ObbState state;
+        synchronized (mObbPathToStateMap) {
+            state = mObbPathToStateMap.get(rawPath);
+        }
+        if (state == null) {
+            Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
+            return null;
+        }
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("obb", "path", state.voldPath);
+            event.checkCode(VoldResponseCode.AsecPathResult);
+            return event.getMessage();
+        } catch (NativeDaemonConnectorException e) {
+            int code = e.getCode();
+            if (code == VoldResponseCode.OpFailedStorageNotFound) {
+                return null;
+            } else {
+                throw new IllegalStateException(String.format("Unexpected response code %d", code));
+            }
+        }
+    }
+
+    @Override
+    public boolean isObbMounted(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        synchronized (mObbMounts) {
+            return mObbPathToStateMap.containsKey(rawPath);
+        }
+    }
+
+    @Override
+    public void mountObb(
+            String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
+        Preconditions.checkNotNull(token, "token cannot be null");
+
+        final int callingUid = Binder.getCallingUid();
+        final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
+        final ObbAction action = new MountObbAction(obbState, key, callingUid);
+        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+        if (DEBUG_OBB)
+            Slog.i(TAG, "Send to OBB handler: " + action.toString());
+    }
+
+    @Override
+    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+        final ObbState existingState;
+        synchronized (mObbPathToStateMap) {
+            existingState = mObbPathToStateMap.get(rawPath);
+        }
+
+        if (existingState != null) {
+            // TODO: separate state object from request data
+            final int callingUid = Binder.getCallingUid();
+            final ObbState newState = new ObbState(
+                    rawPath, existingState.canonicalPath, callingUid, token, nonce);
+            final ObbAction action = new UnmountObbAction(newState, force);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+
+            if (DEBUG_OBB)
+                Slog.i(TAG, "Send to OBB handler: " + action.toString());
+        } else {
+            Slog.w(TAG, "Unknown OBB mount at " + rawPath);
+        }
+    }
+
+    @Override
+    public int getEncryptionState() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+                "no permission to access the crypt keeper");
+
+        waitForReady();
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("cryptfs", "cryptocomplete");
+            return Integer.parseInt(event.getMessage());
+        } catch (NumberFormatException e) {
+            // Bad result - unexpected.
+            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
+            return ENCRYPTION_STATE_ERROR_UNKNOWN;
+        } catch (NativeDaemonConnectorException e) {
+            // Something bad happened.
+            Slog.w(TAG, "Error in communicating with cryptfs in validating");
+            return ENCRYPTION_STATE_ERROR_UNKNOWN;
+        }
+    }
+
+    @Override
+    public int decryptStorage(String password) {
+        if (TextUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password cannot be empty");
+        }
+
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+                "no permission to access the crypt keeper");
+
+        waitForReady();
+
+        if (DEBUG_EVENTS) {
+            Slog.i(TAG, "decrypting storage...");
+        }
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
+
+            final int code = Integer.parseInt(event.getMessage());
+            if (code == 0) {
+                // Decrypt was successful. Post a delayed message before restarting in order
+                // to let the UI to clear itself
+                mHandler.postDelayed(new Runnable() {
+                    public void run() {
+                        try {
+                            mConnector.execute("cryptfs", "restart");
+                        } catch (NativeDaemonConnectorException e) {
+                            Slog.e(TAG, "problem executing in background", e);
+                        }
+                    }
+                }, 1000); // 1 second
+            }
+
+            return code;
+        } catch (NativeDaemonConnectorException e) {
+            // Decryption failed
+            return e.getCode();
+        }
+    }
+
+    public int encryptStorage(String password) {
+        if (TextUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password cannot be empty");
+        }
+
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+            "no permission to access the crypt keeper");
+
+        waitForReady();
+
+        if (DEBUG_EVENTS) {
+            Slog.i(TAG, "encrypting storage...");
+        }
+
+        try {
+            mConnector.execute("cryptfs", "enablecrypto", "inplace", new SensitiveArg(password));
+        } catch (NativeDaemonConnectorException e) {
+            // Encryption failed
+            return e.getCode();
+        }
+
+        return 0;
+    }
+
+    public int changeEncryptionPassword(String password) {
+        if (TextUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password cannot be empty");
+        }
+
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+            "no permission to access the crypt keeper");
+
+        waitForReady();
+
+        if (DEBUG_EVENTS) {
+            Slog.i(TAG, "changing encryption password...");
+        }
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("cryptfs", "changepw", new SensitiveArg(password));
+            return Integer.parseInt(event.getMessage());
+        } catch (NativeDaemonConnectorException e) {
+            // Encryption failed
+            return e.getCode();
+        }
+    }
+
+    /**
+     * Validate a user-supplied password string with cryptfs
+     */
+    @Override
+    public int verifyEncryptionPassword(String password) throws RemoteException {
+        // Only the system process is permitted to validate passwords
+        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+            throw new SecurityException("no permission to access the crypt keeper");
+        }
+
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
+            "no permission to access the crypt keeper");
+
+        if (TextUtils.isEmpty(password)) {
+            throw new IllegalArgumentException("password cannot be empty");
+        }
+
+        waitForReady();
+
+        if (DEBUG_EVENTS) {
+            Slog.i(TAG, "validating encryption password...");
+        }
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
+            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
+            return Integer.parseInt(event.getMessage());
+        } catch (NativeDaemonConnectorException e) {
+            // Encryption failed
+            return e.getCode();
+        }
+    }
+
+    @Override
+    public int mkdirs(String callingPkg, String appPath) {
+        final int userId = UserHandle.getUserId(Binder.getCallingUid());
+        final UserEnvironment userEnv = new UserEnvironment(userId);
+
+        // Validate that reported package name belongs to caller
+        final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+                Context.APP_OPS_SERVICE);
+        appOps.checkPackage(Binder.getCallingUid(), callingPkg);
+
+        try {
+            appPath = new File(appPath).getCanonicalPath();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
+            return -1;
+        }
+
+        if (!appPath.endsWith("/")) {
+            appPath = appPath + "/";
+        }
+
+        // Try translating the app path into a vold path, but require that it
+        // belong to the calling package.
+        String voldPath = maybeTranslatePathForVold(appPath,
+                userEnv.buildExternalStorageAppDataDirs(callingPkg),
+                userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
+        if (voldPath != null) {
+            try {
+                mConnector.execute("volume", "mkdirs", voldPath);
+                return 0;
+            } catch (NativeDaemonConnectorException e) {
+                return e.getCode();
+            }
+        }
+
+        voldPath = maybeTranslatePathForVold(appPath,
+                userEnv.buildExternalStorageAppObbDirs(callingPkg),
+                userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
+        if (voldPath != null) {
+            try {
+                mConnector.execute("volume", "mkdirs", voldPath);
+                return 0;
+            } catch (NativeDaemonConnectorException e) {
+                return e.getCode();
+            }
+        }
+
+        throw new SecurityException("Invalid mkdirs path: " + appPath);
+    }
+
+    /**
+     * Translate the given path from an app-visible path to a vold-visible path,
+     * but only if it's under the given whitelisted paths.
+     *
+     * @param path a canonicalized app-visible path.
+     * @param appPaths list of app-visible paths that are allowed.
+     * @param voldPaths list of vold-visible paths directly corresponding to the
+     *            allowed app-visible paths argument.
+     * @return a vold-visible path representing the original path, or
+     *         {@code null} if the given path didn't have an app-to-vold
+     *         mapping.
+     */
+    @VisibleForTesting
+    public static String maybeTranslatePathForVold(
+            String path, File[] appPaths, File[] voldPaths) {
+        if (appPaths.length != voldPaths.length) {
+            throw new IllegalStateException("Paths must be 1:1 mapping");
+        }
+
+        for (int i = 0; i < appPaths.length; i++) {
+            final String appPath = appPaths[i].getAbsolutePath() + "/";
+            if (path.startsWith(appPath)) {
+                path = new File(voldPaths[i], path.substring(appPath.length()))
+                        .getAbsolutePath();
+                if (!path.endsWith("/")) {
+                    path = path + "/";
+                }
+                return path;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public StorageVolume[] getVolumeList() {
+        final int callingUserId = UserHandle.getCallingUserId();
+        final boolean accessAll = (mContext.checkPermission(
+                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
+                Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
+
+        synchronized (mVolumesLock) {
+            final ArrayList<StorageVolume> filtered = Lists.newArrayList();
+            for (StorageVolume volume : mVolumes) {
+                final UserHandle owner = volume.getOwner();
+                final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
+                if (accessAll || ownerMatch) {
+                    filtered.add(volume);
+                }
+            }
+            return filtered.toArray(new StorageVolume[filtered.size()]);
+        }
+    }
+
+    private void addObbStateLocked(ObbState obbState) throws RemoteException {
+        final IBinder binder = obbState.getBinder();
+        List<ObbState> obbStates = mObbMounts.get(binder);
+
+        if (obbStates == null) {
+            obbStates = new ArrayList<ObbState>();
+            mObbMounts.put(binder, obbStates);
+        } else {
+            for (final ObbState o : obbStates) {
+                if (o.rawPath.equals(obbState.rawPath)) {
+                    throw new IllegalStateException("Attempt to add ObbState twice. "
+                            + "This indicates an error in the MountService logic.");
+                }
+            }
+        }
+
+        obbStates.add(obbState);
+        try {
+            obbState.link();
+        } catch (RemoteException e) {
+            /*
+             * The binder died before we could link it, so clean up our state
+             * and return failure.
+             */
+            obbStates.remove(obbState);
+            if (obbStates.isEmpty()) {
+                mObbMounts.remove(binder);
+            }
+
+            // Rethrow the error so mountObb can get it
+            throw e;
+        }
+
+        mObbPathToStateMap.put(obbState.rawPath, obbState);
+    }
+
+    private void removeObbStateLocked(ObbState obbState) {
+        final IBinder binder = obbState.getBinder();
+        final List<ObbState> obbStates = mObbMounts.get(binder);
+        if (obbStates != null) {
+            if (obbStates.remove(obbState)) {
+                obbState.unlink();
+            }
+            if (obbStates.isEmpty()) {
+                mObbMounts.remove(binder);
+            }
+        }
+
+        mObbPathToStateMap.remove(obbState.rawPath);
+    }
+
+    private class ObbActionHandler extends Handler {
+        private boolean mBound = false;
+        private final List<ObbAction> mActions = new LinkedList<ObbAction>();
+
+        ObbActionHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case OBB_RUN_ACTION: {
+                    final ObbAction action = (ObbAction) msg.obj;
+
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_RUN_ACTION: " + action.toString());
+
+                    // If a bind was already initiated we don't really
+                    // need to do anything. The pending install
+                    // will be processed later on.
+                    if (!mBound) {
+                        // If this is the only one pending we might
+                        // have to bind to the service again.
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            action.handleError();
+                            return;
+                        }
+                    }
+
+                    mActions.add(action);
+                    break;
+                }
+                case OBB_MCS_BOUND: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_BOUND");
+                    if (msg.obj != null) {
+                        mContainerService = (IMediaContainerService) msg.obj;
+                    }
+                    if (mContainerService == null) {
+                        // Something seriously wrong. Bail out
+                        Slog.e(TAG, "Cannot bind to media container service");
+                        for (ObbAction action : mActions) {
+                            // Indicate service bind error
+                            action.handleError();
+                        }
+                        mActions.clear();
+                    } else if (mActions.size() > 0) {
+                        final ObbAction action = mActions.get(0);
+                        if (action != null) {
+                            action.execute(this);
+                        }
+                    } else {
+                        // Should never happen ideally.
+                        Slog.w(TAG, "Empty queue");
+                    }
+                    break;
+                }
+                case OBB_MCS_RECONNECT: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_RECONNECT");
+                    if (mActions.size() > 0) {
+                        if (mBound) {
+                            disconnectService();
+                        }
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            for (ObbAction action : mActions) {
+                                // Indicate service bind error
+                                action.handleError();
+                            }
+                            mActions.clear();
+                        }
+                    }
+                    break;
+                }
+                case OBB_MCS_UNBIND: {
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "OBB_MCS_UNBIND");
+
+                    // Delete pending install
+                    if (mActions.size() > 0) {
+                        mActions.remove(0);
+                    }
+                    if (mActions.size() == 0) {
+                        if (mBound) {
+                            disconnectService();
+                        }
+                    } else {
+                        // There are more pending requests in queue.
+                        // Just post MCS_BOUND message to trigger processing
+                        // of next pending install.
+                        mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
+                    }
+                    break;
+                }
+                case OBB_FLUSH_MOUNT_STATE: {
+                    final String path = (String) msg.obj;
+
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "Flushing all OBB state for path " + path);
+
+                    synchronized (mObbMounts) {
+                        final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
+
+                        final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
+                        while (i.hasNext()) {
+                            final ObbState state = i.next();
+
+                            /*
+                             * If this entry's source file is in the volume path
+                             * that got unmounted, remove it because it's no
+                             * longer valid.
+                             */
+                            if (state.canonicalPath.startsWith(path)) {
+                                obbStatesToRemove.add(state);
+                            }
+                        }
+
+                        for (final ObbState obbState : obbStatesToRemove) {
+                            if (DEBUG_OBB)
+                                Slog.i(TAG, "Removing state for " + obbState.rawPath);
+
+                            removeObbStateLocked(obbState);
+
+                            try {
+                                obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
+                                        OnObbStateChangeListener.UNMOUNTED);
+                            } catch (RemoteException e) {
+                                Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
+                                        + obbState.rawPath);
+                            }
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+
+        private boolean connectToService() {
+            if (DEBUG_OBB)
+                Slog.i(TAG, "Trying to bind to DefaultContainerService");
+
+            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+            if (mContext.bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE)) {
+                mBound = true;
+                return true;
+            }
+            return false;
+        }
+
+        private void disconnectService() {
+            mContainerService = null;
+            mBound = false;
+            mContext.unbindService(mDefContainerConn);
+        }
+    }
+
+    abstract class ObbAction {
+        private static final int MAX_RETRIES = 3;
+        private int mRetries;
+
+        ObbState mObbState;
+
+        ObbAction(ObbState obbState) {
+            mObbState = obbState;
+        }
+
+        public void execute(ObbActionHandler handler) {
+            try {
+                if (DEBUG_OBB)
+                    Slog.i(TAG, "Starting to execute action: " + toString());
+                mRetries++;
+                if (mRetries > MAX_RETRIES) {
+                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
+                    handleError();
+                    return;
+                } else {
+                    handleExecute();
+                    if (DEBUG_OBB)
+                        Slog.i(TAG, "Posting install MCS_UNBIND");
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
+                }
+            } catch (RemoteException e) {
+                if (DEBUG_OBB)
+                    Slog.i(TAG, "Posting install MCS_RECONNECT");
+                mObbActionHandler.sendEmptyMessage(OBB_MCS_RECONNECT);
+            } catch (Exception e) {
+                if (DEBUG_OBB)
+                    Slog.d(TAG, "Error handling OBB action", e);
+                handleError();
+                mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
+            }
+        }
+
+        abstract void handleExecute() throws RemoteException, IOException;
+        abstract void handleError();
+
+        protected ObbInfo getObbInfo() throws IOException {
+            ObbInfo obbInfo;
+            try {
+                obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
+                        + mObbState.ownerPath);
+                obbInfo = null;
+            }
+            if (obbInfo == null) {
+                throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
+            }
+            return obbInfo;
+        }
+
+        protected void sendNewStatusOrIgnore(int status) {
+            if (mObbState == null || mObbState.token == null) {
+                return;
+            }
+
+            try {
+                mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
+            }
+        }
+    }
+
+    class MountObbAction extends ObbAction {
+        private final String mKey;
+        private final int mCallingUid;
+
+        MountObbAction(ObbState obbState, String key, int callingUid) {
+            super(obbState);
+            mKey = key;
+            mCallingUid = callingUid;
+        }
+
+        @Override
+        public void handleExecute() throws IOException, RemoteException {
+            waitForReady();
+            warnOnNotMounted();
+
+            final ObbInfo obbInfo = getObbInfo();
+
+            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
+                Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
+                        + " which is owned by " + obbInfo.packageName);
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
+                return;
+            }
+
+            final boolean isMounted;
+            synchronized (mObbMounts) {
+                isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
+            }
+            if (isMounted) {
+                Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_ALREADY_MOUNTED);
+                return;
+            }
+
+            final String hashedKey;
+            if (mKey == null) {
+                hashedKey = "none";
+            } else {
+                try {
+                    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
+
+                    KeySpec ks = new PBEKeySpec(mKey.toCharArray(), obbInfo.salt,
+                            PBKDF2_HASH_ROUNDS, CRYPTO_ALGORITHM_KEY_SIZE);
+                    SecretKey key = factory.generateSecret(ks);
+                    BigInteger bi = new BigInteger(key.getEncoded());
+                    hashedKey = bi.toString(16);
+                } catch (NoSuchAlgorithmException e) {
+                    Slog.e(TAG, "Could not load PBKDF2 algorithm", e);
+                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
+                    return;
+                } catch (InvalidKeySpecException e) {
+                    Slog.e(TAG, "Invalid key spec when loading PBKDF2 algorithm", e);
+                    sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
+                    return;
+                }
+            }
+
+            int rc = StorageResultCode.OperationSucceeded;
+            try {
+                mConnector.execute("obb", "mount", mObbState.voldPath, new SensitiveArg(hashedKey),
+                        mObbState.ownerGid);
+            } catch (NativeDaemonConnectorException e) {
+                int code = e.getCode();
+                if (code != VoldResponseCode.OpFailedStorageBusy) {
+                    rc = StorageResultCode.OperationFailedInternalError;
+                }
+            }
+
+            if (rc == StorageResultCode.OperationSucceeded) {
+                if (DEBUG_OBB)
+                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
+
+                synchronized (mObbMounts) {
+                    addObbStateLocked(mObbState);
+                }
+
+                sendNewStatusOrIgnore(OnObbStateChangeListener.MOUNTED);
+            } else {
+                Slog.e(TAG, "Couldn't mount OBB file: " + rc);
+
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_MOUNT);
+            }
+        }
+
+        @Override
+        public void handleError() {
+            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("MountObbAction{");
+            sb.append(mObbState);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    class UnmountObbAction extends ObbAction {
+        private final boolean mForceUnmount;
+
+        UnmountObbAction(ObbState obbState, boolean force) {
+            super(obbState);
+            mForceUnmount = force;
+        }
+
+        @Override
+        public void handleExecute() throws IOException {
+            waitForReady();
+            warnOnNotMounted();
+
+            final ObbInfo obbInfo = getObbInfo();
+
+            final ObbState existingState;
+            synchronized (mObbMounts) {
+                existingState = mObbPathToStateMap.get(mObbState.rawPath);
+            }
+
+            if (existingState == null) {
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
+                return;
+            }
+
+            if (existingState.ownerGid != mObbState.ownerGid) {
+                Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
+                        + " (owned by GID " + existingState.ownerGid + ")");
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
+                return;
+            }
+
+            int rc = StorageResultCode.OperationSucceeded;
+            try {
+                final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
+                if (mForceUnmount) {
+                    cmd.appendArg("force");
+                }
+                mConnector.execute(cmd);
+            } catch (NativeDaemonConnectorException e) {
+                int code = e.getCode();
+                if (code == VoldResponseCode.OpFailedStorageBusy) {
+                    rc = StorageResultCode.OperationFailedStorageBusy;
+                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
+                    // If it's not mounted then we've already won.
+                    rc = StorageResultCode.OperationSucceeded;
+                } else {
+                    rc = StorageResultCode.OperationFailedInternalError;
+                }
+            }
+
+            if (rc == StorageResultCode.OperationSucceeded) {
+                synchronized (mObbMounts) {
+                    removeObbStateLocked(existingState);
+                }
+
+                sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
+            } else {
+                Slog.w(TAG, "Could not unmount OBB: " + existingState);
+                sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
+            }
+        }
+
+        @Override
+        public void handleError() {
+            sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_INTERNAL);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("UnmountObbAction{");
+            sb.append(mObbState);
+            sb.append(",force=");
+            sb.append(mForceUnmount);
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    @VisibleForTesting
+    public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
+        // TODO: allow caller to provide Environment for full testing
+        // TODO: extend to support OBB mounts on secondary external storage
+
+        // Only adjust paths when storage is emulated
+        if (!Environment.isExternalStorageEmulated()) {
+            return canonicalPath;
+        }
+
+        String path = canonicalPath.toString();
+
+        // First trim off any external storage prefix
+        final UserEnvironment userEnv = new UserEnvironment(userId);
+
+        // /storage/emulated/0
+        final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath();
+        // /storage/emulated_legacy
+        final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
+                .getAbsolutePath();
+
+        if (path.startsWith(externalPath)) {
+            path = path.substring(externalPath.length() + 1);
+        } else if (path.startsWith(legacyExternalPath)) {
+            path = path.substring(legacyExternalPath.length() + 1);
+        } else {
+            return canonicalPath;
+        }
+
+        // Handle special OBB paths on emulated storage
+        final String obbPath = "Android/obb";
+        if (path.startsWith(obbPath)) {
+            path = path.substring(obbPath.length() + 1);
+
+            if (forVold) {
+                return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
+            } else {
+                final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+                return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
+                        .getAbsolutePath();
+            }
+        }
+
+        // Handle normal external storage paths
+        if (forVold) {
+            return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
+        } else {
+            return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ", 160);
+
+        synchronized (mObbMounts) {
+            pw.println("mObbMounts:");
+            pw.increaseIndent();
+            final Iterator<Entry<IBinder, List<ObbState>>> binders = mObbMounts.entrySet()
+                    .iterator();
+            while (binders.hasNext()) {
+                Entry<IBinder, List<ObbState>> e = binders.next();
+                pw.println(e.getKey() + ":");
+                pw.increaseIndent();
+                final List<ObbState> obbStates = e.getValue();
+                for (final ObbState obbState : obbStates) {
+                    pw.println(obbState);
+                }
+                pw.decreaseIndent();
+            }
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mObbPathToStateMap:");
+            pw.increaseIndent();
+            final Iterator<Entry<String, ObbState>> maps = mObbPathToStateMap.entrySet().iterator();
+            while (maps.hasNext()) {
+                final Entry<String, ObbState> e = maps.next();
+                pw.print(e.getKey());
+                pw.print(" -> ");
+                pw.println(e.getValue());
+            }
+            pw.decreaseIndent();
+        }
+
+        synchronized (mVolumesLock) {
+            pw.println();
+            pw.println("mVolumes:");
+            pw.increaseIndent();
+            for (StorageVolume volume : mVolumes) {
+                pw.println(volume);
+                pw.increaseIndent();
+                pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
+                pw.decreaseIndent();
+            }
+            pw.decreaseIndent();
+        }
+
+        pw.println();
+        pw.println("mConnection:");
+        pw.increaseIndent();
+        mConnector.dump(fd, pw, args);
+        pw.decreaseIndent();
+    }
+
+    /** {@inheritDoc} */
+    public void monitor() {
+        if (mConnector != null) {
+            mConnector.monitor();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
new file mode 100644
index 0000000..417d6d8
--- /dev/null
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.google.android.collect.Lists;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.LinkedList;
+
+/**
+ * Generic connector class for interfacing with a native daemon which uses the
+ * {@code libsysutils} FrameworkListener protocol.
+ */
+final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
+    private static final boolean LOGD = false;
+
+    private final String TAG;
+
+    private String mSocket;
+    private OutputStream mOutputStream;
+    private LocalLog mLocalLog;
+
+    private final ResponseQueue mResponseQueue;
+
+    private INativeDaemonConnectorCallbacks mCallbacks;
+    private Handler mCallbackHandler;
+
+    private AtomicInteger mSequenceNumber;
+
+    private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
+    private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
+
+    /** Lock held whenever communicating with native daemon. */
+    private final Object mDaemonLock = new Object();
+
+    private final int BUFFER_SIZE = 4096;
+
+    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
+            int responseQueueSize, String logTag, int maxLogSize) {
+        mCallbacks = callbacks;
+        mSocket = socket;
+        mResponseQueue = new ResponseQueue(responseQueueSize);
+        mSequenceNumber = new AtomicInteger(0);
+        TAG = logTag != null ? logTag : "NativeDaemonConnector";
+        mLocalLog = new LocalLog(maxLogSize);
+    }
+
+    @Override
+    public void run() {
+        mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
+
+        while (true) {
+            try {
+                listenToSocket();
+            } catch (Exception e) {
+                loge("Error in NativeDaemonConnector: " + e);
+                SystemClock.sleep(5000);
+            }
+        }
+    }
+
+    @Override
+    public boolean handleMessage(Message msg) {
+        String event = (String) msg.obj;
+        try {
+            if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
+                log(String.format("Unhandled event '%s'", event));
+            }
+        } catch (Exception e) {
+            loge("Error handling '" + event + "': " + e);
+        }
+        return true;
+    }
+
+    private LocalSocketAddress determineSocketAddress() {
+        // If we're testing, set up a socket in a namespace that's accessible to test code.
+        // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
+        // production devices, even if said native daemons ill-advisedly pick a socket name that
+        // starts with __test__, only allow this on debug builds.
+        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
+            return new LocalSocketAddress(mSocket);
+        } else {
+            return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
+        }
+    }
+
+    private void listenToSocket() throws IOException {
+        LocalSocket socket = null;
+
+        try {
+            socket = new LocalSocket();
+            LocalSocketAddress address = determineSocketAddress();
+
+            socket.connect(address);
+
+            InputStream inputStream = socket.getInputStream();
+            synchronized (mDaemonLock) {
+                mOutputStream = socket.getOutputStream();
+            }
+
+            mCallbacks.onDaemonConnected();
+
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int start = 0;
+
+            while (true) {
+                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
+                if (count < 0) {
+                    loge("got " + count + " reading with start = " + start);
+                    break;
+                }
+
+                // Add our starting point to the count and reset the start.
+                count += start;
+                start = 0;
+
+                for (int i = 0; i < count; i++) {
+                    if (buffer[i] == 0) {
+                        final String rawEvent = new String(
+                                buffer, start, i - start, StandardCharsets.UTF_8);
+                        log("RCV <- {" + rawEvent + "}");
+
+                        try {
+                            final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
+                                    rawEvent);
+                            if (event.isClassUnsolicited()) {
+                                // TODO: migrate to sending NativeDaemonEvent instances
+                                mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
+                                        event.getCode(), event.getRawEvent()));
+                            } else {
+                                mResponseQueue.add(event.getCmdNumber(), event);
+                            }
+                        } catch (IllegalArgumentException e) {
+                            log("Problem parsing message: " + rawEvent + " - " + e);
+                        }
+
+                        start = i + 1;
+                    }
+                }
+                if (start == 0) {
+                    final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
+                    log("RCV incomplete <- {" + rawEvent + "}");
+                }
+
+                // We should end at the amount we read. If not, compact then
+                // buffer and read again.
+                if (start != count) {
+                    final int remaining = BUFFER_SIZE - start;
+                    System.arraycopy(buffer, start, buffer, 0, remaining);
+                    start = remaining;
+                } else {
+                    start = 0;
+                }
+            }
+        } catch (IOException ex) {
+            loge("Communications error: " + ex);
+            throw ex;
+        } finally {
+            synchronized (mDaemonLock) {
+                if (mOutputStream != null) {
+                    try {
+                        loge("closing stream for " + mSocket);
+                        mOutputStream.close();
+                    } catch (IOException e) {
+                        loge("Failed closing output stream: " + e);
+                    }
+                    mOutputStream = null;
+                }
+            }
+
+            try {
+                if (socket != null) {
+                    socket.close();
+                }
+            } catch (IOException ex) {
+                loge("Failed closing socket: " + ex);
+            }
+        }
+    }
+
+    /**
+     * Wrapper around argument that indicates it's sensitive and shouldn't be
+     * logged.
+     */
+    public static class SensitiveArg {
+        private final Object mArg;
+
+        public SensitiveArg(Object arg) {
+            mArg = arg;
+        }
+
+        @Override
+        public String toString() {
+            return String.valueOf(mArg);
+        }
+    }
+
+    /**
+     * Make command for daemon, escaping arguments as needed.
+     */
+    @VisibleForTesting
+    static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
+            String cmd, Object... args) {
+        if (cmd.indexOf('\0') >= 0) {
+            throw new IllegalArgumentException("Unexpected command: " + cmd);
+        }
+        if (cmd.indexOf(' ') >= 0) {
+            throw new IllegalArgumentException("Arguments must be separate from command");
+        }
+
+        rawBuilder.append(sequenceNumber).append(' ').append(cmd);
+        logBuilder.append(sequenceNumber).append(' ').append(cmd);
+        for (Object arg : args) {
+            final String argString = String.valueOf(arg);
+            if (argString.indexOf('\0') >= 0) {
+                throw new IllegalArgumentException("Unexpected argument: " + arg);
+            }
+
+            rawBuilder.append(' ');
+            logBuilder.append(' ');
+
+            appendEscaped(rawBuilder, argString);
+            if (arg instanceof SensitiveArg) {
+                logBuilder.append("[scrubbed]");
+            } else {
+                appendEscaped(logBuilder, argString);
+            }
+        }
+
+        rawBuilder.append('\0');
+    }
+
+    /**
+     * Issue the given command to the native daemon and return a single expected
+     * response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
+        return execute(cmd.mCmd, cmd.mArguments.toArray());
+    }
+
+    /**
+     * Issue the given command to the native daemon and return a single expected
+     * response. Any arguments must be separated from base command so they can
+     * be properly escaped.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent execute(String cmd, Object... args)
+            throws NativeDaemonConnectorException {
+        final NativeDaemonEvent[] events = executeForList(cmd, args);
+        if (events.length != 1) {
+            throw new NativeDaemonConnectorException(
+                    "Expected exactly one response, but received " + events.length);
+        }
+        return events[0];
+    }
+
+    /**
+     * Issue the given command to the native daemon and return any
+     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+     * final terminal response.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
+        return executeForList(cmd.mCmd, cmd.mArguments.toArray());
+    }
+
+    /**
+     * Issue the given command to the native daemon and return any
+     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
+     * final terminal response. Any arguments must be separated from base
+     * command so they can be properly escaped.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] executeForList(String cmd, Object... args)
+            throws NativeDaemonConnectorException {
+            return execute(DEFAULT_TIMEOUT, cmd, args);
+    }
+
+    /**
+     * Issue the given command to the native daemon and return any {@linke
+     * NativeDaemonEvent@isClassContinue()} responses, including the final
+     * terminal response. Note that the timeout does not count time in deep
+     * sleep. Any arguments must be separated from base command so they can be
+     * properly escaped.
+     *
+     * @throws NativeDaemonConnectorException when problem communicating with
+     *             native daemon, or if the response matches
+     *             {@link NativeDaemonEvent#isClassClientError()} or
+     *             {@link NativeDaemonEvent#isClassServerError()}.
+     */
+    public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
+            throws NativeDaemonConnectorException {
+        final long startTime = SystemClock.elapsedRealtime();
+
+        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
+
+        final StringBuilder rawBuilder = new StringBuilder();
+        final StringBuilder logBuilder = new StringBuilder();
+        final int sequenceNumber = mSequenceNumber.incrementAndGet();
+
+        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
+
+        final String rawCmd = rawBuilder.toString();
+        final String logCmd = logBuilder.toString();
+
+        log("SND -> {" + logCmd + "}");
+
+        synchronized (mDaemonLock) {
+            if (mOutputStream == null) {
+                throw new NativeDaemonConnectorException("missing output stream");
+            } else {
+                try {
+                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
+                } catch (IOException e) {
+                    throw new NativeDaemonConnectorException("problem sending command", e);
+                }
+            }
+        }
+
+        NativeDaemonEvent event = null;
+        do {
+            event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
+            if (event == null) {
+                loge("timed-out waiting for response to " + logCmd);
+                throw new NativeDaemonFailureException(logCmd, event);
+            }
+            log("RMV <- {" + event + "}");
+            events.add(event);
+        } while (event.isClassContinue());
+
+        final long endTime = SystemClock.elapsedRealtime();
+        if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
+            loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
+        }
+
+        if (event.isClassClientError()) {
+            throw new NativeDaemonArgumentException(logCmd, event);
+        }
+        if (event.isClassServerError()) {
+            throw new NativeDaemonFailureException(logCmd, event);
+        }
+
+        return events.toArray(new NativeDaemonEvent[events.size()]);
+    }
+
+    /**
+     * Append the given argument to {@link StringBuilder}, escaping as needed,
+     * and surrounding with quotes when it contains spaces.
+     */
+    @VisibleForTesting
+    static void appendEscaped(StringBuilder builder, String arg) {
+        final boolean hasSpaces = arg.indexOf(' ') >= 0;
+        if (hasSpaces) {
+            builder.append('"');
+        }
+
+        final int length = arg.length();
+        for (int i = 0; i < length; i++) {
+            final char c = arg.charAt(i);
+
+            if (c == '"') {
+                builder.append("\\\"");
+            } else if (c == '\\') {
+                builder.append("\\\\");
+            } else {
+                builder.append(c);
+            }
+        }
+
+        if (hasSpaces) {
+            builder.append('"');
+        }
+    }
+
+    private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
+        public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
+            super(command, event);
+        }
+
+        @Override
+        public IllegalArgumentException rethrowAsParcelableException() {
+            throw new IllegalArgumentException(getMessage(), this);
+        }
+    }
+
+    private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
+        public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
+            super(command, event);
+        }
+    }
+
+    /**
+     * Command builder that handles argument list building. Any arguments must
+     * be separated from base command so they can be properly escaped.
+     */
+    public static class Command {
+        private String mCmd;
+        private ArrayList<Object> mArguments = Lists.newArrayList();
+
+        public Command(String cmd, Object... args) {
+            mCmd = cmd;
+            for (Object arg : args) {
+                appendArg(arg);
+            }
+        }
+
+        public Command appendArg(Object arg) {
+            mArguments.add(arg);
+            return this;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void monitor() {
+        synchronized (mDaemonLock) { }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mLocalLog.dump(fd, pw, args);
+        pw.println();
+        mResponseQueue.dump(fd, pw, args);
+    }
+
+    private void log(String logstring) {
+        if (LOGD) Slog.d(TAG, logstring);
+        mLocalLog.log(logstring);
+    }
+
+    private void loge(String logstring) {
+        Slog.e(TAG, logstring);
+        mLocalLog.log(logstring);
+    }
+
+    private static class ResponseQueue {
+
+        private static class PendingCmd {
+            public final int cmdNum;
+            public final String logCmd;
+
+            public BlockingQueue<NativeDaemonEvent> responses =
+                    new ArrayBlockingQueue<NativeDaemonEvent>(10);
+
+            // The availableResponseCount member is used to track when we can remove this
+            // instance from the ResponseQueue.
+            // This is used under the protection of a sync of the mPendingCmds object.
+            // A positive value means we've had more writers retreive this object while
+            // a negative value means we've had more readers.  When we've had an equal number
+            // (it goes to zero) we can remove this object from the mPendingCmds list.
+            // Note that we may have more responses for this command (and more readers
+            // coming), but that would result in a new PendingCmd instance being created
+            // and added with the same cmdNum.
+            // Also note that when this goes to zero it just means a parity of readers and
+            // writers have retrieved this object - not that they are done using it.  The
+            // responses queue may well have more responses yet to be read or may get more
+            // responses added to it.  But all those readers/writers have retreived and
+            // hold references to this instance already so it can be removed from
+            // mPendingCmds queue.
+            public int availableResponseCount;
+
+            public PendingCmd(int cmdNum, String logCmd) {
+                this.cmdNum = cmdNum;
+                this.logCmd = logCmd;
+            }
+        }
+
+        private final LinkedList<PendingCmd> mPendingCmds;
+        private int mMaxCount;
+
+        ResponseQueue(int maxCount) {
+            mPendingCmds = new LinkedList<PendingCmd>();
+            mMaxCount = maxCount;
+        }
+
+        public void add(int cmdNum, NativeDaemonEvent response) {
+            PendingCmd found = null;
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    if (pendingCmd.cmdNum == cmdNum) {
+                        found = pendingCmd;
+                        break;
+                    }
+                }
+                if (found == null) {
+                    // didn't find it - make sure our queue isn't too big before adding
+                    while (mPendingCmds.size() >= mMaxCount) {
+                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                                "more buffered than allowed: " + mPendingCmds.size() +
+                                " >= " + mMaxCount);
+                        // let any waiter timeout waiting for this
+                        PendingCmd pendingCmd = mPendingCmds.remove();
+                        Slog.e("NativeDaemonConnector.ResponseQueue",
+                                "Removing request: " + pendingCmd.logCmd + " (" +
+                                pendingCmd.cmdNum + ")");
+                    }
+                    found = new PendingCmd(cmdNum, null);
+                    mPendingCmds.add(found);
+                }
+                found.availableResponseCount++;
+                // if a matching remove call has already retrieved this we can remove this
+                // instance from our list
+                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
+            }
+            try {
+                found.responses.put(response);
+            } catch (InterruptedException e) { }
+        }
+
+        // note that the timeout does not count time in deep sleep.  If you don't want
+        // the device to sleep, hold a wakelock
+        public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
+            PendingCmd found = null;
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    if (pendingCmd.cmdNum == cmdNum) {
+                        found = pendingCmd;
+                        break;
+                    }
+                }
+                if (found == null) {
+                    found = new PendingCmd(cmdNum, logCmd);
+                    mPendingCmds.add(found);
+                }
+                found.availableResponseCount--;
+                // if a matching add call has already retrieved this we can remove this
+                // instance from our list
+                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
+            }
+            NativeDaemonEvent result = null;
+            try {
+                result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {}
+            if (result == null) {
+                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
+            }
+            return result;
+        }
+
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("Pending requests:");
+            synchronized (mPendingCmds) {
+                for (PendingCmd pendingCmd : mPendingCmds) {
+                    pw.println("  Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/NativeDaemonConnectorException.java b/services/core/java/com/android/server/NativeDaemonConnectorException.java
new file mode 100644
index 0000000..590bbcc
--- /dev/null
+++ b/services/core/java/com/android/server/NativeDaemonConnectorException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2006 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.os.Parcel;
+
+/**
+ * An exception that indicates there was an error with a
+ * {@link NativeDaemonConnector} operation.
+ */
+public class NativeDaemonConnectorException extends Exception {
+    private String mCmd;
+    private NativeDaemonEvent mEvent;
+
+    public NativeDaemonConnectorException(String detailMessage) {
+        super(detailMessage);
+    }
+
+    public NativeDaemonConnectorException(String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+    }
+
+    public NativeDaemonConnectorException(String cmd, NativeDaemonEvent event) {
+        super("command '" + cmd + "' failed with '" + event + "'");
+        mCmd = cmd;
+        mEvent = event;
+    }
+
+    public int getCode() {
+        return mEvent.getCode();
+    }
+
+    public String getCmd() {
+        return mCmd;
+    }
+
+    /**
+     * Rethrow as a {@link RuntimeException} subclass that is handled by
+     * {@link Parcel#writeException(Exception)}.
+     */
+    public IllegalArgumentException rethrowAsParcelableException() {
+        throw new IllegalStateException(getMessage(), this);
+    }
+}
diff --git a/services/core/java/com/android/server/NativeDaemonEvent.java b/services/core/java/com/android/server/NativeDaemonEvent.java
new file mode 100644
index 0000000..2095152
--- /dev/null
+++ b/services/core/java/com/android/server/NativeDaemonEvent.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.util.Slog;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+
+/**
+ * Parsed event from native side of {@link NativeDaemonConnector}.
+ */
+public class NativeDaemonEvent {
+
+    // TODO: keep class ranges in sync with ResponseCode.h
+    // TODO: swap client and server error ranges to roughly mirror HTTP spec
+
+    private final int mCmdNumber;
+    private final int mCode;
+    private final String mMessage;
+    private final String mRawEvent;
+    private String[] mParsed;
+
+    private NativeDaemonEvent(int cmdNumber, int code, String message, String rawEvent) {
+        mCmdNumber = cmdNumber;
+        mCode = code;
+        mMessage = message;
+        mRawEvent = rawEvent;
+        mParsed = null;
+    }
+
+    public int getCmdNumber() {
+        return mCmdNumber;
+    }
+
+    public int getCode() {
+        return mCode;
+    }
+
+    public String getMessage() {
+        return mMessage;
+    }
+
+    @Deprecated
+    public String getRawEvent() {
+        return mRawEvent;
+    }
+
+    @Override
+    public String toString() {
+        return mRawEvent;
+    }
+
+    /**
+     * Test if event represents a partial response which is continued in
+     * additional subsequent events.
+     */
+    public boolean isClassContinue() {
+        return mCode >= 100 && mCode < 200;
+    }
+
+    /**
+     * Test if event represents a command success.
+     */
+    public boolean isClassOk() {
+        return mCode >= 200 && mCode < 300;
+    }
+
+    /**
+     * Test if event represents a remote native daemon error.
+     */
+    public boolean isClassServerError() {
+        return mCode >= 400 && mCode < 500;
+    }
+
+    /**
+     * Test if event represents a command syntax or argument error.
+     */
+    public boolean isClassClientError() {
+        return mCode >= 500 && mCode < 600;
+    }
+
+    /**
+     * Test if event represents an unsolicited event from native daemon.
+     */
+    public boolean isClassUnsolicited() {
+        return isClassUnsolicited(mCode);
+    }
+
+    private static boolean isClassUnsolicited(int code) {
+        return code >= 600 && code < 700;
+    }
+
+    /**
+     * Verify this event matches the given code.
+     *
+     * @throws IllegalStateException if {@link #getCode()} doesn't match.
+     */
+    public void checkCode(int code) {
+        if (mCode != code) {
+            throw new IllegalStateException("Expected " + code + " but was: " + this);
+        }
+    }
+
+    /**
+     * Parse the given raw event into {@link NativeDaemonEvent} instance.
+     *
+     * @throws IllegalArgumentException when line doesn't match format expected
+     *             from native side.
+     */
+    public static NativeDaemonEvent parseRawEvent(String rawEvent) {
+        final String[] parsed = rawEvent.split(" ");
+        if (parsed.length < 2) {
+            throw new IllegalArgumentException("Insufficient arguments");
+        }
+
+        int skiplength = 0;
+
+        final int code;
+        try {
+            code = Integer.parseInt(parsed[0]);
+            skiplength = parsed[0].length() + 1;
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("problem parsing code", e);
+        }
+
+        int cmdNumber = -1;
+        if (isClassUnsolicited(code) == false) {
+            if (parsed.length < 3) {
+                throw new IllegalArgumentException("Insufficient arguemnts");
+            }
+            try {
+                cmdNumber = Integer.parseInt(parsed[1]);
+                skiplength += parsed[1].length() + 1;
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("problem parsing cmdNumber", e);
+            }
+        }
+
+        final String message = rawEvent.substring(skiplength);
+
+        return new NativeDaemonEvent(cmdNumber, code, message, rawEvent);
+    }
+
+    /**
+     * Filter the given {@link NativeDaemonEvent} list, returning
+     * {@link #getMessage()} for any events matching the requested code.
+     */
+    public static String[] filterMessageList(NativeDaemonEvent[] events, int matchCode) {
+        final ArrayList<String> result = Lists.newArrayList();
+        for (NativeDaemonEvent event : events) {
+            if (event.getCode() == matchCode) {
+                result.add(event.getMessage());
+            }
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    /**
+     * Find the Nth field of the event.
+     *
+     * This ignores and code or cmdNum, the first return value is given for N=0.
+     * Also understands "\"quoted\" multiword responses" and tries them as a single field
+     */
+    public String getField(int n) {
+        if (mParsed == null) {
+            mParsed = unescapeArgs(mRawEvent);
+        }
+        n += 2; // skip code and command#
+        if (n > mParsed.length) return null;
+            return mParsed[n];
+        }
+
+    public static String[] unescapeArgs(String rawEvent) {
+        final boolean DEBUG_ROUTINE = false;
+        final String LOGTAG = "unescapeArgs";
+        final ArrayList<String> parsed = new ArrayList<String>();
+        final int length = rawEvent.length();
+        int current = 0;
+        int wordEnd = -1;
+        boolean quoted = false;
+
+        if (DEBUG_ROUTINE) Slog.e(LOGTAG, "parsing '" + rawEvent + "'");
+        if (rawEvent.charAt(current) == '\"') {
+            quoted = true;
+            current++;
+        }
+        while (current < length) {
+            // find the end of the word
+            if (quoted) {
+                wordEnd = current;
+                while ((wordEnd = rawEvent.indexOf('\"', wordEnd)) != -1) {
+                    if (rawEvent.charAt(wordEnd - 1) != '\\') {
+                        break;
+                    } else {
+                        wordEnd++; // skip this escaped quote and keep looking
+                    }
+                }
+            } else {
+                wordEnd = rawEvent.indexOf(' ', current);
+            }
+            // if we didn't find the end-o-word token, take the rest of the string
+            if (wordEnd == -1) wordEnd = length;
+            String word = rawEvent.substring(current, wordEnd);
+            current += word.length();
+            if (!quoted) {
+                word = word.trim();
+            } else {
+                current++;  // skip the trailing quote
+            }
+            // unescape stuff within the word
+            word = word.replace("\\\\", "\\");
+            word = word.replace("\\\"", "\"");
+
+            if (DEBUG_ROUTINE) Slog.e(LOGTAG, "found '" + word + "'");
+            parsed.add(word);
+
+            // find the beginning of the next word - either of these options
+            int nextSpace = rawEvent.indexOf(' ', current);
+            int nextQuote = rawEvent.indexOf(" \"", current);
+            if (DEBUG_ROUTINE) {
+                Slog.e(LOGTAG, "nextSpace=" + nextSpace + ", nextQuote=" + nextQuote);
+            }
+            if (nextQuote > -1 && nextQuote <= nextSpace) {
+                quoted = true;
+                current = nextQuote + 2;
+            } else {
+                quoted = false;
+                if (nextSpace > -1) {
+                    current = nextSpace + 1;
+                }
+            } // else we just start the next word after the current and read til the end
+            if (DEBUG_ROUTINE) {
+                Slog.e(LOGTAG, "next loop - current=" + current +
+                        ", length=" + length + ", quoted=" + quoted);
+            }
+        }
+        return parsed.toArray(new String[parsed.size()]);
+    }
+}
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
new file mode 100644
index 0000000..ad7ec99
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -0,0 +1,1810 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.SHUTDOWN;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_TETHERING;
+import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.GetMarkResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsListResult;
+import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult;
+import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED;
+
+import android.content.Context;
+import android.net.INetworkManagementEventObserver;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkStats;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.util.Preconditions;
+import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.NativeDaemonConnector.SensitiveArg;
+import com.android.server.net.LockdownVpnTracker;
+import com.google.android.collect.Maps;
+
+import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.StringTokenizer;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @hide
+ */
+public class NetworkManagementService extends INetworkManagementService.Stub
+        implements Watchdog.Monitor {
+    private static final String TAG = "NetworkManagementService";
+    private static final boolean DBG = false;
+    private static final String NETD_TAG = "NetdConnector";
+    private static final String NETD_SOCKET_NAME = "netd";
+
+    private static final String ADD = "add";
+    private static final String REMOVE = "remove";
+
+    private static final String ALLOW = "allow";
+    private static final String DENY = "deny";
+
+    private static final String DEFAULT = "default";
+    private static final String SECONDARY = "secondary";
+
+    /**
+     * Name representing {@link #setGlobalAlert(long)} limit when delivered to
+     * {@link INetworkManagementEventObserver#limitReached(String, String)}.
+     */
+    public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
+
+    class NetdResponseCode {
+        /* Keep in sync with system/netd/ResponseCode.h */
+        public static final int InterfaceListResult       = 110;
+        public static final int TetherInterfaceListResult = 111;
+        public static final int TetherDnsFwdTgtListResult = 112;
+        public static final int TtyListResult             = 113;
+        public static final int TetheringStatsListResult  = 114;
+
+        public static final int TetherStatusResult        = 210;
+        public static final int IpFwdStatusResult         = 211;
+        public static final int InterfaceGetCfgResult     = 213;
+        public static final int SoftapStatusResult        = 214;
+        public static final int InterfaceRxCounterResult  = 216;
+        public static final int InterfaceTxCounterResult  = 217;
+        public static final int QuotaCounterResult        = 220;
+        public static final int TetheringStatsResult      = 221;
+        public static final int DnsProxyQueryResult       = 222;
+        public static final int ClatdStatusResult         = 223;
+        public static final int GetMarkResult             = 225;
+
+        public static final int InterfaceChange           = 600;
+        public static final int BandwidthControl          = 601;
+        public static final int InterfaceClassActivity    = 613;
+        public static final int InterfaceAddressChange    = 614;
+        public static final int InterfaceDnsServerInfo    = 615;
+    }
+
+    /**
+     * Binder context for this service
+     */
+    private Context mContext;
+
+    /**
+     * connector object for communicating with netd
+     */
+    private NativeDaemonConnector mConnector;
+
+    private final Handler mMainHandler = new Handler();
+
+    private Thread mThread;
+    private CountDownLatch mConnectedSignal = new CountDownLatch(1);
+
+    private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
+            new RemoteCallbackList<INetworkManagementEventObserver>();
+
+    private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
+
+    private Object mQuotaLock = new Object();
+    /** Set of interfaces with active quotas. */
+    private HashMap<String, Long> mActiveQuotas = Maps.newHashMap();
+    /** Set of interfaces with active alerts. */
+    private HashMap<String, Long> mActiveAlerts = Maps.newHashMap();
+    /** Set of UIDs with active reject rules. */
+    private SparseBooleanArray mUidRejectOnQuota = new SparseBooleanArray();
+
+    private Object mIdleTimerLock = new Object();
+    /** Set of interfaces with active idle timers. */
+    private static class IdleTimerParams {
+        public final int timeout;
+        public final String label;
+        public int networkCount;
+
+        IdleTimerParams(int timeout, String label) {
+            this.timeout = timeout;
+            this.label = label;
+            this.networkCount = 1;
+        }
+    }
+    private HashMap<String, IdleTimerParams> mActiveIdleTimers = Maps.newHashMap();
+
+    private volatile boolean mBandwidthControlEnabled;
+    private volatile boolean mFirewallEnabled;
+
+    /**
+     * Constructs a new NetworkManagementService instance
+     *
+     * @param context  Binder context for this service
+     */
+    private NetworkManagementService(Context context, String socket) {
+        mContext = context;
+
+        if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
+            return;
+        }
+
+        mConnector = new NativeDaemonConnector(
+                new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160);
+        mThread = new Thread(mConnector, NETD_TAG);
+
+        // Add ourself to the Watchdog monitors.
+        Watchdog.getInstance().addMonitor(this);
+    }
+
+    static NetworkManagementService create(Context context,
+            String socket) throws InterruptedException {
+        final NetworkManagementService service = new NetworkManagementService(context, socket);
+        final CountDownLatch connectedSignal = service.mConnectedSignal;
+        if (DBG) Slog.d(TAG, "Creating NetworkManagementService");
+        service.mThread.start();
+        if (DBG) Slog.d(TAG, "Awaiting socket connection");
+        connectedSignal.await();
+        if (DBG) Slog.d(TAG, "Connected");
+        return service;
+    }
+
+    public static NetworkManagementService create(Context context) throws InterruptedException {
+        return create(context, NETD_SOCKET_NAME);
+    }
+
+    public void systemReady() {
+        prepareNativeDaemon();
+        if (DBG) Slog.d(TAG, "Prepared");
+    }
+
+    @Override
+    public void registerObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        mObservers.register(observer);
+    }
+
+    @Override
+    public void unregisterObserver(INetworkManagementEventObserver observer) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        mObservers.unregister(observer);
+    }
+
+    /**
+     * Notify our observers of an interface status change
+     */
+    private void notifyInterfaceStatusChanged(String iface, boolean up) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceStatusChanged(iface, up);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of an interface link state change
+     * (typically, an Ethernet cable has been plugged-in or unplugged).
+     */
+    private void notifyInterfaceLinkStateChanged(String iface, boolean up) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceLinkStateChanged(iface, up);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of an interface addition.
+     */
+    private void notifyInterfaceAdded(String iface) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceAdded(iface);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of an interface removal.
+     */
+    private void notifyInterfaceRemoved(String iface) {
+        // netd already clears out quota and alerts for removed ifaces; update
+        // our sanity-checking state.
+        mActiveAlerts.remove(iface);
+        mActiveQuotas.remove(iface);
+
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceRemoved(iface);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of a limit reached.
+     */
+    private void notifyLimitReached(String limitName, String iface) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).limitReached(limitName, iface);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of a change in the data activity state of the interface
+     */
+    private void notifyInterfaceClassActivity(String label, boolean active) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Prepare native daemon once connected, enabling modules and pushing any
+     * existing in-memory rules.
+     */
+    private void prepareNativeDaemon() {
+        mBandwidthControlEnabled = false;
+
+        // only enable bandwidth control when support exists
+        final boolean hasKernelSupport = new File("/proc/net/xt_qtaguid/ctrl").exists();
+        if (hasKernelSupport) {
+            Slog.d(TAG, "enabling bandwidth control");
+            try {
+                mConnector.execute("bandwidth", "enable");
+                mBandwidthControlEnabled = true;
+            } catch (NativeDaemonConnectorException e) {
+                Log.wtf(TAG, "problem enabling bandwidth controls", e);
+            }
+        } else {
+            Slog.d(TAG, "not enabling bandwidth control");
+        }
+
+        SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0");
+
+        if (mBandwidthControlEnabled) {
+            try {
+                IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME))
+                        .noteNetworkStatsEnabled();
+            } catch (RemoteException e) {
+            }
+        }
+
+        // push any existing quota or UID rules
+        synchronized (mQuotaLock) {
+            int size = mActiveQuotas.size();
+            if (size > 0) {
+                Slog.d(TAG, "pushing " + size + " active quota rules");
+                final HashMap<String, Long> activeQuotas = mActiveQuotas;
+                mActiveQuotas = Maps.newHashMap();
+                for (Map.Entry<String, Long> entry : activeQuotas.entrySet()) {
+                    setInterfaceQuota(entry.getKey(), entry.getValue());
+                }
+            }
+
+            size = mActiveAlerts.size();
+            if (size > 0) {
+                Slog.d(TAG, "pushing " + size + " active alert rules");
+                final HashMap<String, Long> activeAlerts = mActiveAlerts;
+                mActiveAlerts = Maps.newHashMap();
+                for (Map.Entry<String, Long> entry : activeAlerts.entrySet()) {
+                    setInterfaceAlert(entry.getKey(), entry.getValue());
+                }
+            }
+
+            size = mUidRejectOnQuota.size();
+            if (size > 0) {
+                Slog.d(TAG, "pushing " + size + " active uid rules");
+                final SparseBooleanArray uidRejectOnQuota = mUidRejectOnQuota;
+                mUidRejectOnQuota = new SparseBooleanArray();
+                for (int i = 0; i < uidRejectOnQuota.size(); i++) {
+                    setUidNetworkRules(uidRejectOnQuota.keyAt(i), uidRejectOnQuota.valueAt(i));
+                }
+            }
+        }
+
+        // TODO: Push any existing firewall state
+        setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled());
+    }
+
+    /**
+     * Notify our observers of a new or updated interface address.
+     */
+    private void notifyAddressUpdated(String iface, LinkAddress address) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).addressUpdated(iface, address);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of a deleted interface address.
+     */
+    private void notifyAddressRemoved(String iface, LinkAddress address) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).addressRemoved(iface, address);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
+     * Notify our observers of DNS server information received.
+     */
+    private void notifyInterfaceDnsServerInfo(String iface, long lifetime, String[] addresses) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceDnsServerInfo(iface, lifetime, addresses);
+            } catch (RemoteException e) {
+            } catch (RuntimeException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    //
+    // Netd Callback handling
+    //
+
+    private class NetdCallbackReceiver implements INativeDaemonConnectorCallbacks {
+        @Override
+        public void onDaemonConnected() {
+            // event is dispatched from internal NDC thread, so we prepare the
+            // daemon back on main thread.
+            if (mConnectedSignal != null) {
+                mConnectedSignal.countDown();
+                mConnectedSignal = null;
+            } else {
+                mMainHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        prepareNativeDaemon();
+                    }
+                });
+            }
+        }
+
+        @Override
+        public boolean onEvent(int code, String raw, String[] cooked) {
+            String errorMessage = String.format("Invalid event from daemon (%s)", raw);
+            switch (code) {
+            case NetdResponseCode.InterfaceChange:
+                    /*
+                     * a network interface change occured
+                     * Format: "NNN Iface added <name>"
+                     *         "NNN Iface removed <name>"
+                     *         "NNN Iface changed <name> <up/down>"
+                     *         "NNN Iface linkstatus <name> <up/down>"
+                     */
+                    if (cooked.length < 4 || !cooked[1].equals("Iface")) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+                    if (cooked[2].equals("added")) {
+                        notifyInterfaceAdded(cooked[3]);
+                        return true;
+                    } else if (cooked[2].equals("removed")) {
+                        notifyInterfaceRemoved(cooked[3]);
+                        return true;
+                    } else if (cooked[2].equals("changed") && cooked.length == 5) {
+                        notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up"));
+                        return true;
+                    } else if (cooked[2].equals("linkstate") && cooked.length == 5) {
+                        notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up"));
+                        return true;
+                    }
+                    throw new IllegalStateException(errorMessage);
+                    // break;
+            case NetdResponseCode.BandwidthControl:
+                    /*
+                     * Bandwidth control needs some attention
+                     * Format: "NNN limit alert <alertName> <ifaceName>"
+                     */
+                    if (cooked.length < 5 || !cooked[1].equals("limit")) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+                    if (cooked[2].equals("alert")) {
+                        notifyLimitReached(cooked[3], cooked[4]);
+                        return true;
+                    }
+                    throw new IllegalStateException(errorMessage);
+                    // break;
+            case NetdResponseCode.InterfaceClassActivity:
+                    /*
+                     * An network interface class state changed (active/idle)
+                     * Format: "NNN IfaceClass <active/idle> <label>"
+                     */
+                    if (cooked.length < 4 || !cooked[1].equals("IfaceClass")) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+                    boolean isActive = cooked[2].equals("active");
+                    notifyInterfaceClassActivity(cooked[3], isActive);
+                    return true;
+                    // break;
+            case NetdResponseCode.InterfaceAddressChange:
+                    /*
+                     * A network address change occurred
+                     * Format: "NNN Address updated <addr> <iface> <flags> <scope>"
+                     *         "NNN Address removed <addr> <iface> <flags> <scope>"
+                     */
+                    if (cooked.length < 7 || !cooked[1].equals("Address")) {
+                        throw new IllegalStateException(errorMessage);
+                    }
+
+                    String iface = cooked[4];
+                    LinkAddress address;
+                    try {
+                        int flags = Integer.parseInt(cooked[5]);
+                        int scope = Integer.parseInt(cooked[6]);
+                        address = new LinkAddress(cooked[3], flags, scope);
+                    } catch(NumberFormatException e) {     // Non-numeric lifetime or scope.
+                        throw new IllegalStateException(errorMessage, e);
+                    } catch(IllegalArgumentException e) {  // Malformed/invalid IP address.
+                        throw new IllegalStateException(errorMessage, e);
+                    }
+
+                    if (cooked[2].equals("updated")) {
+                        notifyAddressUpdated(iface, address);
+                    } else {
+                        notifyAddressRemoved(iface, address);
+                    }
+                    return true;
+                    // break;
+            case NetdResponseCode.InterfaceDnsServerInfo:
+                    /*
+                     * Information about available DNS servers has been received.
+                     * Format: "NNN DnsInfo servers <interface> <lifetime> <servers>"
+                     */
+                    long lifetime;  // Actually a 32-bit unsigned integer.
+
+                    if (cooked.length == 6 &&
+                        cooked[1].equals("DnsInfo") &&
+                        cooked[2].equals("servers")) {
+                        try {
+                            lifetime = Long.parseLong(cooked[4]);
+                        } catch (NumberFormatException e) {
+                            throw new IllegalStateException(errorMessage);
+                        }
+                        String[] servers = cooked[5].split(",");
+                        notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers);
+                    }
+                    return true;
+                    // break;
+            default: break;
+            }
+            return false;
+        }
+    }
+
+
+    //
+    // INetworkManagementService members
+    //
+
+    @Override
+    public String[] listInterfaces() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("interface", "list"), InterfaceListResult);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public InterfaceConfiguration getInterfaceConfig(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("interface", "getcfg", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        event.checkCode(InterfaceGetCfgResult);
+
+        // Rsp: 213 xx:xx:xx:xx:xx:xx yyy.yyy.yyy.yyy zzz flag1 flag2 flag3
+        final StringTokenizer st = new StringTokenizer(event.getMessage());
+
+        InterfaceConfiguration cfg;
+        try {
+            cfg = new InterfaceConfiguration();
+            cfg.setHardwareAddress(st.nextToken(" "));
+            InetAddress addr = null;
+            int prefixLength = 0;
+            try {
+                addr = NetworkUtils.numericToInetAddress(st.nextToken());
+            } catch (IllegalArgumentException iae) {
+                Slog.e(TAG, "Failed to parse ipaddr", iae);
+            }
+
+            try {
+                prefixLength = Integer.parseInt(st.nextToken());
+            } catch (NumberFormatException nfe) {
+                Slog.e(TAG, "Failed to parse prefixLength", nfe);
+            }
+
+            cfg.setLinkAddress(new LinkAddress(addr, prefixLength));
+            while (st.hasMoreTokens()) {
+                cfg.setFlag(st.nextToken());
+            }
+        } catch (NoSuchElementException nsee) {
+            throw new IllegalStateException("Invalid response from daemon: " + event);
+        }
+        return cfg;
+    }
+
+    @Override
+    public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        LinkAddress linkAddr = cfg.getLinkAddress();
+        if (linkAddr == null || linkAddr.getAddress() == null) {
+            throw new IllegalStateException("Null LinkAddress given");
+        }
+
+        final Command cmd = new Command("interface", "setcfg", iface,
+                linkAddr.getAddress().getHostAddress(),
+                linkAddr.getNetworkPrefixLength());
+        for (String flag : cfg.getFlags()) {
+            cmd.appendArg(flag);
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setInterfaceDown(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
+        ifcg.setInterfaceDown();
+        setInterfaceConfig(iface, ifcg);
+    }
+
+    @Override
+    public void setInterfaceUp(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        final InterfaceConfiguration ifcg = getInterfaceConfig(iface);
+        ifcg.setInterfaceUp();
+        setInterfaceConfig(iface, ifcg);
+    }
+
+    @Override
+    public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute(
+                    "interface", "ipv6privacyextensions", iface, enable ? "enable" : "disable");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    /* TODO: This is right now a IPv4 only function. Works for wifi which loses its
+       IPv6 addresses on interface down, but we need to do full clean up here */
+    @Override
+    public void clearInterfaceAddresses(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "clearaddrs", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void enableIpv6(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "ipv6", iface, "enable");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void disableIpv6(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "ipv6", iface, "disable");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void addRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        modifyRoute(interfaceName, ADD, route, DEFAULT);
+    }
+
+    @Override
+    public void removeRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        modifyRoute(interfaceName, REMOVE, route, DEFAULT);
+    }
+
+    @Override
+    public void addSecondaryRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        modifyRoute(interfaceName, ADD, route, SECONDARY);
+    }
+
+    @Override
+    public void removeSecondaryRoute(String interfaceName, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        modifyRoute(interfaceName, REMOVE, route, SECONDARY);
+    }
+
+    private void modifyRoute(String interfaceName, String action, RouteInfo route, String type) {
+        final Command cmd = new Command("interface", "route", action, interfaceName, type);
+
+        // create triplet: dest-ip-addr prefixlength gateway-ip-addr
+        final LinkAddress la = route.getDestination();
+        cmd.appendArg(la.getAddress().getHostAddress());
+        cmd.appendArg(la.getNetworkPrefixLength());
+
+        if (route.getGateway() == null) {
+            if (la.getAddress() instanceof Inet4Address) {
+                cmd.appendArg("0.0.0.0");
+            } else {
+                cmd.appendArg("::0");
+            }
+        } else {
+            cmd.appendArg(route.getGateway().getHostAddress());
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    private ArrayList<String> readRouteList(String filename) {
+        FileInputStream fstream = null;
+        ArrayList<String> list = new ArrayList<String>();
+
+        try {
+            fstream = new FileInputStream(filename);
+            DataInputStream in = new DataInputStream(fstream);
+            BufferedReader br = new BufferedReader(new InputStreamReader(in));
+            String s;
+
+            // throw away the title line
+
+            while (((s = br.readLine()) != null) && (s.length() != 0)) {
+                list.add(s);
+            }
+        } catch (IOException ex) {
+            // return current list, possibly empty
+        } finally {
+            if (fstream != null) {
+                try {
+                    fstream.close();
+                } catch (IOException ex) {}
+            }
+        }
+
+        return list;
+    }
+
+    @Override
+    public RouteInfo[] getRoutes(String interfaceName) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        ArrayList<RouteInfo> routes = new ArrayList<RouteInfo>();
+
+        // v4 routes listed as:
+        // iface dest-addr gateway-addr flags refcnt use metric netmask mtu window IRTT
+        for (String s : readRouteList("/proc/net/route")) {
+            String[] fields = s.split("\t");
+
+            if (fields.length > 7) {
+                String iface = fields[0];
+
+                if (interfaceName.equals(iface)) {
+                    String dest = fields[1];
+                    String gate = fields[2];
+                    String flags = fields[3]; // future use?
+                    String mask = fields[7];
+                    try {
+                        // address stored as a hex string, ex: 0014A8C0
+                        InetAddress destAddr =
+                                NetworkUtils.intToInetAddress((int)Long.parseLong(dest, 16));
+                        int prefixLength =
+                                NetworkUtils.netmaskIntToPrefixLength(
+                                (int)Long.parseLong(mask, 16));
+                        LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
+
+                        // address stored as a hex string, ex 0014A8C0
+                        InetAddress gatewayAddr =
+                                NetworkUtils.intToInetAddress((int)Long.parseLong(gate, 16));
+
+                        RouteInfo route = new RouteInfo(linkAddress, gatewayAddr);
+                        routes.add(route);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error parsing route " + s + " : " + e);
+                        continue;
+                    }
+                }
+            }
+        }
+
+        // v6 routes listed as:
+        // dest-addr prefixlength ?? ?? gateway-addr ?? ?? ?? ?? iface
+        for (String s : readRouteList("/proc/net/ipv6_route")) {
+            String[]fields = s.split("\\s+");
+            if (fields.length > 9) {
+                String iface = fields[9].trim();
+                if (interfaceName.equals(iface)) {
+                    String dest = fields[0];
+                    String prefix = fields[1];
+                    String gate = fields[4];
+
+                    try {
+                        // prefix length stored as a hex string, ex 40
+                        int prefixLength = Integer.parseInt(prefix, 16);
+
+                        // address stored as a 32 char hex string
+                        // ex fe800000000000000000000000000000
+                        InetAddress destAddr = NetworkUtils.hexToInet6Address(dest);
+                        LinkAddress linkAddress = new LinkAddress(destAddr, prefixLength);
+
+                        InetAddress gateAddr = NetworkUtils.hexToInet6Address(gate);
+
+                        RouteInfo route = new RouteInfo(linkAddress, gateAddr);
+                        routes.add(route);
+                    } catch (Exception e) {
+                        Log.e(TAG, "Error parsing route " + s + " : " + e);
+                        continue;
+                    }
+                }
+            }
+        }
+        return routes.toArray(new RouteInfo[routes.size()]);
+    }
+
+    @Override
+    public void setMtu(String iface, int mtu) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("interface", "setmtu", iface, mtu);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void shutdown() {
+        // TODO: remove from aidl if nobody calls externally
+        mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG);
+
+        Slog.d(TAG, "Shutting down");
+    }
+
+    @Override
+    public boolean getIpForwardingEnabled() throws IllegalStateException{
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("ipfwd", "status");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        // 211 Forwarding enabled
+        event.checkCode(IpFwdStatusResult);
+        return event.getMessage().endsWith("enabled");
+    }
+
+    @Override
+    public void setIpForwardingEnabled(boolean enable) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("ipfwd", enable ? "enable" : "disable");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void startTethering(String[] dhcpRange) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        // cmd is "tether start first_start first_stop second_start second_stop ..."
+        // an odd number of addrs will fail
+
+        final Command cmd = new Command("tether", "start");
+        for (String d : dhcpRange) {
+            cmd.appendArg(d);
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void stopTethering() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("tether", "stop");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public boolean isTetheringStarted() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("tether", "status");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        // 210 Tethering services started
+        event.checkCode(TetherStatusResult);
+        return event.getMessage().endsWith("started");
+    }
+
+    @Override
+    public void tetherInterface(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("tether", "interface", "add", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void untetherInterface(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("tether", "interface", "remove", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public String[] listTetheredInterfaces() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("tether", "interface", "list"),
+                    TetherInterfaceListResult);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setDnsForwarders(String[] dns) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final Command cmd = new Command("tether", "dns", "set");
+        for (String s : dns) {
+            cmd.appendArg(NetworkUtils.numericToInetAddress(s).getHostAddress());
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public String[] getDnsForwarders() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("tether", "dns", "list"), TetherDnsFwdTgtListResult);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    private void modifyNat(String action, String internalInterface, String externalInterface)
+            throws SocketException {
+        final Command cmd = new Command("nat", action, internalInterface, externalInterface);
+
+        final NetworkInterface internalNetworkInterface = NetworkInterface.getByName(
+                internalInterface);
+        if (internalNetworkInterface == null) {
+            cmd.appendArg("0");
+        } else {
+            Collection<InterfaceAddress> interfaceAddresses = internalNetworkInterface
+                    .getInterfaceAddresses();
+            cmd.appendArg(interfaceAddresses.size());
+            for (InterfaceAddress ia : interfaceAddresses) {
+                InetAddress addr = NetworkUtils.getNetworkPart(
+                        ia.getAddress(), ia.getNetworkPrefixLength());
+                cmd.appendArg(addr.getHostAddress() + "/" + ia.getNetworkPrefixLength());
+            }
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void enableNat(String internalInterface, String externalInterface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            modifyNat("enable", internalInterface, externalInterface);
+        } catch (SocketException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public void disableNat(String internalInterface, String externalInterface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            modifyNat("disable", internalInterface, externalInterface);
+        } catch (SocketException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public String[] listTtys() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return NativeDaemonEvent.filterMessageList(
+                    mConnector.executeForList("list_ttys"), TtyListResult);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void attachPppd(
+            String tty, String localAddr, String remoteAddr, String dns1Addr, String dns2Addr) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("pppd", "attach", tty,
+                    NetworkUtils.numericToInetAddress(localAddr).getHostAddress(),
+                    NetworkUtils.numericToInetAddress(remoteAddr).getHostAddress(),
+                    NetworkUtils.numericToInetAddress(dns1Addr).getHostAddress(),
+                    NetworkUtils.numericToInetAddress(dns2Addr).getHostAddress());
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void detachPppd(String tty) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("pppd", "detach", tty);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void startAccessPoint(
+            WifiConfiguration wifiConfig, String wlanIface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            wifiFirmwareReload(wlanIface, "AP");
+            if (wifiConfig == null) {
+                mConnector.execute("softap", "set", wlanIface);
+            } else {
+                mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
+                                   "broadcast", "6", getSecurityType(wifiConfig),
+                                   new SensitiveArg(wifiConfig.preSharedKey));
+            }
+            mConnector.execute("softap", "startap");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    private static String getSecurityType(WifiConfiguration wifiConfig) {
+        switch (wifiConfig.getAuthType()) {
+            case KeyMgmt.WPA_PSK:
+                return "wpa-psk";
+            case KeyMgmt.WPA2_PSK:
+                return "wpa2-psk";
+            default:
+                return "open";
+        }
+    }
+
+    /* @param mode can be "AP", "STA" or "P2P" */
+    @Override
+    public void wifiFirmwareReload(String wlanIface, String mode) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("softap", "fwreload", wlanIface, mode);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void stopAccessPoint(String wlanIface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("softap", "stopap");
+            wifiFirmwareReload(wlanIface, "STA");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            if (wifiConfig == null) {
+                mConnector.execute("softap", "set", wlanIface);
+            } else {
+                mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID,
+                                   "broadcast", "6", getSecurityType(wifiConfig),
+                                   new SensitiveArg(wifiConfig.preSharedKey));
+            }
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void addIdleTimer(String iface, int timeout, String label) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        if (DBG) Slog.d(TAG, "Adding idletimer");
+
+        synchronized (mIdleTimerLock) {
+            IdleTimerParams params = mActiveIdleTimers.get(iface);
+            if (params != null) {
+                // the interface already has idletimer, update network count
+                params.networkCount++;
+                return;
+            }
+
+            try {
+                mConnector.execute("idletimer", "add", iface, Integer.toString(timeout), label);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+            mActiveIdleTimers.put(iface, new IdleTimerParams(timeout, label));
+        }
+    }
+
+    @Override
+    public void removeIdleTimer(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        if (DBG) Slog.d(TAG, "Removing idletimer");
+
+        synchronized (mIdleTimerLock) {
+            IdleTimerParams params = mActiveIdleTimers.get(iface);
+            if (params == null || --(params.networkCount) > 0) {
+                return;
+            }
+
+            try {
+                mConnector.execute("idletimer", "remove", iface,
+                        Integer.toString(params.timeout), params.label);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+            mActiveIdleTimers.remove(iface);
+        }
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsSummaryDev() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return mStatsFactory.readNetworkStatsSummaryDev();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsSummaryXt() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return mStatsFactory.readNetworkStatsSummaryXt();
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsDetail() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return mStatsFactory.readNetworkStatsDetail(UID_ALL);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public void setInterfaceQuota(String iface, long quotaBytes) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        synchronized (mQuotaLock) {
+            if (mActiveQuotas.containsKey(iface)) {
+                throw new IllegalStateException("iface " + iface + " already has quota");
+            }
+
+            try {
+                // TODO: support quota shared across interfaces
+                mConnector.execute("bandwidth", "setiquota", iface, quotaBytes);
+                mActiveQuotas.put(iface, quotaBytes);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+        }
+    }
+
+    @Override
+    public void removeInterfaceQuota(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        synchronized (mQuotaLock) {
+            if (!mActiveQuotas.containsKey(iface)) {
+                // TODO: eventually consider throwing
+                return;
+            }
+
+            mActiveQuotas.remove(iface);
+            mActiveAlerts.remove(iface);
+
+            try {
+                // TODO: support quota shared across interfaces
+                mConnector.execute("bandwidth", "removeiquota", iface);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+        }
+    }
+
+    @Override
+    public void setInterfaceAlert(String iface, long alertBytes) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        // quick sanity check
+        if (!mActiveQuotas.containsKey(iface)) {
+            throw new IllegalStateException("setting alert requires existing quota on iface");
+        }
+
+        synchronized (mQuotaLock) {
+            if (mActiveAlerts.containsKey(iface)) {
+                throw new IllegalStateException("iface " + iface + " already has alert");
+            }
+
+            try {
+                // TODO: support alert shared across interfaces
+                mConnector.execute("bandwidth", "setinterfacealert", iface, alertBytes);
+                mActiveAlerts.put(iface, alertBytes);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+        }
+    }
+
+    @Override
+    public void removeInterfaceAlert(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        synchronized (mQuotaLock) {
+            if (!mActiveAlerts.containsKey(iface)) {
+                // TODO: eventually consider throwing
+                return;
+            }
+
+            try {
+                // TODO: support alert shared across interfaces
+                mConnector.execute("bandwidth", "removeinterfacealert", iface);
+                mActiveAlerts.remove(iface);
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+        }
+    }
+
+    @Override
+    public void setGlobalAlert(long alertBytes) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        try {
+            mConnector.execute("bandwidth", "setglobalalert", alertBytes);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        // silently discard when control disabled
+        // TODO: eventually migrate to be always enabled
+        if (!mBandwidthControlEnabled) return;
+
+        synchronized (mQuotaLock) {
+            final boolean oldRejectOnQuota = mUidRejectOnQuota.get(uid, false);
+            if (oldRejectOnQuota == rejectOnQuotaInterfaces) {
+                // TODO: eventually consider throwing
+                return;
+            }
+
+            try {
+                mConnector.execute("bandwidth",
+                        rejectOnQuotaInterfaces ? "addnaughtyapps" : "removenaughtyapps", uid);
+                if (rejectOnQuotaInterfaces) {
+                    mUidRejectOnQuota.put(uid, true);
+                } else {
+                    mUidRejectOnQuota.delete(uid);
+                }
+            } catch (NativeDaemonConnectorException e) {
+                throw e.rethrowAsParcelableException();
+            }
+        }
+    }
+
+    @Override
+    public boolean isBandwidthControlEnabled() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        return mBandwidthControlEnabled;
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsUidDetail(int uid) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            return mStatsFactory.readNetworkStatsDetail(uid);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public NetworkStats getNetworkStatsTethering() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+        try {
+            final NativeDaemonEvent[] events = mConnector.executeForList(
+                    "bandwidth", "gettetherstats");
+            for (NativeDaemonEvent event : events) {
+                if (event.getCode() != TetheringStatsListResult) continue;
+
+                // 114 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets
+                final StringTokenizer tok = new StringTokenizer(event.getMessage());
+                try {
+                    final String ifaceIn = tok.nextToken();
+                    final String ifaceOut = tok.nextToken();
+
+                    final NetworkStats.Entry entry = new NetworkStats.Entry();
+                    entry.iface = ifaceOut;
+                    entry.uid = UID_TETHERING;
+                    entry.set = SET_DEFAULT;
+                    entry.tag = TAG_NONE;
+                    entry.rxBytes = Long.parseLong(tok.nextToken());
+                    entry.rxPackets = Long.parseLong(tok.nextToken());
+                    entry.txBytes = Long.parseLong(tok.nextToken());
+                    entry.txPackets = Long.parseLong(tok.nextToken());
+                    stats.combineValues(entry);
+                } catch (NoSuchElementException e) {
+                    throw new IllegalStateException("problem parsing tethering stats: " + event);
+                } catch (NumberFormatException e) {
+                    throw new IllegalStateException("problem parsing tethering stats: " + event);
+                }
+            }
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+        return stats;
+    }
+
+    @Override
+    public void setDefaultInterfaceForDns(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "setdefaultif", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setDnsServersForInterface(String iface, String[] servers, String domains) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final Command cmd = new Command("resolver", "setifdns", iface,
+                (domains == null ? "" : domains));
+
+        for (String s : servers) {
+            InetAddress a = NetworkUtils.numericToInetAddress(s);
+            if (a.isAnyLocalAddress() == false) {
+                cmd.appendArg(a.getHostAddress());
+            }
+        }
+
+        try {
+            mConnector.execute(cmd);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setUidRangeRoute(String iface, int uid_start, int uid_end) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark",
+                    "uid", "add", iface, uid_start, uid_end);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearUidRangeRoute(String iface, int uid_start, int uid_end) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark",
+                    "uid", "remove", iface, uid_start, uid_end);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setMarkedForwarding(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark", "rule", "add", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearMarkedForwarding(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark", "rule", "remove", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public int getMarkForUid(int uid) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("interface", "fwmark", "get", "mark", uid);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+        event.checkCode(GetMarkResult);
+        return Integer.parseInt(event.getMessage());
+    }
+
+    @Override
+    public int getMarkForProtect() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("interface", "fwmark", "get", "protect");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+        event.checkCode(GetMarkResult);
+        return Integer.parseInt(event.getMessage());
+    }
+
+    @Override
+    public void setMarkedForwardingRoute(String iface, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            LinkAddress dest = route.getDestination();
+            mConnector.execute("interface", "fwmark", "route", "add", iface,
+                    dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength());
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearMarkedForwardingRoute(String iface, RouteInfo route) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            LinkAddress dest = route.getDestination();
+            mConnector.execute("interface", "fwmark", "route", "remove", iface,
+                    dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength());
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setHostExemption(LinkAddress host) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark", "exempt", "add", host);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearHostExemption(LinkAddress host) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("interface", "fwmark", "exempt", "remove", host);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "setifaceforuidrange", iface, uid_start, uid_end);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearDnsInterfaceForUidRange(int uid_start, int uid_end) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "clearifaceforuidrange", uid_start, uid_end);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void clearDnsInterfaceMaps() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "clearifacemapping");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+
+    @Override
+    public void flushDefaultDnsCache() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "flushdefaultif");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void flushInterfaceDnsCache(String iface) {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "flushif", iface);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setFirewallEnabled(boolean enabled) {
+        enforceSystemUid();
+        try {
+            mConnector.execute("firewall", enabled ? "enable" : "disable");
+            mFirewallEnabled = enabled;
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public boolean isFirewallEnabled() {
+        enforceSystemUid();
+        return mFirewallEnabled;
+    }
+
+    @Override
+    public void setFirewallInterfaceRule(String iface, boolean allow) {
+        enforceSystemUid();
+        Preconditions.checkState(mFirewallEnabled);
+        final String rule = allow ? ALLOW : DENY;
+        try {
+            mConnector.execute("firewall", "set_interface_rule", iface, rule);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setFirewallEgressSourceRule(String addr, boolean allow) {
+        enforceSystemUid();
+        Preconditions.checkState(mFirewallEnabled);
+        final String rule = allow ? ALLOW : DENY;
+        try {
+            mConnector.execute("firewall", "set_egress_source_rule", addr, rule);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setFirewallEgressDestRule(String addr, int port, boolean allow) {
+        enforceSystemUid();
+        Preconditions.checkState(mFirewallEnabled);
+        final String rule = allow ? ALLOW : DENY;
+        try {
+            mConnector.execute("firewall", "set_egress_dest_rule", addr, port, rule);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void setFirewallUidRule(int uid, boolean allow) {
+        enforceSystemUid();
+        Preconditions.checkState(mFirewallEnabled);
+        final String rule = allow ? ALLOW : DENY;
+        try {
+            mConnector.execute("firewall", "set_uid_rule", uid, rule);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    private static void enforceSystemUid() {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID) {
+            throw new SecurityException("Only available to AID_SYSTEM");
+        }
+    }
+
+    @Override
+    public void setDnsInterfaceForPid(String iface, int pid) throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "setifaceforpid", iface, pid);
+        } catch (NativeDaemonConnectorException e) {
+            throw new IllegalStateException(
+                    "Error communicating with native deamon to set interface for pid" + iface, e);
+        }
+    }
+
+    @Override
+    public void clearDnsInterfaceForPid(int pid) throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+        try {
+            mConnector.execute("resolver", "clearifaceforpid", pid);
+        } catch (NativeDaemonConnectorException e) {
+            throw new IllegalStateException(
+                    "Error communicating with native deamon to clear interface for pid " + pid, e);
+        }
+    }
+
+    @Override
+    public void startClatd(String interfaceName) throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("clatd", "start", interfaceName);
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public void stopClatd() throws IllegalStateException {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        try {
+            mConnector.execute("clatd", "stop");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+    }
+
+    @Override
+    public boolean isClatdStarted() {
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        final NativeDaemonEvent event;
+        try {
+            event = mConnector.execute("clatd", "status");
+        } catch (NativeDaemonConnectorException e) {
+            throw e.rethrowAsParcelableException();
+        }
+
+        event.checkCode(ClatdStatusResult);
+        return event.getMessage().endsWith("started");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void monitor() {
+        if (mConnector != null) {
+            mConnector.monitor();
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        pw.println("NetworkManagementService NativeDaemonConnector Log:");
+        mConnector.dump(fd, pw, args);
+        pw.println();
+
+        pw.print("Bandwidth control enabled: "); pw.println(mBandwidthControlEnabled);
+
+        synchronized (mQuotaLock) {
+            pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString());
+            pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString());
+        }
+
+        synchronized (mUidRejectOnQuota) {
+            pw.print("UID reject on quota ifaces: [");
+            final int size = mUidRejectOnQuota.size();
+            for (int i = 0; i < size; i++) {
+                pw.print(mUidRejectOnQuota.keyAt(i));
+                if (i < size - 1) pw.print(",");
+            }
+            pw.println("]");
+        }
+
+        pw.print("Firewall enabled: "); pw.println(mFirewallEnabled);
+    }
+}
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateService.java b/services/core/java/com/android/server/NetworkTimeUpdateService.java
new file mode 100644
index 0000000..fddb54e
--- /dev/null
+++ b/services/core/java/com/android/server/NetworkTimeUpdateService.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.NtpTrustedTime;
+import android.util.TrustedTime;
+
+import com.android.internal.telephony.TelephonyIntents;
+
+/**
+ * Monitors the network time and updates the system time if it is out of sync
+ * and there hasn't been any NITZ update from the carrier recently.
+ * If looking up the network time fails for some reason, it tries a few times with a short
+ * interval and then resets to checking on longer intervals.
+ * <p>
+ * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
+ * available.
+ * </p>
+ */
+public class NetworkTimeUpdateService {
+
+    private static final String TAG = "NetworkTimeUpdateService";
+    private static final boolean DBG = false;
+
+    private static final int EVENT_AUTO_TIME_CHANGED = 1;
+    private static final int EVENT_POLL_NETWORK_TIME = 2;
+    private static final int EVENT_NETWORK_CONNECTED = 3;
+
+    private static final String ACTION_POLL =
+            "com.android.server.NetworkTimeUpdateService.action.POLL";
+    private static int POLL_REQUEST = 0;
+
+    private static final long NOT_SET = -1;
+    private long mNitzTimeSetTime = NOT_SET;
+    // TODO: Have a way to look up the timezone we are in
+    private long mNitzZoneSetTime = NOT_SET;
+
+    private Context mContext;
+    private TrustedTime mTime;
+
+    // NTP lookup is done on this thread and handler
+    private Handler mHandler;
+    private AlarmManager mAlarmManager;
+    private PendingIntent mPendingPollIntent;
+    private SettingsObserver mSettingsObserver;
+    // The last time that we successfully fetched the NTP time.
+    private long mLastNtpFetchTime = NOT_SET;
+
+    // Normal polling frequency
+    private final long mPollingIntervalMs;
+    // Try-again polling interval, in case the network request failed
+    private final long mPollingIntervalShorterMs;
+    // Number of times to try again
+    private final int mTryAgainTimesMax;
+    // If the time difference is greater than this threshold, then update the time.
+    private final int mTimeErrorThresholdMs;
+    // Keeps track of how many quick attempts were made to fetch NTP time.
+    // During bootup, the network may not have been up yet, or it's taking time for the
+    // connection to happen.
+    private int mTryAgainCounter;
+
+    public NetworkTimeUpdateService(Context context) {
+        mContext = context;
+        mTime = NtpTrustedTime.getInstance(context);
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent pollIntent = new Intent(ACTION_POLL, null);
+        mPendingPollIntent = PendingIntent.getBroadcast(mContext, POLL_REQUEST, pollIntent, 0);
+
+        mPollingIntervalMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpPollingInterval);
+        mPollingIntervalShorterMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpPollingIntervalShorter);
+        mTryAgainTimesMax = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpRetry);
+        mTimeErrorThresholdMs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_ntpThreshold);
+    }
+
+    /** Initialize the receivers and initiate the first NTP request */
+    public void systemRunning() {
+        registerForTelephonyIntents();
+        registerForAlarms();
+        registerForConnectivityIntents();
+
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mHandler = new MyHandler(thread.getLooper());
+        // Check the network time on the new thread
+        mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+
+        mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
+        mSettingsObserver.observe(mContext);
+    }
+
+    private void registerForTelephonyIntents() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
+        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
+        mContext.registerReceiver(mNitzReceiver, intentFilter);
+    }
+
+    private void registerForAlarms() {
+        mContext.registerReceiver(
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget();
+                }
+            }, new IntentFilter(ACTION_POLL));
+    }
+
+    private void registerForConnectivityIntents() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mConnectivityReceiver, intentFilter);
+    }
+
+    private void onPollNetworkTime(int event) {
+        // If Automatic time is not set, don't bother.
+        if (!isAutomaticTimeRequested()) return;
+
+        final long refTime = SystemClock.elapsedRealtime();
+        // If NITZ time was received less than mPollingIntervalMs time ago,
+        // no need to sync to NTP.
+        if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
+            resetAlarm(mPollingIntervalMs);
+            return;
+        }
+        final long currentTime = System.currentTimeMillis();
+        if (DBG) Log.d(TAG, "System time = " + currentTime);
+        // Get the NTP time
+        if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
+                || event == EVENT_AUTO_TIME_CHANGED) {
+            if (DBG) Log.d(TAG, "Before Ntp fetch");
+
+            // force refresh NTP cache when outdated
+            if (mTime.getCacheAge() >= mPollingIntervalMs) {
+                mTime.forceRefresh();
+            }
+
+            // only update when NTP time is fresh
+            if (mTime.getCacheAge() < mPollingIntervalMs) {
+                final long ntp = mTime.currentTimeMillis();
+                mTryAgainCounter = 0;
+                // If the clock is more than N seconds off or this is the first time it's been
+                // fetched since boot, set the current time.
+                if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
+                        || mLastNtpFetchTime == NOT_SET) {
+                    // Set the system time
+                    if (DBG && mLastNtpFetchTime == NOT_SET
+                            && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
+                        Log.d(TAG, "For initial setup, rtc = " + currentTime);
+                    }
+                    if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
+                    // Make sure we don't overflow, since it's going to be converted to an int
+                    if (ntp / 1000 < Integer.MAX_VALUE) {
+                        SystemClock.setCurrentTimeMillis(ntp);
+                    }
+                } else {
+                    if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
+                }
+                mLastNtpFetchTime = SystemClock.elapsedRealtime();
+            } else {
+                // Try again shortly
+                mTryAgainCounter++;
+                if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
+                    resetAlarm(mPollingIntervalShorterMs);
+                } else {
+                    // Try much later
+                    mTryAgainCounter = 0;
+                    resetAlarm(mPollingIntervalMs);
+                }
+                return;
+            }
+        }
+        resetAlarm(mPollingIntervalMs);
+    }
+
+    /**
+     * Cancel old alarm and starts a new one for the specified interval.
+     *
+     * @param interval when to trigger the alarm, starting from now.
+     */
+    private void resetAlarm(long interval) {
+        mAlarmManager.cancel(mPendingPollIntent);
+        long now = SystemClock.elapsedRealtime();
+        long next = now + interval;
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
+    }
+
+    /**
+     * Checks if the user prefers to automatically set the time.
+     */
+    private boolean isAutomaticTimeRequested() {
+        return Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0;
+    }
+
+    /** Receiver for Nitz time events */
+    private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
+                mNitzTimeSetTime = SystemClock.elapsedRealtime();
+            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
+                mNitzZoneSetTime = SystemClock.elapsedRealtime();
+            }
+        }
+    };
+
+    /** Receiver for ConnectivityManager events */
+    private BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
+                // There is connectivity
+                final ConnectivityManager connManager = (ConnectivityManager) context
+                        .getSystemService(Context.CONNECTIVITY_SERVICE);
+                final NetworkInfo netInfo = connManager.getActiveNetworkInfo();
+                if (netInfo != null) {
+                    // Verify that it's a WIFI connection
+                    if (netInfo.getState() == NetworkInfo.State.CONNECTED &&
+                            (netInfo.getType() == ConnectivityManager.TYPE_WIFI ||
+                                netInfo.getType() == ConnectivityManager.TYPE_ETHERNET) ) {
+                        mHandler.obtainMessage(EVENT_NETWORK_CONNECTED).sendToTarget();
+                    }
+                }
+            }
+        }
+    };
+
+    /** Handler to do the network accesses on */
+    private class MyHandler extends Handler {
+
+        public MyHandler(Looper l) {
+            super(l);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_AUTO_TIME_CHANGED:
+                case EVENT_POLL_NETWORK_TIME:
+                case EVENT_NETWORK_CONNECTED:
+                    onPollNetworkTime(msg.what);
+                    break;
+            }
+        }
+    }
+
+    /** Observer to watch for changes to the AUTO_TIME setting */
+    private static class SettingsObserver extends ContentObserver {
+
+        private int mMsg;
+        private Handler mHandler;
+
+        SettingsObserver(Handler handler, int msg) {
+            super(handler);
+            mHandler = handler;
+            mMsg = msg;
+        }
+
+        void observe(Context context) {
+            ContentResolver resolver = context.getContentResolver();
+            resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
+                    false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            mHandler.obtainMessage(mMsg).sendToTarget();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
new file mode 100644
index 0000000..16d2468
--- /dev/null
+++ b/services/core/java/com/android/server/NsdService.java
@@ -0,0 +1,849 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.nsd.NsdServiceInfo;
+import android.net.nsd.DnsSdTxtRecord;
+import android.net.nsd.INsdManager;
+import android.net.nsd.NsdManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.NativeDaemonConnector.Command;
+import com.android.internal.R;
+
+/**
+ * Network Service Discovery Service handles remote service discovery operation requests by
+ * implementing the INsdManager interface.
+ *
+ * @hide
+ */
+public class NsdService extends INsdManager.Stub {
+    private static final String TAG = "NsdService";
+    private static final String MDNS_TAG = "mDnsConnector";
+
+    private static final boolean DBG = true;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private NsdStateMachine mNsdStateMachine;
+
+    /**
+     * Clients receiving asynchronous messages
+     */
+    private HashMap<Messenger, ClientInfo> mClients = new HashMap<Messenger, ClientInfo>();
+
+    /* A map from unique id to client info */
+    private SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<ClientInfo>();
+
+    private AsyncChannel mReplyChannel = new AsyncChannel();
+
+    private int INVALID_ID = 0;
+    private int mUniqueId = 1;
+
+    private static final int BASE = Protocol.BASE_NSD_MANAGER;
+    private static final int CMD_TO_STRING_COUNT = NsdManager.RESOLVE_SERVICE - BASE + 1;
+    private static String[] sCmdToString = new String[CMD_TO_STRING_COUNT];
+
+    static {
+        sCmdToString[NsdManager.DISCOVER_SERVICES - BASE] = "DISCOVER";
+        sCmdToString[NsdManager.STOP_DISCOVERY - BASE] = "STOP-DISCOVER";
+        sCmdToString[NsdManager.REGISTER_SERVICE - BASE] = "REGISTER";
+        sCmdToString[NsdManager.UNREGISTER_SERVICE - BASE] = "UNREGISTER";
+        sCmdToString[NsdManager.RESOLVE_SERVICE - BASE] = "RESOLVE";
+    }
+
+    private static String cmdToString(int cmd) {
+        cmd -= BASE;
+        if ((cmd >= 0) && (cmd < sCmdToString.length)) {
+            return sCmdToString[cmd];
+        } else {
+            return null;
+        }
+    }
+
+    private class NsdStateMachine extends StateMachine {
+
+        private final DefaultState mDefaultState = new DefaultState();
+        private final DisabledState mDisabledState = new DisabledState();
+        private final EnabledState mEnabledState = new EnabledState();
+
+        @Override
+        protected String getWhatToString(int what) {
+            return cmdToString(what);
+        }
+
+        /**
+         * Observes the NSD on/off setting, and takes action when changed.
+         */
+        private void registerForNsdSetting() {
+            ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
+                @Override
+                    public void onChange(boolean selfChange) {
+                        if (isNsdEnabled()) {
+                            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
+                        } else {
+                            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
+                        }
+                    }
+            };
+
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.NSD_ON),
+                    false, contentObserver);
+        }
+
+        NsdStateMachine(String name) {
+            super(name);
+            addState(mDefaultState);
+                addState(mDisabledState, mDefaultState);
+                addState(mEnabledState, mDefaultState);
+            if (isNsdEnabled()) {
+                setInitialState(mEnabledState);
+            } else {
+                setInitialState(mDisabledState);
+            }
+            setLogRecSize(25);
+            registerForNsdSetting();
+        }
+
+        class DefaultState extends State {
+            @Override
+            public boolean processMessage(Message msg) {
+                switch (msg.what) {
+                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                            AsyncChannel c = (AsyncChannel) msg.obj;
+                            if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
+                            c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
+                            ClientInfo cInfo = new ClientInfo(c, msg.replyTo);
+                            mClients.put(msg.replyTo, cInfo);
+                        } else {
+                            Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
+                        }
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+                            Slog.e(TAG, "Send failed, client connection lost");
+                        } else {
+                            if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+                        }
+                        mClients.remove(msg.replyTo);
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                        AsyncChannel ac = new AsyncChannel();
+                        ac.connect(mContext, getHandler(), msg.replyTo);
+                        break;
+                    case NsdManager.DISCOVER_SERVICES:
+                        replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR);
+                       break;
+                    case NsdManager.STOP_DISCOVERY:
+                       replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+                               NsdManager.FAILURE_INTERNAL_ERROR);
+                        break;
+                    case NsdManager.REGISTER_SERVICE:
+                        replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        break;
+                    case NsdManager.UNREGISTER_SERVICE:
+                        replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        break;
+                    case NsdManager.RESOLVE_SERVICE:
+                        replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR);
+                        break;
+                    case NsdManager.NATIVE_DAEMON_EVENT:
+                    default:
+                        Slog.e(TAG, "Unhandled " + msg);
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class DisabledState extends State {
+            @Override
+            public void enter() {
+                sendNsdStateChangeBroadcast(false);
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                switch (msg.what) {
+                    case NsdManager.ENABLE:
+                        transitionTo(mEnabledState);
+                        break;
+                    default:
+                        return NOT_HANDLED;
+                }
+                return HANDLED;
+            }
+        }
+
+        class EnabledState extends State {
+            @Override
+            public void enter() {
+                sendNsdStateChangeBroadcast(true);
+                if (mClients.size() > 0) {
+                    startMDnsDaemon();
+                }
+            }
+
+            @Override
+            public void exit() {
+                if (mClients.size() > 0) {
+                    stopMDnsDaemon();
+                }
+            }
+
+            private boolean requestLimitReached(ClientInfo clientInfo) {
+                if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
+                    if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
+                    return true;
+                }
+                return false;
+            }
+
+            private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
+                clientInfo.mClientIds.put(clientId, globalId);
+                mIdToClientInfoMap.put(globalId, clientInfo);
+            }
+
+            private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
+                clientInfo.mClientIds.remove(clientId);
+                mIdToClientInfoMap.remove(globalId);
+            }
+
+            @Override
+            public boolean processMessage(Message msg) {
+                ClientInfo clientInfo;
+                NsdServiceInfo servInfo;
+                boolean result = HANDLED;
+                int id;
+                switch (msg.what) {
+                  case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                        //First client
+                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL &&
+                                mClients.size() == 0) {
+                            startMDnsDaemon();
+                        }
+                        result = NOT_HANDLED;
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        //Last client
+                        if (mClients.size() == 1) {
+                            stopMDnsDaemon();
+                        }
+                        result = NOT_HANDLED;
+                        break;
+                    case NsdManager.DISABLE:
+                        //TODO: cleanup clients
+                        transitionTo(mDisabledState);
+                        break;
+                    case NsdManager.DISCOVER_SERVICES:
+                        if (DBG) Slog.d(TAG, "Discover services");
+                        servInfo = (NsdServiceInfo) msg.obj;
+                        clientInfo = mClients.get(msg.replyTo);
+
+                        if (requestLimitReached(clientInfo)) {
+                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                                    NsdManager.FAILURE_MAX_LIMIT);
+                            break;
+                        }
+
+                        id = getUniqueId();
+                        if (discoverServices(id, servInfo.getServiceType())) {
+                            if (DBG) {
+                                Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
+                                        servInfo.getServiceType());
+                            }
+                            storeRequestMap(msg.arg2, id, clientInfo);
+                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
+                        } else {
+                            stopServiceDiscovery(id);
+                            replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+                        break;
+                    case NsdManager.STOP_DISCOVERY:
+                        if (DBG) Slog.d(TAG, "Stop service discovery");
+                        clientInfo = mClients.get(msg.replyTo);
+
+                        try {
+                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
+                        } catch (NullPointerException e) {
+                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            break;
+                        }
+                        removeRequestMap(msg.arg2, id, clientInfo);
+                        if (stopServiceDiscovery(id)) {
+                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
+                        } else {
+                            replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+                        break;
+                    case NsdManager.REGISTER_SERVICE:
+                        if (DBG) Slog.d(TAG, "Register service");
+                        clientInfo = mClients.get(msg.replyTo);
+                        if (requestLimitReached(clientInfo)) {
+                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+                                    NsdManager.FAILURE_MAX_LIMIT);
+                            break;
+                        }
+
+                        id = getUniqueId();
+                        if (registerService(id, (NsdServiceInfo) msg.obj)) {
+                            if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
+                            storeRequestMap(msg.arg2, id, clientInfo);
+                            // Return success after mDns reports success
+                        } else {
+                            unregisterService(id);
+                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+                        break;
+                    case NsdManager.UNREGISTER_SERVICE:
+                        if (DBG) Slog.d(TAG, "unregister service");
+                        clientInfo = mClients.get(msg.replyTo);
+                        try {
+                            id = clientInfo.mClientIds.get(msg.arg2).intValue();
+                        } catch (NullPointerException e) {
+                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                            break;
+                        }
+                        removeRequestMap(msg.arg2, id, clientInfo);
+                        if (unregisterService(id)) {
+                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
+                        } else {
+                            replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+                        break;
+                    case NsdManager.RESOLVE_SERVICE:
+                        if (DBG) Slog.d(TAG, "Resolve service");
+                        servInfo = (NsdServiceInfo) msg.obj;
+                        clientInfo = mClients.get(msg.replyTo);
+
+
+                        if (clientInfo.mResolvedService != null) {
+                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+                                    NsdManager.FAILURE_ALREADY_ACTIVE);
+                            break;
+                        }
+
+                        id = getUniqueId();
+                        if (resolveService(id, servInfo)) {
+                            clientInfo.mResolvedService = new NsdServiceInfo();
+                            storeRequestMap(msg.arg2, id, clientInfo);
+                        } else {
+                            replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR);
+                        }
+                        break;
+                    case NsdManager.NATIVE_DAEMON_EVENT:
+                        NativeEvent event = (NativeEvent) msg.obj;
+                        if (!handleNativeEvent(event.code, event.raw,
+                                NativeDaemonEvent.unescapeArgs(event.raw))) {
+                            result = NOT_HANDLED;
+                        }
+                        break;
+                    default:
+                        result = NOT_HANDLED;
+                        break;
+                }
+                return result;
+            }
+
+            private boolean handleNativeEvent(int code, String raw, String[] cooked) {
+                boolean handled = true;
+                NsdServiceInfo servInfo;
+                int id = Integer.parseInt(cooked[1]);
+                ClientInfo clientInfo = mIdToClientInfoMap.get(id);
+                if (clientInfo == null) {
+                    Slog.e(TAG, "Unique id with no client mapping: " + id);
+                    handled = false;
+                    return handled;
+                }
+
+                /* This goes in response as msg.arg2 */
+                int clientId = -1;
+                int keyId = clientInfo.mClientIds.indexOfValue(id);
+                if (keyId != -1) {
+                    clientId = clientInfo.mClientIds.keyAt(keyId);
+                } else {
+                    // This can happen because of race conditions. For example,
+                    // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
+                    // and we may get in this situation.
+                    Slog.d(TAG, "Notification for a listener that is no longer active: " + id);
+                    handled = false;
+                    return handled;
+                }
+
+                switch (code) {
+                    case NativeResponseCode.SERVICE_FOUND:
+                        /* NNN uniqueId serviceName regType domain */
+                        if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
+                        servInfo = new NsdServiceInfo(cooked[2], cooked[3], null);
+                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
+                                clientId, servInfo);
+                        break;
+                    case NativeResponseCode.SERVICE_LOST:
+                        /* NNN uniqueId serviceName regType domain */
+                        if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
+                        servInfo = new NsdServiceInfo(cooked[2], cooked[3], null);
+                        clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
+                                clientId, servInfo);
+                        break;
+                    case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
+                        /* NNN uniqueId errorCode */
+                        if (DBG) Slog.d(TAG, "SERVICE_DISC_FAILED Raw: " + raw);
+                        clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        break;
+                    case NativeResponseCode.SERVICE_REGISTERED:
+                        /* NNN regId serviceName regType */
+                        if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
+                        servInfo = new NsdServiceInfo(cooked[2], null, null);
+                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
+                                id, clientId, servInfo);
+                        break;
+                    case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
+                        /* NNN regId errorCode */
+                        if (DBG) Slog.d(TAG, "SERVICE_REGISTER_FAILED Raw: " + raw);
+                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
+                               NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        break;
+                    case NativeResponseCode.SERVICE_UPDATED:
+                        /* NNN regId */
+                        break;
+                    case NativeResponseCode.SERVICE_UPDATE_FAILED:
+                        /* NNN regId errorCode */
+                        break;
+                    case NativeResponseCode.SERVICE_RESOLVED:
+                        /* NNN resolveId fullName hostName port txtlen txtdata */
+                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVED Raw: " + raw);
+                        int index = cooked[2].indexOf(".");
+                        if (index == -1) {
+                            Slog.e(TAG, "Invalid service found " + raw);
+                            break;
+                        }
+                        String name = cooked[2].substring(0, index);
+                        String rest = cooked[2].substring(index);
+                        String type = rest.replace(".local.", "");
+
+                        clientInfo.mResolvedService.setServiceName(name);
+                        clientInfo.mResolvedService.setServiceType(type);
+                        clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
+
+                        stopResolveService(id);
+                        removeRequestMap(clientId, id, clientInfo);
+
+                        int id2 = getUniqueId();
+                        if (getAddrInfo(id2, cooked[3])) {
+                            storeRequestMap(clientId, id2, clientInfo);
+                        } else {
+                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                            clientInfo.mResolvedService = null;
+                        }
+                        break;
+                    case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
+                        /* NNN resolveId errorCode */
+                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
+                        stopResolveService(id);
+                        removeRequestMap(clientId, id, clientInfo);
+                        clientInfo.mResolvedService = null;
+                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        break;
+                    case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
+                        /* NNN resolveId errorCode */
+                        stopGetAddrInfo(id);
+                        removeRequestMap(clientId, id, clientInfo);
+                        clientInfo.mResolvedService = null;
+                        if (DBG) Slog.d(TAG, "SERVICE_RESOLVE_FAILED Raw: " + raw);
+                        clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        break;
+                    case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
+                        /* NNN resolveId hostname ttl addr */
+                        if (DBG) Slog.d(TAG, "SERVICE_GET_ADDR_SUCCESS Raw: " + raw);
+                        try {
+                            clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
+                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
+                                   0, clientId, clientInfo.mResolvedService);
+                        } catch (java.net.UnknownHostException e) {
+                            clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
+                                    NsdManager.FAILURE_INTERNAL_ERROR, clientId);
+                        }
+                        stopGetAddrInfo(id);
+                        removeRequestMap(clientId, id, clientInfo);
+                        clientInfo.mResolvedService = null;
+                        break;
+                    default:
+                        handled = false;
+                        break;
+                }
+                return handled;
+            }
+       }
+    }
+
+    private NativeDaemonConnector mNativeConnector;
+    private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1);
+
+    private NsdService(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+
+        mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10,
+                MDNS_TAG, 25);
+
+        mNsdStateMachine = new NsdStateMachine(TAG);
+        mNsdStateMachine.start();
+
+        Thread th = new Thread(mNativeConnector, MDNS_TAG);
+        th.start();
+    }
+
+    public static NsdService create(Context context) throws InterruptedException {
+        NsdService service = new NsdService(context);
+        service.mNativeDaemonConnected.await();
+        return service;
+    }
+
+    public Messenger getMessenger() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET,
+            "NsdService");
+        return new Messenger(mNsdStateMachine.getHandler());
+    }
+
+    public void setEnabled(boolean enable) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                "NsdService");
+        Settings.Global.putInt(mContentResolver, Settings.Global.NSD_ON, enable ? 1 : 0);
+        if (enable) {
+            mNsdStateMachine.sendMessage(NsdManager.ENABLE);
+        } else {
+            mNsdStateMachine.sendMessage(NsdManager.DISABLE);
+        }
+    }
+
+    private void sendNsdStateChangeBroadcast(boolean enabled) {
+        final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        if (enabled) {
+            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_ENABLED);
+        } else {
+            intent.putExtra(NsdManager.EXTRA_NSD_STATE, NsdManager.NSD_STATE_DISABLED);
+        }
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private boolean isNsdEnabled() {
+        boolean ret = Settings.Global.getInt(mContentResolver, Settings.Global.NSD_ON, 1) == 1;
+        if (DBG) Slog.d(TAG, "Network service discovery enabled " + ret);
+        return ret;
+    }
+
+    private int getUniqueId() {
+        if (++mUniqueId == INVALID_ID) return ++mUniqueId;
+        return mUniqueId;
+    }
+
+    /* These should be in sync with system/netd/mDnsResponseCode.h */
+    class NativeResponseCode {
+        public static final int SERVICE_DISCOVERY_FAILED    =   602;
+        public static final int SERVICE_FOUND               =   603;
+        public static final int SERVICE_LOST                =   604;
+
+        public static final int SERVICE_REGISTRATION_FAILED =   605;
+        public static final int SERVICE_REGISTERED          =   606;
+
+        public static final int SERVICE_RESOLUTION_FAILED   =   607;
+        public static final int SERVICE_RESOLVED            =   608;
+
+        public static final int SERVICE_UPDATED             =   609;
+        public static final int SERVICE_UPDATE_FAILED       =   610;
+
+        public static final int SERVICE_GET_ADDR_FAILED     =   611;
+        public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
+    }
+
+    private class NativeEvent {
+        final int code;
+        final String raw;
+
+        NativeEvent(int code, String raw) {
+            this.code = code;
+            this.raw = raw;
+        }
+    }
+
+    class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
+        public void onDaemonConnected() {
+            mNativeDaemonConnected.countDown();
+        }
+
+        public boolean onEvent(int code, String raw, String[] cooked) {
+            // TODO: NDC translates a message to a callback, we could enhance NDC to
+            // directly interact with a state machine through messages
+            NativeEvent event = new NativeEvent(code, raw);
+            mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
+            return true;
+        }
+    }
+
+    private boolean startMDnsDaemon() {
+        if (DBG) Slog.d(TAG, "startMDnsDaemon");
+        try {
+            mNativeConnector.execute("mdnssd", "start-service");
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to start daemon" + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopMDnsDaemon() {
+        if (DBG) Slog.d(TAG, "stopMDnsDaemon");
+        try {
+            mNativeConnector.execute("mdnssd", "stop-service");
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to start daemon" + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean registerService(int regId, NsdServiceInfo service) {
+        if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
+        try {
+            //Add txtlen and txtdata
+            mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(),
+                    service.getServiceType(), service.getPort());
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to execute registerService " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean unregisterService(int regId) {
+        if (DBG) Slog.d(TAG, "unregisterService: " + regId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-register", regId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to execute unregisterService " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean updateService(int regId, DnsSdTxtRecord t) {
+        if (DBG) Slog.d(TAG, "updateService: " + regId + " " + t);
+        try {
+            if (t == null) return false;
+            mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData());
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to updateServices " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean discoverServices(int discoveryId, String serviceType) {
+        if (DBG) Slog.d(TAG, "discoverServices: " + discoveryId + " " + serviceType);
+        try {
+            mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to discoverServices " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopServiceDiscovery(int discoveryId) {
+        if (DBG) Slog.d(TAG, "stopServiceDiscovery: " + discoveryId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-discover", discoveryId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to stopServiceDiscovery " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean resolveService(int resolveId, NsdServiceInfo service) {
+        if (DBG) Slog.d(TAG, "resolveService: " + resolveId + " " + service);
+        try {
+            mNativeConnector.execute("mdnssd", "resolve", resolveId, service.getServiceName(),
+                    service.getServiceType(), "local.");
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to resolveService " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopResolveService(int resolveId) {
+        if (DBG) Slog.d(TAG, "stopResolveService: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-resolve", resolveId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to stop resolve " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean getAddrInfo(int resolveId, String hostname) {
+        if (DBG) Slog.d(TAG, "getAdddrInfo: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "getaddrinfo", resolveId, hostname);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to getAddrInfo " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean stopGetAddrInfo(int resolveId) {
+        if (DBG) Slog.d(TAG, "stopGetAdddrInfo: " + resolveId);
+        try {
+            mNativeConnector.execute("mdnssd", "stop-getaddrinfo", resolveId);
+        } catch(NativeDaemonConnectorException e) {
+            Slog.e(TAG, "Failed to stopGetAddrInfo " + e);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public 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 ServiceDiscoverService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        for (ClientInfo client : mClients.values()) {
+            pw.println("Client Info");
+            pw.println(client);
+        }
+
+        mNsdStateMachine.dump(fd, pw, args);
+    }
+
+    /* arg2 on the source message has an id that needs to be retained in replies
+     * see NsdManager for details */
+    private Message obtainMessage(Message srcMsg) {
+        Message msg = Message.obtain();
+        msg.arg2 = srcMsg.arg2;
+        return msg;
+    }
+
+    private void replyToMessage(Message msg, int what) {
+        if (msg.replyTo == null) return;
+        Message dstMsg = obtainMessage(msg);
+        dstMsg.what = what;
+        mReplyChannel.replyToMessage(msg, dstMsg);
+    }
+
+    private void replyToMessage(Message msg, int what, int arg1) {
+        if (msg.replyTo == null) return;
+        Message dstMsg = obtainMessage(msg);
+        dstMsg.what = what;
+        dstMsg.arg1 = arg1;
+        mReplyChannel.replyToMessage(msg, dstMsg);
+    }
+
+    private void replyToMessage(Message msg, int what, Object obj) {
+        if (msg.replyTo == null) return;
+        Message dstMsg = obtainMessage(msg);
+        dstMsg.what = what;
+        dstMsg.obj = obj;
+        mReplyChannel.replyToMessage(msg, dstMsg);
+    }
+
+    /* Information tracked per client */
+    private class ClientInfo {
+
+        private static final int MAX_LIMIT = 10;
+        private final AsyncChannel mChannel;
+        private final Messenger mMessenger;
+        /* Remembers a resolved service until getaddrinfo completes */
+        private NsdServiceInfo mResolvedService;
+
+        /* A map from client id to unique id sent to mDns */
+        private SparseArray<Integer> mClientIds = new SparseArray<Integer>();
+
+        private ClientInfo(AsyncChannel c, Messenger m) {
+            mChannel = c;
+            mMessenger = m;
+            if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
+        }
+
+        @Override
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("mChannel ").append(mChannel).append("\n");
+            sb.append("mMessenger ").append(mMessenger).append("\n");
+            sb.append("mResolvedService ").append(mResolvedService).append("\n");
+            for(int i = 0; i< mClientIds.size(); i++) {
+                sb.append("clientId ").append(mClientIds.keyAt(i));
+                sb.append(" mDnsId ").append(mClientIds.valueAt(i)).append("\n");
+            }
+            return sb.toString();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/RandomBlock.java b/services/core/java/com/android/server/RandomBlock.java
new file mode 100644
index 0000000..6d6d901
--- /dev/null
+++ b/services/core/java/com/android/server/RandomBlock.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.util.Slog;
+
+import java.io.Closeable;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * A block of 512 random {@code byte}s.
+ */
+class RandomBlock {
+
+    private static final String TAG = "RandomBlock";
+    private static final boolean DEBUG = false;
+    private static final int BLOCK_SIZE = 512;
+    private byte[] block = new byte[BLOCK_SIZE];
+
+    private RandomBlock() { }
+
+    static RandomBlock fromFile(String filename) throws IOException {
+        if (DEBUG) Slog.v(TAG, "reading from file " + filename);
+        InputStream stream = null;
+        try {
+            stream = new FileInputStream(filename);
+            return fromStream(stream);
+        } finally {
+            close(stream);
+        }
+    }
+
+    private static RandomBlock fromStream(InputStream in) throws IOException {
+        RandomBlock retval = new RandomBlock();
+        int total = 0;
+        while(total < BLOCK_SIZE) {
+            int result = in.read(retval.block, total, BLOCK_SIZE - total);
+            if (result == -1) {
+                throw new EOFException();
+            }
+            total += result;
+        }
+        return retval;
+    }
+
+    void toFile(String filename, boolean sync) throws IOException {
+        if (DEBUG) Slog.v(TAG, "writing to file " + filename);
+        RandomAccessFile out = null;
+        try {
+            out = new RandomAccessFile(filename, sync ? "rws" : "rw");
+            toDataOut(out);
+            truncateIfPossible(out);
+        } finally {
+            close(out);
+        }
+    }
+
+    private static void truncateIfPossible(RandomAccessFile f) {
+        try {
+            f.setLength(BLOCK_SIZE);
+        } catch (IOException e) {
+            // ignore this exception.  Sometimes, the file we're trying to
+            // write is a character device, such as /dev/urandom, and
+            // these character devices do not support setting the length.
+        }
+    }
+
+    private void toDataOut(DataOutput out) throws IOException {
+        out.write(block);
+    }
+
+    private static void close(Closeable c) {
+        try {
+            if (c == null) {
+                return;
+            }
+            c.close();
+        } catch (IOException e) {
+            Slog.w(TAG, "IOException thrown while closing Closeable", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/RecognitionManagerService.java b/services/core/java/com/android/server/RecognitionManagerService.java
new file mode 100644
index 0000000..c2e749d
--- /dev/null
+++ b/services/core/java/com/android/server/RecognitionManagerService.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2010 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 com.android.internal.content.PackageMonitor;
+
+import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.speech.RecognitionService;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.util.List;
+
+public class RecognitionManagerService extends Binder {
+    final static String TAG = "RecognitionManagerService";
+
+    private final Context mContext;
+    private final MyPackageMonitor mMonitor;
+    private final IPackageManager mIPm;
+
+    private static final boolean DEBUG = false;
+
+    class MyPackageMonitor extends PackageMonitor {
+        public void onSomePackagesChanged() {
+            int userHandle = getChangingUserId();
+            if (DEBUG) Slog.i(TAG, "onSomePackagesChanged user=" + userHandle);
+            ComponentName comp = getCurRecognizer(userHandle);
+            if (comp == null) {
+                if (anyPackagesAppearing()) {
+                    comp = findAvailRecognizer(null, userHandle);
+                    if (comp != null) {
+                        setCurRecognizer(comp, userHandle);
+                    }
+                }
+                return;
+            }
+
+            int change = isPackageDisappearing(comp.getPackageName()); 
+            if (change == PACKAGE_PERMANENT_CHANGE
+                    || change == PACKAGE_TEMPORARY_CHANGE) {
+                setCurRecognizer(findAvailRecognizer(null, userHandle), userHandle);
+                
+            } else if (isPackageModified(comp.getPackageName())) {
+                setCurRecognizer(findAvailRecognizer(comp.getPackageName(), userHandle),
+                        userHandle);
+            }
+        }
+    }
+
+    RecognitionManagerService(Context context) {
+        mContext = context;
+        mMonitor = new MyPackageMonitor();
+        mMonitor.register(context, null, UserHandle.ALL, true);
+        mIPm = AppGlobals.getPackageManager();
+        mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+    }
+
+    public void systemReady() {
+        initForUser(UserHandle.USER_OWNER);
+    }
+
+    private void initForUser(int userHandle) {
+        if (DEBUG) Slog.i(TAG, "initForUser user=" + userHandle);
+        ComponentName comp = getCurRecognizer(userHandle);
+        ServiceInfo info = null;
+        if (comp != null) {
+            // See if the current recognizer is still available.
+            try {
+                info = mIPm.getServiceInfo(comp, 0, userHandle);
+            } catch (RemoteException e) {
+            }
+        }
+        if (info == null) {
+            comp = findAvailRecognizer(null, userHandle);
+            if (comp != null) {
+                setCurRecognizer(comp, userHandle);
+            }
+        }
+    }
+
+    ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
+        List<ResolveInfo> available =
+                mContext.getPackageManager().queryIntentServicesAsUser(
+                        new Intent(RecognitionService.SERVICE_INTERFACE), 0, userHandle);
+        int numAvailable = available.size();
+
+        if (numAvailable == 0) {
+            Slog.w(TAG, "no available voice recognition services found for user " + userHandle);
+            return null;
+        } else {
+            if (prefPackage != null) {
+                for (int i=0; i<numAvailable; i++) {
+                    ServiceInfo serviceInfo = available.get(i).serviceInfo;
+                    if (prefPackage.equals(serviceInfo.packageName)) {
+                        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+                    }
+                }
+            }
+            if (numAvailable > 1) {
+                Slog.w(TAG, "more than one voice recognition service found, picking first");
+            }
+
+            ServiceInfo serviceInfo = available.get(0).serviceInfo;
+            return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        }
+    }
+
+    ComponentName getCurRecognizer(int userHandle) {
+        String curRecognizer = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE, userHandle);
+        if (TextUtils.isEmpty(curRecognizer)) {
+            return null;
+        }
+        if (DEBUG) Slog.i(TAG, "getCurRecognizer curRecognizer=" + curRecognizer
+                + " user=" + userHandle);
+        return ComponentName.unflattenFromString(curRecognizer);
+    }
+
+    void setCurRecognizer(ComponentName comp, int userHandle) {
+        Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                Settings.Secure.VOICE_RECOGNITION_SERVICE,
+                comp != null ? comp.flattenToShortString() : "", userHandle);
+        if (DEBUG) Slog.i(TAG, "setCurRecognizer comp=" + comp
+                + " user=" + userHandle);
+    }
+
+    BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (DEBUG) Slog.i(TAG, "received " + action);
+            if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+                int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userHandle > 0) {
+                    initForUser(userHandle);
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/SamplingProfilerService.java b/services/core/java/com/android/server/SamplingProfilerService.java
new file mode 100644
index 0000000..fbf1aa4
--- /dev/null
+++ b/services/core/java/com/android/server/SamplingProfilerService.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 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.ContentResolver;
+import android.os.DropBoxManager;
+import android.os.FileObserver;
+import android.os.Binder;
+
+import android.util.Slog;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import com.android.internal.os.SamplingProfilerIntegration;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class SamplingProfilerService extends Binder {
+
+    private static final String TAG = "SamplingProfilerService";
+    private static final boolean LOCAL_LOGV = false;
+    public static final String SNAPSHOT_DIR = SamplingProfilerIntegration.SNAPSHOT_DIR;
+
+    private final Context mContext;
+    private FileObserver snapshotObserver;
+
+    public SamplingProfilerService(Context context) {
+        mContext = context;
+        registerSettingObserver(context);
+        startWorking(context);
+    }
+
+    private void startWorking(Context context) {
+        if (LOCAL_LOGV) Slog.v(TAG, "starting SamplingProfilerService!");
+
+        final DropBoxManager dropbox =
+                (DropBoxManager) context.getSystemService(Context.DROPBOX_SERVICE);
+
+        // before FileObserver is ready, there could have already been some snapshots
+        // in the directory, we don't want to miss them
+        File[] snapshotFiles = new File(SNAPSHOT_DIR).listFiles();
+        for (int i = 0; snapshotFiles != null && i < snapshotFiles.length; i++) {
+            handleSnapshotFile(snapshotFiles[i], dropbox);
+        }
+
+        // detect new snapshot and put it in dropbox
+        // delete it afterwards no matter what happened before
+        // Note: needs listening at event ATTRIB rather than CLOSE_WRITE, because we set the
+        // readability of snapshot files after writing them!
+        snapshotObserver = new FileObserver(SNAPSHOT_DIR, FileObserver.ATTRIB) {
+            @Override
+            public void onEvent(int event, String path) {
+                handleSnapshotFile(new File(SNAPSHOT_DIR, path), dropbox);
+            }
+        };
+        snapshotObserver.startWatching();
+
+        if (LOCAL_LOGV) Slog.v(TAG, "SamplingProfilerService activated");
+    }
+
+    private void handleSnapshotFile(File file, DropBoxManager dropbox) {
+        try {
+            dropbox.addFile(TAG, file, 0);
+            if (LOCAL_LOGV) Slog.v(TAG, file.getPath() + " added to dropbox");
+        } catch (IOException e) {
+            Slog.e(TAG, "Can't add " + file.getPath() + " to dropbox", e);
+        } finally {
+            file.delete();
+        }
+    }
+
+    private void registerSettingObserver(Context context) {
+        ContentResolver contentResolver = context.getContentResolver();
+        contentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.SAMPLING_PROFILER_MS),
+                false, new SamplingProfilerSettingsObserver(contentResolver));
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        pw.println("SamplingProfilerService:");
+        pw.println("Watching directory: " + SNAPSHOT_DIR);
+    }
+
+    private class SamplingProfilerSettingsObserver extends ContentObserver {
+        private ContentResolver mContentResolver;
+        public SamplingProfilerSettingsObserver(ContentResolver contentResolver) {
+            super(null);
+            mContentResolver = contentResolver;
+            onChange(false);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            Integer samplingProfilerMs = Settings.Global.getInt(
+                    mContentResolver, Settings.Global.SAMPLING_PROFILER_MS, 0);
+            // setting this secure property will start or stop sampling profiler,
+            // as well as adjust the the time between taking snapshots.
+            SystemProperties.set("persist.sys.profiler_ms", samplingProfilerMs.toString());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
new file mode 100644
index 0000000..1abe458
--- /dev/null
+++ b/services/core/java/com/android/server/SerialService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.hardware.ISerialManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.util.ArrayList;
+
+public class SerialService extends ISerialManager.Stub {
+
+    private final Context mContext;
+    private final String[] mSerialPorts;
+
+    public SerialService(Context context) {
+        mContext = context;
+        mSerialPorts = context.getResources().getStringArray(
+                com.android.internal.R.array.config_serialPorts);
+    }
+
+    public String[] getSerialPorts() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+
+        ArrayList<String> ports = new ArrayList<String>();
+        for (int i = 0; i < mSerialPorts.length; i++) {
+            String path = mSerialPorts[i];
+            if (new File(path).exists()) {
+                ports.add(path);
+            }
+        }
+        String[] result = new String[ports.size()];
+        ports.toArray(result);
+        return result;
+    }
+
+    public ParcelFileDescriptor openSerialPort(String path) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SERIAL_PORT, null);
+        for (int i = 0; i < mSerialPorts.length; i++) {
+            if (mSerialPorts[i].equals(path)) {
+                return native_open(path);
+            }
+        }
+        throw new IllegalArgumentException("Invalid serial port " + path);
+    }
+
+    private native ParcelFileDescriptor native_open(String path);
+}
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
new file mode 100644
index 0000000..5c7bfab
--- /dev/null
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.content.PackageMonitor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Find the best Service, and bind to it.
+ * Handles run-time package changes.
+ */
+public class ServiceWatcher implements ServiceConnection {
+    private static final boolean D = false;
+    public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
+    public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
+
+    private final String mTag;
+    private final Context mContext;
+    private final PackageManager mPm;
+    private final List<HashSet<Signature>> mSignatureSets;
+    private final String mAction;
+
+    /**
+     * If mServicePackageName is not null, only this package will be searched for the service that
+     * implements mAction. When null, all packages in the system that matches one of the signature
+     * in mSignatureSets are searched.
+     */
+    private final String mServicePackageName;
+    private final Runnable mNewServiceWork;
+    private final Handler mHandler;
+
+    private Object mLock = new Object();
+
+    // all fields below synchronized on mLock
+    private IBinder mBinder;   // connected service
+    private String mPackageName;  // current best package
+    private int mVersion = Integer.MIN_VALUE;  // current best version
+    /**
+     * Whether the currently-connected service is multiuser-aware. This can change at run-time
+     * when switching from one version of a service to another.
+     */
+    private boolean mIsMultiuser = false;
+
+    public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
+            List<String> initialPackageNames) {
+        PackageManager pm = context.getPackageManager();
+        ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>();
+        for (int i = 0, size = initialPackageNames.size(); i < size; i++) {
+            String pkg = initialPackageNames.get(i);
+            try {
+                HashSet<Signature> set = new HashSet<Signature>();
+                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
+                set.addAll(Arrays.asList(sigs));
+                sigSets.add(set);
+            } catch (NameNotFoundException e) {
+                Log.w("ServiceWatcher", pkg + " not found");
+            }
+        }
+        return sigSets;
+    }
+
+    public ServiceWatcher(Context context, String logTag, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Runnable newServiceWork,
+            Handler handler) {
+        mContext = context;
+        mTag = logTag;
+        mAction = action;
+        mPm = mContext.getPackageManager();
+        mNewServiceWork = newServiceWork;
+        mHandler = handler;
+        Resources resources = context.getResources();
+
+        // Whether to enable service overlay.
+        boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
+        ArrayList<String>  initialPackageNames = new ArrayList<String>();
+        if (enableOverlay) {
+            // A list of package names used to create the signatures.
+            String[] pkgs = resources.getStringArray(initialPackageNamesResId);
+            if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
+            mServicePackageName = null;
+            if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
+        } else {
+            // The default package name that is searched for service implementation when overlay is
+            // disabled.
+            String servicePackageName = resources.getString(defaultServicePackageNameResId);
+            if (servicePackageName != null) initialPackageNames.add(servicePackageName);
+            mServicePackageName = servicePackageName;
+            if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
+        }
+        mSignatureSets = getSignatureSets(context, initialPackageNames);
+    }
+
+    public boolean start() {
+        synchronized (mLock) {
+            if (!bindBestPackageLocked(mServicePackageName)) return false;
+        }
+
+        // listen for user change
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                    switchUser();
+                }
+            }
+        }, UserHandle.ALL, intentFilter, null, mHandler);
+
+        // listen for relevant package changes if service overlay is enabled.
+        if (mServicePackageName == null) {
+            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
+        }
+
+        return true;
+    }
+
+    /**
+     * Searches and binds to the best package, or do nothing
+     * if the best package is already bound.
+     * Only checks the named package, or checks all packages if it
+     * is null.
+     * Return true if a new package was found to bind to.
+     */
+    private boolean bindBestPackageLocked(String justCheckThisPackage) {
+        Intent intent = new Intent(mAction);
+        if (justCheckThisPackage != null) {
+            intent.setPackage(justCheckThisPackage);
+        }
+        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
+                PackageManager.GET_META_DATA, UserHandle.USER_OWNER);
+        int bestVersion = Integer.MIN_VALUE;
+        String bestPackage = null;
+        boolean bestIsMultiuser = false;
+        if (rInfos != null) {
+            for (ResolveInfo rInfo : rInfos) {
+                String packageName = rInfo.serviceInfo.packageName;
+
+                // check signature
+                try {
+                    PackageInfo pInfo;
+                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+                    if (!isSignatureMatch(pInfo.signatures)) {
+                        Log.w(mTag, packageName + " resolves service " + mAction
+                                + ", but has wrong signature, ignoring");
+                        continue;
+                    }
+                } catch (NameNotFoundException e) {
+                    Log.wtf(mTag, e);
+                    continue;
+                }
+
+                // check metadata
+                int version = Integer.MIN_VALUE;
+                boolean isMultiuser = false;
+                if (rInfo.serviceInfo.metaData != null) {
+                    version = rInfo.serviceInfo.metaData.getInt(
+                            EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
+                    isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
+                }
+
+                if (version > mVersion) {
+                    bestVersion = version;
+                    bestPackage = packageName;
+                    bestIsMultiuser = isMultiuser;
+                }
+            }
+
+            if (D) {
+                Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
+                        (justCheckThisPackage == null ? ""
+                                : "(" + justCheckThisPackage + ") "), rInfos.size(),
+                        (bestPackage == null ? "no new best package"
+                                : "new best package: " + bestPackage)));
+            }
+        } else {
+            if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
+        }
+        if (bestPackage != null) {
+            bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
+            return true;
+        }
+        return false;
+    }
+
+    private void unbindLocked() {
+        String pkg;
+        pkg = mPackageName;
+        mPackageName = null;
+        mVersion = Integer.MIN_VALUE;
+        mIsMultiuser = false;
+        if (pkg != null) {
+            if (D) Log.d(mTag, "unbinding " + pkg);
+            mContext.unbindService(this);
+        }
+    }
+
+    private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
+        unbindLocked();
+        Intent intent = new Intent(mAction);
+        intent.setPackage(packageName);
+        mPackageName = packageName;
+        mVersion = version;
+        mIsMultiuser = isMultiuser;
+        if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
+                + (isMultiuser ? "multi" : "single") + "-user)");
+        mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+                | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.OWNER : UserHandle.CURRENT);
+    }
+
+    public static boolean isSignatureMatch(Signature[] signatures,
+            List<HashSet<Signature>> sigSets) {
+        if (signatures == null) return false;
+
+        // build hashset of input to test against
+        HashSet<Signature> inputSet = new HashSet<Signature>();
+        for (Signature s : signatures) {
+            inputSet.add(s);
+        }
+
+        // test input against each of the signature sets
+        for (HashSet<Signature> referenceSet : sigSets) {
+            if (referenceSet.equals(inputSet)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isSignatureMatch(Signature[] signatures) {
+        return isSignatureMatch(signatures, mSignatureSets);
+    }
+
+    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
+        /**
+         * Called when package has been reinstalled
+         */
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            synchronized (mLock) {
+                if (packageName.equals(mPackageName)) {
+                    // package updated, make sure to rebind
+                    unbindLocked();
+                }
+                // Need to check all packages because this method is also called when a
+                // system app is uninstalled and the stock version in reinstalled.
+                bindBestPackageLocked(null);
+            }
+        }
+
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            synchronized (mLock) {
+                if (packageName.equals(mPackageName)) {
+                    // package updated, make sure to rebind
+                    unbindLocked();
+                }
+                // check the new package is case it is better
+                bindBestPackageLocked(null);
+            }
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            synchronized (mLock) {
+                if (packageName.equals(mPackageName)) {
+                    unbindLocked();
+                    // the currently bound package was removed,
+                    // need to search for a new package
+                    bindBestPackageLocked(null);
+                }
+            }
+        }
+
+        @Override
+        public boolean onPackageChanged(String packageName, int uid, String[] components) {
+            synchronized (mLock) {
+                if (packageName.equals(mPackageName)) {
+                    // service enabled or disabled, make sure to rebind
+                    unbindLocked();
+                }
+                // the service might be disabled, need to search for a new
+                // package
+                bindBestPackageLocked(null);
+            }
+            return super.onPackageChanged(packageName, uid, components);
+        }
+    };
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder binder) {
+        synchronized (mLock) {
+            String packageName = name.getPackageName();
+            if (packageName.equals(mPackageName)) {
+                if (D) Log.d(mTag, packageName + " connected");
+                mBinder = binder;
+                if (mHandler !=null && mNewServiceWork != null) {
+                    mHandler.post(mNewServiceWork);
+                }
+            } else {
+                Log.w(mTag, "unexpected onServiceConnected: " + packageName);
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        synchronized (mLock) {
+            String packageName = name.getPackageName();
+            if (D) Log.d(mTag, packageName + " disconnected");
+
+            if (packageName.equals(mPackageName)) {
+                mBinder = null;
+            }
+        }
+    }
+
+    public String getBestPackageName() {
+        synchronized (mLock) {
+            return mPackageName;
+        }
+    }
+
+    public int getBestVersion() {
+        synchronized (mLock) {
+            return mVersion;
+        }
+    }
+
+    public IBinder getBinder() {
+        synchronized (mLock) {
+            return mBinder;
+        }
+    }
+
+    public void switchUser() {
+        synchronized (mLock) {
+            if (!mIsMultiuser) {
+                unbindLocked();
+                bindBestPackageLocked(mServicePackageName);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/ShutdownActivity.java b/services/core/java/com/android/server/ShutdownActivity.java
new file mode 100644
index 0000000..be65141
--- /dev/null
+++ b/services/core/java/com/android/server/ShutdownActivity.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.server.power.ShutdownThread;
+
+public class ShutdownActivity extends Activity {
+
+    private static final String TAG = "ShutdownActivity";
+    private boolean mReboot;
+    private boolean mConfirm;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
+        mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
+        Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
+
+        Thread thr = new Thread("ShutdownActivity") {
+            @Override
+            public void run() {
+                IPowerManager pm = IPowerManager.Stub.asInterface(
+                        ServiceManager.getService(Context.POWER_SERVICE));
+                try {
+                    if (mReboot) {
+                        pm.reboot(mConfirm, null, false);
+                    } else {
+                        pm.shutdown(mConfirm, false);
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        };
+        thr.start();
+        finish();
+        // Wait for us to tell the power manager to shutdown.
+        try {
+            thr.join();
+        } catch (InterruptedException e) {
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/SystemServer.java b/services/core/java/com/android/server/SystemServer.java
new file mode 100644
index 0000000..662df92
--- /dev/null
+++ b/services/core/java/com/android/server/SystemServer.java
@@ -0,0 +1,1159 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManagerNative;
+import android.app.IAlarmManager;
+import android.app.INotificationManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.media.AudioService;
+import android.net.wifi.p2p.WifiP2pService;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.service.dreams.DreamService;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.view.WindowManager;
+
+import com.android.internal.R;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.SamplingProfilerIntegration;
+import com.android.server.accessibility.AccessibilityManagerService;
+import com.android.server.accounts.AccountManagerService;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.appwidget.AppWidgetService;
+import com.android.server.backup.BackupManagerService;
+import com.android.server.clipboard.ClipboardService;
+import com.android.server.content.ContentService;
+import com.android.server.devicepolicy.DevicePolicyManagerService;
+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;
+import com.android.server.pm.UserManagerService;
+import com.android.server.power.PowerManagerService;
+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;
+import com.android.server.wm.WindowManagerService;
+
+import dalvik.system.VMRuntime;
+import dalvik.system.Zygote;
+
+import java.io.File;
+import java.util.Timer;
+import java.util.TimerTask;
+
+class ServerThread {
+    private static final String TAG = "SystemServer";
+    private static final String ENCRYPTING_STATE = "trigger_restart_min_framework";
+    private static final String ENCRYPTED_STATE = "1";
+
+    ContentResolver mContentResolver;
+
+    void reportWtf(String msg, Throwable e) {
+        Slog.w(TAG, "***********************************************");
+        Log.wtf(TAG, "BOOT FAILURE " + msg, e);
+    }
+
+    public void initAndLoop() {
+        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
+            SystemClock.uptimeMillis());
+
+        Looper.prepareMainLooper();
+
+        android.os.Process.setThreadPriority(
+                android.os.Process.THREAD_PRIORITY_FOREGROUND);
+
+        BinderInternal.disableBackgroundScheduling(true);
+        android.os.Process.setCanSelfBackground(false);
+
+        // Check whether we failed to shut down last time we tried.
+        {
+            final String shutdownAction = SystemProperties.get(
+                    ShutdownThread.SHUTDOWN_ACTION_PROPERTY, "");
+            if (shutdownAction != null && shutdownAction.length() > 0) {
+                boolean reboot = (shutdownAction.charAt(0) == '1');
+
+                final String reason;
+                if (shutdownAction.length() > 1) {
+                    reason = shutdownAction.substring(1, shutdownAction.length());
+                } else {
+                    reason = null;
+                }
+
+                ShutdownThread.rebootOrShutdown(reboot, reason);
+            }
+        }
+
+        String factoryTestStr = SystemProperties.get("ro.factorytest");
+        int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
+                : Integer.parseInt(factoryTestStr);
+
+        Installer installer = null;
+        AccountManagerService accountManager = null;
+        ContentService contentService = null;
+        LightsManager lights = null;
+        PowerManagerService power = null;
+        DisplayManagerService display = null;
+        BatteryService battery = null;
+        VibratorService vibrator = null;
+        IAlarmManager alarm = null;
+        MountService mountService = null;
+        NetworkManagementService networkManagement = null;
+        NetworkStatsService networkStats = null;
+        NetworkPolicyManagerService networkPolicy = null;
+        ConnectivityService connectivity = null;
+        WifiP2pService wifiP2p = null;
+        WifiService wifi = null;
+        NsdService serviceDiscovery= null;
+        IPackageManager pm = null;
+        Context context = null;
+        WindowManagerService wm = null;
+        BluetoothManagerService bluetooth = null;
+        DockObserver dock = null;
+        UsbService usb = null;
+        SerialService serial = null;
+        TwilightManager twilight = null;
+        RecognitionManagerService recognition = null;
+        NetworkTimeUpdateService networkTimeUpdater = null;
+        CommonTimeManagementService commonTimeMgmtService = null;
+        InputManagerService inputManager = null;
+        TelephonyRegistry telephonyRegistry = null;
+        ConsumerIrService consumerIr = null;
+
+        // Create a handler thread just for the window manager to enjoy.
+        HandlerThread wmHandlerThread = new HandlerThread("WindowManager");
+        wmHandlerThread.start();
+        Handler wmHandler = new Handler(wmHandlerThread.getLooper());
+        wmHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                //Looper.myLooper().setMessageLogging(new LogPrinter(
+                //        android.util.Log.DEBUG, TAG, android.util.Log.LOG_ID_SYSTEM));
+                android.os.Process.setThreadPriority(
+                        android.os.Process.THREAD_PRIORITY_DISPLAY);
+                android.os.Process.setCanSelfBackground(false);
+
+                // For debug builds, log event loop stalls to dropbox for analysis.
+                if (StrictMode.conditionallyEnableDebugLogging()) {
+                    Slog.i(TAG, "Enabled StrictMode logging for WM Looper");
+                }
+            }
+        });
+
+        // bootstrap services
+        boolean onlyCore = false;
+        boolean firstBoot = false;
+        try {
+            // Wait for installd to finished starting up so that it has a chance to
+            // create critical directories such as /data/user with the appropriate
+            // permissions.  We need this to complete before we initialize other services.
+            Slog.i(TAG, "Waiting for installd to be ready.");
+            installer = new Installer();
+            installer.ping();
+
+            Slog.i(TAG, "Power Manager");
+            power = new PowerManagerService();
+            ServiceManager.addService(Context.POWER_SERVICE, power);
+
+            Slog.i(TAG, "Activity Manager");
+            context = ActivityManagerService.main(factoryTest);
+        } catch (RuntimeException e) {
+            Slog.e("System", "******************************************");
+            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);
+        boolean disableTelephony = SystemProperties.getBoolean("config.disable_telephony", false);
+        boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
+        boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
+        boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
+        boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false);
+
+        try {
+            Slog.i(TAG, "Display Manager");
+            display = new DisplayManagerService(context, wmHandler);
+            ServiceManager.addService(Context.DISPLAY_SERVICE, display, true);
+
+            Slog.i(TAG, "Telephony Registry");
+            telephonyRegistry = new TelephonyRegistry(context);
+            ServiceManager.addService("telephony.registry", telephonyRegistry);
+
+            Slog.i(TAG, "Scheduling Policy");
+            ServiceManager.addService("scheduling_policy", new SchedulingPolicyService());
+
+            AttributeCache.init(context);
+
+            if (!display.waitForDefaultDisplay()) {
+                reportWtf("Timeout waiting for default display to be initialized.",
+                        new Throwable());
+            }
+
+            Slog.i(TAG, "Package Manager");
+            // Only run "core" apps if we're encrypting the device.
+            String cryptState = SystemProperties.get("vold.decrypt");
+            if (ENCRYPTING_STATE.equals(cryptState)) {
+                Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
+                onlyCore = true;
+            } else if (ENCRYPTED_STATE.equals(cryptState)) {
+                Slog.w(TAG, "Device encrypted - only parsing core apps");
+                onlyCore = true;
+            }
+
+            pm = PackageManagerService.main(context, installer,
+                    factoryTest != SystemServer.FACTORY_TEST_OFF,
+                    onlyCore);
+            try {
+                firstBoot = pm.isFirstBoot();
+            } catch (RemoteException e) {
+            }
+
+            ActivityManagerService.setSystemProcess();
+
+            Slog.i(TAG, "Entropy Mixer");
+            ServiceManager.addService("entropy", new EntropyMixer(context));
+
+            Slog.i(TAG, "User Service");
+            ServiceManager.addService(Context.USER_SERVICE,
+                    UserManagerService.getInstance());
+
+            mContentResolver = context.getContentResolver();
+
+            // The AccountManager must come before the ContentService
+            try {
+                // TODO: seems like this should be disable-able, but req'd by ContentService
+                Slog.i(TAG, "Account Manager");
+                accountManager = new AccountManagerService(context);
+                ServiceManager.addService(Context.ACCOUNT_SERVICE, accountManager);
+            } catch (Throwable e) {
+                Slog.e(TAG, "Failure starting Account Manager", e);
+            }
+
+            Slog.i(TAG, "Content Manager");
+            contentService = ContentService.main(context,
+                    factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);
+
+            Slog.i(TAG, "System Content Providers");
+            ActivityManagerService.installSystemProviders();
+
+            systemServiceManager.startService(LightsService.class);
+            lights = LocalServices.getService(LightsManager.class);
+
+            Slog.i(TAG, "Battery Service");
+            battery = new BatteryService(context, lights);
+            ServiceManager.addService("battery", battery);
+
+            Slog.i(TAG, "Vibrator Service");
+            vibrator = new VibratorService(context);
+            ServiceManager.addService("vibrator", vibrator);
+
+            Slog.i(TAG, "Consumer IR Service");
+            consumerIr = new ConsumerIrService(context);
+            ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr);
+
+            // only initialize the power service after we have started the
+            // lights service, content providers and the battery service.
+            power.init(context, lights, battery,
+                    BatteryStatsService.getService(),
+                    ActivityManagerService.self().getAppOpsService(), display);
+
+            systemServiceManager.startService(AlarmManagerService.class);
+            alarm = IAlarmManager.Stub.asInterface(
+                    ServiceManager.getService(Context.ALARM_SERVICE));
+
+            Slog.i(TAG, "Init Watchdog");
+            Watchdog.getInstance().init(context, ActivityManagerService.self());
+            Watchdog.getInstance().addThread(wmHandler, "WindowManager thread");
+
+            Slog.i(TAG, "Input Manager");
+            inputManager = new InputManagerService(context, wmHandler);
+
+            Slog.i(TAG, "Window Manager");
+            wm = WindowManagerService.main(context, power, display, inputManager,
+                    wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,
+                    !firstBoot, onlyCore);
+            ServiceManager.addService(Context.WINDOW_SERVICE, wm);
+            ServiceManager.addService(Context.INPUT_SERVICE, inputManager);
+
+            ActivityManagerService.self().setWindowManager(wm);
+
+            inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
+            inputManager.start();
+
+            display.setWindowManager(wm);
+            display.setInputManager(inputManager);
+
+            // Skip Bluetooth if we have an emulator kernel
+            // TODO: Use a more reliable check to see if this product should
+            // support Bluetooth - see bug 988521
+            if (SystemProperties.get("ro.kernel.qemu").equals("1")) {
+                Slog.i(TAG, "No Bluetooh Service (emulator)");
+            } else if (factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
+                Slog.i(TAG, "No Bluetooth Service (factory test)");
+            } else if (!context.getPackageManager().hasSystemFeature
+                       (PackageManager.FEATURE_BLUETOOTH)) {
+                Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)");
+            } else if (disableBluetooth) {
+                Slog.i(TAG, "Bluetooth Service disabled by config");
+            } else {
+                Slog.i(TAG, "Bluetooth Manager Service");
+                bluetooth = new BluetoothManagerService(context);
+                ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth);
+            }
+        } catch (RuntimeException e) {
+            Slog.e("System", "******************************************");
+            Slog.e("System", "************ Failure starting core service", e);
+        }
+
+        DevicePolicyManagerService devicePolicy = null;
+        StatusBarManagerService statusBar = null;
+        INotificationManager notification = null;
+        InputMethodManagerService imm = null;
+        AppWidgetService appWidget = null;
+        WallpaperManagerService wallpaper = null;
+        LocationManagerService location = null;
+        CountryDetectorService countryDetector = null;
+        TextServicesManagerService tsms = null;
+        LockSettingsService lockSettings = null;
+        DreamManagerService dreamy = null;
+        AssetAtlasService atlas = null;
+        PrintManagerService printManager = null;
+        MediaRouterService mediaRouter = null;
+
+        // Bring up services needed for UI.
+        if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+            //if (!disableNonCoreServices) { // TODO: View depends on these; mock them?
+            if (true) {
+                try {
+                    Slog.i(TAG, "Input Method Service");
+                    imm = new InputMethodManagerService(context, wm);
+                    ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
+                } catch (Throwable e) {
+                    reportWtf("starting Input Manager Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Accessibility Manager");
+                    ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
+                            new AccessibilityManagerService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Accessibility Manager", e);
+                }
+            }
+        }
+
+        try {
+            wm.displayReady();
+        } catch (Throwable e) {
+            reportWtf("making display ready", e);
+        }
+
+        try {
+            pm.performBootDexOpt();
+        } catch (Throwable e) {
+            reportWtf("performing boot dexopt", e);
+        }
+
+        try {
+            ActivityManagerNative.getDefault().showBootMessage(
+                    context.getResources().getText(
+                            com.android.internal.R.string.android_upgrading_starting_apps),
+                    false);
+        } catch (RemoteException e) {
+        }
+
+        if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+            if (!disableStorage &&
+                !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+                try {
+                    /*
+                     * NotificationManagerService is dependant on MountService,
+                     * (for media / usb notifications) so we must start MountService first.
+                     */
+                    Slog.i(TAG, "Mount Service");
+                    mountService = new MountService(context);
+                    ServiceManager.addService("mount", mountService);
+                } catch (Throwable e) {
+                    reportWtf("starting Mount Service", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG,  "LockSettingsService");
+                    lockSettings = new LockSettingsService(context);
+                    ServiceManager.addService("lock_settings", lockSettings);
+                } catch (Throwable e) {
+                    reportWtf("starting LockSettingsService service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Device Policy");
+                    devicePolicy = new DevicePolicyManagerService(context);
+                    ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);
+                } catch (Throwable e) {
+                    reportWtf("starting DevicePolicyService", e);
+                }
+            }
+
+            if (!disableSystemUI) {
+                try {
+                    Slog.i(TAG, "Status Bar");
+                    statusBar = new StatusBarManagerService(context, wm);
+                    ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar);
+                } catch (Throwable e) {
+                    reportWtf("starting StatusBarManagerService", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Clipboard Service");
+                    ServiceManager.addService(Context.CLIPBOARD_SERVICE,
+                            new ClipboardService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Clipboard Service", e);
+                }
+            }
+
+            if (!disableNetwork) {
+                try {
+                    Slog.i(TAG, "NetworkManagement Service");
+                    networkManagement = NetworkManagementService.create(context);
+                    ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement);
+                } catch (Throwable e) {
+                    reportWtf("starting NetworkManagement Service", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Text Service Manager Service");
+                    tsms = new TextServicesManagerService(context);
+                    ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms);
+                } catch (Throwable e) {
+                    reportWtf("starting Text Service Manager Service", e);
+                }
+            }
+
+            if (!disableNetwork) {
+                try {
+                    Slog.i(TAG, "NetworkStats Service");
+                    networkStats = new NetworkStatsService(context, networkManagement, alarm);
+                    ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
+                } catch (Throwable e) {
+                    reportWtf("starting NetworkStats Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "NetworkPolicy Service");
+                    networkPolicy = new NetworkPolicyManagerService(
+                            context, ActivityManagerService.self(), power,
+                            networkStats, networkManagement);
+                    ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy);
+                } catch (Throwable e) {
+                    reportWtf("starting NetworkPolicy Service", e);
+                }
+
+               try {
+                    Slog.i(TAG, "Wi-Fi P2pService");
+                    wifiP2p = new WifiP2pService(context);
+                    ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p);
+                } catch (Throwable e) {
+                    reportWtf("starting Wi-Fi P2pService", e);
+                }
+
+               try {
+                    Slog.i(TAG, "Wi-Fi Service");
+                    wifi = new WifiService(context);
+                    ServiceManager.addService(Context.WIFI_SERVICE, wifi);
+                } catch (Throwable e) {
+                    reportWtf("starting Wi-Fi Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Connectivity Service");
+                    connectivity = new ConnectivityService(
+                            context, networkManagement, networkStats, networkPolicy);
+                    ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity);
+                    networkStats.bindConnectivityManager(connectivity);
+                    networkPolicy.bindConnectivityManager(connectivity);
+
+                    wifiP2p.connectivityServiceReady();
+                    wifi.checkAndStartWifi();
+                } catch (Throwable e) {
+                    reportWtf("starting Connectivity Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Network Service Discovery Service");
+                    serviceDiscovery = NsdService.create(context);
+                    ServiceManager.addService(
+                            Context.NSD_SERVICE, serviceDiscovery);
+                } catch (Throwable e) {
+                    reportWtf("starting Service Discovery Service", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "UpdateLock Service");
+                    ServiceManager.addService(Context.UPDATE_LOCK_SERVICE,
+                            new UpdateLockService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting UpdateLockService", e);
+                }
+            }
+
+            /*
+             * MountService has a few dependencies: Notification Manager and
+             * AppWidget Provider. Make sure MountService is completely started
+             * first before continuing.
+             */
+            if (mountService != null && !onlyCore) {
+                mountService.waitForAsecScan();
+            }
+
+            try {
+                if (accountManager != null)
+                    accountManager.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making Account Manager Service ready", e);
+            }
+
+            try {
+                if (contentService != null)
+                    contentService.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making Content Service ready", e);
+            }
+
+            systemServiceManager.startService(NotificationManagerService.class);
+            notification = INotificationManager.Stub.asInterface(
+                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+            networkPolicy.bindNotificationManager(notification);
+
+            systemServiceManager.startService(DeviceStorageMonitorService.class);
+
+            if (!disableLocation) {
+                try {
+                    Slog.i(TAG, "Location Manager");
+                    location = new LocationManagerService(context);
+                    ServiceManager.addService(Context.LOCATION_SERVICE, location);
+                } catch (Throwable e) {
+                    reportWtf("starting Location Manager", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Country Detector");
+                    countryDetector = new CountryDetectorService(context);
+                    ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector);
+                } catch (Throwable e) {
+                    reportWtf("starting Country Detector", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Search Service");
+                    ServiceManager.addService(Context.SEARCH_SERVICE,
+                            new SearchManagerService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Search Service", e);
+                }
+            }
+
+            try {
+                Slog.i(TAG, "DropBox Service");
+                ServiceManager.addService(Context.DROPBOX_SERVICE,
+                        new DropBoxManagerService(context, new File("/data/system/dropbox")));
+            } catch (Throwable e) {
+                reportWtf("starting DropBoxManagerService", e);
+            }
+
+            if (!disableNonCoreServices && context.getResources().getBoolean(
+                        R.bool.config_enableWallpaperService)) {
+                try {
+                    Slog.i(TAG, "Wallpaper Service");
+                    wallpaper = new WallpaperManagerService(context);
+                    ServiceManager.addService(Context.WALLPAPER_SERVICE, wallpaper);
+                } catch (Throwable e) {
+                    reportWtf("starting Wallpaper Service", e);
+                }
+            }
+
+            if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
+                try {
+                    Slog.i(TAG, "Audio Service");
+                    ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context));
+                } catch (Throwable e) {
+                    reportWtf("starting Audio Service", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Dock Observer");
+                    // Listen for dock station changes
+                    dock = new DockObserver(context);
+                } catch (Throwable e) {
+                    reportWtf("starting DockObserver", e);
+                }
+            }
+
+            if (!disableMedia) {
+                try {
+                    Slog.i(TAG, "Wired Accessory Manager");
+                    // Listen for wired headset changes
+                    inputManager.setWiredAccessoryCallbacks(
+                            new WiredAccessoryManager(context, inputManager));
+                } catch (Throwable e) {
+                    reportWtf("starting WiredAccessoryManager", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "USB Service");
+                    // Manage USB host and device support
+                    usb = new UsbService(context);
+                    ServiceManager.addService(Context.USB_SERVICE, usb);
+                } catch (Throwable e) {
+                    reportWtf("starting UsbService", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Serial Service");
+                    // Serial port support
+                    serial = new SerialService(context);
+                    ServiceManager.addService(Context.SERIAL_SERVICE, serial);
+                } catch (Throwable e) {
+                    Slog.e(TAG, "Failure starting SerialService", e);
+                }
+            }
+
+            systemServiceManager.startService(TwilightService.class);
+            twilight = LocalServices.getService(TwilightManager.class);
+
+            systemServiceManager.startService(UiModeManagerService.class);
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Backup Service");
+                    ServiceManager.addService(Context.BACKUP_SERVICE,
+                            new BackupManagerService(context));
+                } catch (Throwable e) {
+                    Slog.e(TAG, "Failure starting Backup Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "AppWidget Service");
+                    appWidget = new AppWidgetService(context);
+                    ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget);
+                } catch (Throwable e) {
+                    reportWtf("starting AppWidget Service", e);
+                }
+
+                try {
+                    Slog.i(TAG, "Recognition Service");
+                    recognition = new RecognitionManagerService(context);
+                } catch (Throwable e) {
+                    reportWtf("starting Recognition Service", e);
+                }
+            }
+
+            try {
+                Slog.i(TAG, "DiskStats Service");
+                ServiceManager.addService("diskstats", new DiskStatsService(context));
+            } catch (Throwable e) {
+                reportWtf("starting DiskStats Service", e);
+            }
+
+            try {
+                // need to add this service even if SamplingProfilerIntegration.isEnabled()
+                // is false, because it is this service that detects system property change and
+                // turns on SamplingProfilerIntegration. Plus, when sampling profiler doesn't work,
+                // there is little overhead for running this service.
+                Slog.i(TAG, "SamplingProfiler Service");
+                ServiceManager.addService("samplingprofiler",
+                            new SamplingProfilerService(context));
+            } catch (Throwable e) {
+                reportWtf("starting SamplingProfiler Service", e);
+            }
+
+            if (!disableNetwork) {
+                try {
+                    Slog.i(TAG, "NetworkTimeUpdateService");
+                    networkTimeUpdater = new NetworkTimeUpdateService(context);
+                } catch (Throwable e) {
+                    reportWtf("starting NetworkTimeUpdate service", e);
+                }
+            }
+
+            if (!disableMedia) {
+                try {
+                    Slog.i(TAG, "CommonTimeManagementService");
+                    commonTimeMgmtService = new CommonTimeManagementService(context);
+                    ServiceManager.addService("commontime_management", commonTimeMgmtService);
+                } catch (Throwable e) {
+                    reportWtf("starting CommonTimeManagementService service", e);
+                }
+            }
+
+            if (!disableNetwork) {
+                try {
+                    Slog.i(TAG, "CertBlacklister");
+                    CertBlacklister blacklister = new CertBlacklister(context);
+                } catch (Throwable e) {
+                    reportWtf("starting CertBlacklister", e);
+                }
+            }
+
+            if (!disableNonCoreServices && 
+                context.getResources().getBoolean(R.bool.config_dreamsSupported)) {
+                try {
+                    Slog.i(TAG, "Dreams Service");
+                    // Dreams (interactive idle-time views, a/k/a screen savers)
+                    dreamy = new DreamManagerService(context, wmHandler);
+                    ServiceManager.addService(DreamService.DREAM_SERVICE, dreamy);
+                } catch (Throwable e) {
+                    reportWtf("starting DreamManagerService", e);
+                }
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Assets Atlas Service");
+                    atlas = new AssetAtlasService(context);
+                    ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas);
+                } catch (Throwable e) {
+                    reportWtf("starting AssetAtlasService", e);
+                }
+            }
+
+            try {
+                Slog.i(TAG, "IdleMaintenanceService");
+                new IdleMaintenanceService(context, battery);
+            } catch (Throwable e) {
+                reportWtf("starting IdleMaintenanceService", e);
+            }
+
+            try {
+                Slog.i(TAG, "Print Service");
+                printManager = new PrintManagerService(context);
+                ServiceManager.addService(Context.PRINT_SERVICE, printManager);
+            } catch (Throwable e) {
+                reportWtf("starting Print Service", e);
+            }
+
+            if (!disableNonCoreServices) {
+                try {
+                    Slog.i(TAG, "Media Router Service");
+                    mediaRouter = new MediaRouterService(context);
+                    ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+                } catch (Throwable e) {
+                    reportWtf("starting MediaRouterService", e);
+                }
+            }
+        }
+
+        // Before things start rolling, be sure we have decided whether
+        // we are in safe mode.
+        final boolean safeMode = wm.detectSafeMode();
+        if (safeMode) {
+            ActivityManagerService.self().enterSafeMode();
+            // Post the safe mode state in the Zygote class
+            Zygote.systemInSafeMode = true;
+            // Disable the JIT for the system_server process
+            VMRuntime.getRuntime().disableJitCompilation();
+        } else {
+            // Enable the JIT for the system_server process
+            VMRuntime.getRuntime().startJitCompilation();
+        }
+
+        // It is now time to start up the app processes...
+
+        try {
+            vibrator.systemReady();
+        } catch (Throwable e) {
+            reportWtf("making Vibrator Service ready", e);
+        }
+
+        if (lockSettings != null) {
+            try {
+                lockSettings.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making Lock Settings Service ready", e);
+            }
+        }
+
+        if (devicePolicy != null) {
+            try {
+                devicePolicy.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making Device Policy Service ready", e);
+            }
+        }
+
+        systemServiceManager.startBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
+
+        try {
+            wm.systemReady();
+        } catch (Throwable e) {
+            reportWtf("making Window Manager Service ready", e);
+        }
+
+        if (safeMode) {
+            ActivityManagerService.self().showSafeModeOverlay();
+        }
+
+        // Update the configuration for this context by hand, because we're going
+        // to start using it before the config change done in wm.systemReady() will
+        // propagate to it.
+        Configuration config = wm.computeNewConfiguration();
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager w = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+        w.getDefaultDisplay().getMetrics(metrics);
+        context.getResources().updateConfiguration(config, metrics);
+
+        try {
+            power.systemReady(twilight, dreamy);
+        } catch (Throwable e) {
+            reportWtf("making Power Manager Service ready", e);
+        }
+
+        try {
+            pm.systemReady();
+        } catch (Throwable e) {
+            reportWtf("making Package Manager Service ready", e);
+        }
+
+        try {
+            display.systemReady(safeMode, onlyCore);
+        } catch (Throwable e) {
+            reportWtf("making Display Manager Service ready", e);
+        }
+
+        // These are needed to propagate to the runnable below.
+        final Context contextF = context;
+        final MountService mountServiceF = mountService;
+        final BatteryService batteryF = battery;
+        final NetworkManagementService networkManagementF = networkManagement;
+        final NetworkStatsService networkStatsF = networkStats;
+        final NetworkPolicyManagerService networkPolicyF = networkPolicy;
+        final ConnectivityService connectivityF = connectivity;
+        final DockObserver dockF = dock;
+        final UsbService usbF = usb;
+        final AppWidgetService appWidgetF = appWidget;
+        final WallpaperManagerService wallpaperF = wallpaper;
+        final InputMethodManagerService immF = imm;
+        final RecognitionManagerService recognitionF = recognition;
+        final LocationManagerService locationF = location;
+        final CountryDetectorService countryDetectorF = countryDetector;
+        final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
+        final CommonTimeManagementService commonTimeMgmtServiceF = commonTimeMgmtService;
+        final TextServicesManagerService textServiceManagerServiceF = tsms;
+        final StatusBarManagerService statusBarF = statusBar;
+        final DreamManagerService dreamyF = dreamy;
+        final AssetAtlasService atlasF = atlas;
+        final InputManagerService inputManagerF = inputManager;
+        final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
+        final PrintManagerService printManagerF = printManager;
+        final MediaRouterService mediaRouterF = mediaRouter;
+
+        // We now tell the activity manager it is okay to run third party
+        // code.  It will call back into us once it has gotten to the state
+        // where third party code can really run (but before it has actually
+        // started launching the initial applications), for us to complete our
+        // initialization.
+        ActivityManagerService.self().systemReady(new Runnable() {
+            public void run() {
+                Slog.i(TAG, "Making services ready");
+
+                try {
+                    ActivityManagerService.self().startObservingNativeCrashes();
+                } catch (Throwable e) {
+                    reportWtf("observing native crashes", e);
+                }
+                try {
+                    startSystemUi(contextF);
+                } catch (Throwable e) {
+                    reportWtf("starting System UI", e);
+                }
+                try {
+                    if (mountServiceF != null) mountServiceF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Mount Service ready", e);
+                }
+                try {
+                    if (batteryF != null) batteryF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Battery Service ready", e);
+                }
+                try {
+                    if (networkManagementF != null) networkManagementF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Network Managment Service ready", e);
+                }
+                try {
+                    if (networkStatsF != null) networkStatsF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Network Stats Service ready", e);
+                }
+                try {
+                    if (networkPolicyF != null) networkPolicyF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Network Policy Service ready", e);
+                }
+                try {
+                    if (connectivityF != null) connectivityF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Connectivity Service ready", e);
+                }
+                try {
+                    if (dockF != null) dockF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Dock Service ready", e);
+                }
+                try {
+                    if (usbF != null) usbF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making USB Service ready", e);
+                }
+                try {
+                    if (recognitionF != null) recognitionF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Recognition Service ready", e);
+                }
+                Watchdog.getInstance().start();
+
+                // 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);
+                } catch (Throwable e) {
+                    reportWtf("Notifying AppWidgetService running", e);
+                }
+                try {
+                    if (wallpaperF != null) wallpaperF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying WallpaperService running", e);
+                }
+                try {
+                    if (immF != null) immF.systemRunning(statusBarF);
+                } catch (Throwable e) {
+                    reportWtf("Notifying InputMethodService running", e);
+                }
+                try {
+                    if (locationF != null) locationF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying Location Service running", e);
+                }
+                try {
+                    if (countryDetectorF != null) countryDetectorF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying CountryDetectorService running", e);
+                }
+                try {
+                    if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying NetworkTimeService running", e);
+                }
+                try {
+                    if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying CommonTimeManagementService running", e);
+                }
+                try {
+                    if (textServiceManagerServiceF != null)
+                        textServiceManagerServiceF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying TextServicesManagerService running", e);
+                }
+                try {
+                    if (dreamyF != null) dreamyF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying DreamManagerService running", e);
+                }
+                try {
+                    if (atlasF != null) atlasF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying AssetAtlasService running", e);
+                }
+                try {
+                    // TODO(BT) Pass parameter to input manager
+                    if (inputManagerF != null) inputManagerF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying InputManagerService running", e);
+                }
+
+                try {
+                    if (telephonyRegistryF != null) telephonyRegistryF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying TelephonyRegistry running", e);
+                }
+
+                try {
+                    if (printManagerF != null) printManagerF.systemRuning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying PrintManagerService running", e);
+                }
+
+                try {
+                    if (mediaRouterF != null) mediaRouterF.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying MediaRouterService running", e);
+                }
+
+                systemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETE);
+            }
+        });
+
+        // For debug builds, log event loop stalls to dropbox for analysis.
+        if (StrictMode.conditionallyEnableDebugLogging()) {
+            Slog.i(TAG, "Enabled StrictMode for system server main thread.");
+        }
+
+        Looper.loop();
+        Slog.d(TAG, "System ServerThread is exiting!");
+    }
+
+    static final void startSystemUi(Context context) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.systemui",
+                    "com.android.systemui.SystemUIService"));
+        //Slog.d(TAG, "Starting service: " + intent);
+        context.startServiceAsUser(intent, UserHandle.OWNER);
+    }
+}
+
+public class SystemServer {
+    private static final String TAG = "SystemServer";
+
+    public static final int FACTORY_TEST_OFF = 0;
+    public static final int FACTORY_TEST_LOW_LEVEL = 1;
+    public static final int FACTORY_TEST_HIGH_LEVEL = 2;
+
+    static Timer timer;
+    static final long SNAPSHOT_INTERVAL = 60 * 60 * 1000; // 1hr
+
+    // The earliest supported time.  We pick one day into 1970, to
+    // give any timezone code room without going into negative time.
+    private static final long EARLIEST_SUPPORTED_TIME = 86400 * 1000;
+
+    /**
+     * Called to initialize native system services.
+     */
+    private static native void nativeInit();
+
+    public static void main(String[] args) {
+
+        /*
+         * In case the runtime switched since last boot (such as when
+         * the old runtime was removed in an OTA), set the system
+         * property so that it is in sync. We can't do this in
+         * libnativehelper's JniInvocation::Init code where we already
+         * had to fallback to a different runtime because it is
+         * running as root and we need to be the system user to set
+         * the property. http://b/11463182
+         */
+        SystemProperties.set("persist.sys.dalvik.vm.lib",
+                             VMRuntime.getRuntime().vmLibrary());
+
+        if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
+            // If a device's clock is before 1970 (before 0), a lot of
+            // APIs crash dealing with negative numbers, notably
+            // java.io.File#setLastModified, so instead we fake it and
+            // hope that time from cell towers or NTP fixes it
+            // shortly.
+            Slog.w(TAG, "System clock is before 1970; setting to 1970.");
+            SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
+        }
+
+        if (SamplingProfilerIntegration.isEnabled()) {
+            SamplingProfilerIntegration.start();
+            timer = new Timer();
+            timer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    SamplingProfilerIntegration.writeSnapshot("system_server", null);
+                }
+            }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
+        }
+
+        // Mmmmmm... more memory!
+        dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
+
+        // The system server has to run all of the time, so it needs to be
+        // as efficient as possible with its memory usage.
+        VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
+
+        Environment.setUserRequired(true);
+
+        System.loadLibrary("android_servers");
+
+        Slog.i(TAG, "Entered the Android system server!");
+
+        // Initialize native services.
+        nativeInit();
+
+        // This used to be its own separate thread, but now it is
+        // just the loop we run on the main thread.
+        ServerThread thr = new ServerThread();
+        thr.initAndLoop();
+    }
+}
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
new file mode 100644
index 0000000..b9d30ce
--- /dev/null
+++ b/services/core/java/com/android/server/SystemService.java
@@ -0,0 +1,156 @@
+/*
+ * 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_LOCK_SETTINGS_READY = 480;
+    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);
+    }
+
+    public final boolean isSafeMode() {
+        return mManager.isSafeMode();
+    }
+
+    /**
+     * 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/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
new file mode 100644
index 0000000..59f7e92
--- /dev/null
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -0,0 +1,163 @@
+/*
+ * 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;
+    private boolean mSafeMode;
+
+    // Services that should receive lifecycle events.
+    private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+
+    private int mCurrentPhase = -1;
+
+    public SystemServiceManager(Context context) {
+        mContext = context;
+    }
+
+    public void startService(String className) {
+        try {
+            startService(Class.forName(className));
+        } catch (ClassNotFoundException cnfe) {
+            Slog.i(TAG, className + " not available, ignoring.");
+        }
+    }
+
+    /**
+     * 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);
+            }
+        }
+    }
+
+    /** Sets the safe mode flag for services to query. */
+    public void setSafeMode(boolean safeMode) {
+        mSafeMode = safeMode;
+    }
+
+    /**
+     * Returns whether we are booting into safe mode.
+     * @return safe mode flag
+     */
+    public boolean isSafeMode() {
+        return mSafeMode;
+    }
+
+    /**
+     * 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/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
new file mode 100644
index 0000000..699d79e
--- /dev/null
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -0,0 +1,797 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.LinkCapabilities;
+import android.net.LinkProperties;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telephony.CellLocation;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.CellInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.ServiceStateTracker;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.server.am.BatteryStatsService;
+
+/**
+ * Since phone process can be restarted, this class provides a centralized place
+ * that applications can register and be called back from.
+ */
+class TelephonyRegistry extends ITelephonyRegistry.Stub {
+    private static final String TAG = "TelephonyRegistry";
+    private static final boolean DBG = false;
+    private static final boolean DBG_LOC = false;
+
+    private static class Record {
+        String pkgForDebug;
+
+        IBinder binder;
+
+        IPhoneStateListener callback;
+
+        int callerUid;
+
+        int events;
+
+        @Override
+        public String toString() {
+            return "{pkgForDebug=" + pkgForDebug + " callerUid=" + callerUid +
+                    " events=" + Integer.toHexString(events) + "}";
+        }
+    }
+
+    private final Context mContext;
+
+    // access should be inside synchronized (mRecords) for these two fields
+    private final ArrayList<IBinder> mRemoveList = new ArrayList<IBinder>();
+    private final ArrayList<Record> mRecords = new ArrayList<Record>();
+
+    private final IBatteryStats mBatteryStats;
+
+    private int mCallState = TelephonyManager.CALL_STATE_IDLE;
+
+    private String mCallIncomingNumber = "";
+
+    private ServiceState mServiceState = new ServiceState();
+
+    private SignalStrength mSignalStrength = new SignalStrength();
+
+    private boolean mMessageWaiting = false;
+
+    private boolean mCallForwarding = false;
+
+    private int mDataActivity = TelephonyManager.DATA_ACTIVITY_NONE;
+
+    private int mDataConnectionState = TelephonyManager.DATA_UNKNOWN;
+
+    private boolean mDataConnectionPossible = false;
+
+    private String mDataConnectionReason = "";
+
+    private String mDataConnectionApn = "";
+
+    private ArrayList<String> mConnectedApns;
+
+    private LinkProperties mDataConnectionLinkProperties;
+
+    private LinkCapabilities mDataConnectionLinkCapabilities;
+
+    private Bundle mCellLocation = new Bundle();
+
+    private int mDataConnectionNetworkType;
+
+    private int mOtaspMode = ServiceStateTracker.OTASP_UNKNOWN;
+
+    private List<CellInfo> mCellInfo = null;
+
+    static final int PHONE_STATE_PERMISSION_MASK =
+                PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
+                PhoneStateListener.LISTEN_CALL_STATE |
+                PhoneStateListener.LISTEN_DATA_ACTIVITY |
+                PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
+                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR;
+
+    private static final int MSG_USER_SWITCHED = 1;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_SWITCHED: {
+                    if (DBG) Slog.d(TAG, "MSG_USER_SWITCHED userId=" + msg.arg1);
+                    TelephonyRegistry.this.notifyCellLocation(mCellLocation);
+                    break;
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_SWITCHED,
+                       intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0), 0));
+            }
+        }
+    };
+
+    // we keep a copy of all of the state so we can send it out when folks
+    // register for it
+    //
+    // In these calls we call with the lock held. This is safe becasuse remote
+    // calls go through a oneway interface and local calls going through a
+    // handler before they get to app code.
+
+    TelephonyRegistry(Context context) {
+        CellLocation  location = CellLocation.getEmpty();
+
+        // Note that location can be null for non-phone builds like
+        // like the generic one.
+        if (location != null) {
+            location.fillInNotifierBundle(mCellLocation);
+        }
+        mContext = context;
+        mBatteryStats = BatteryStatsService.getService();
+        mConnectedApns = new ArrayList<String>();
+    }
+
+    public void systemRunning() {
+        // Watch for interesting updates
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        filter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+    }
+
+    @Override
+    public void listen(String pkgForDebug, IPhoneStateListener callback, int events,
+            boolean notifyNow) {
+        int callerUid = UserHandle.getCallingUserId();
+        int myUid = UserHandle.myUserId();
+        if (DBG) {
+            Slog.d(TAG, "listen: E pkg=" + pkgForDebug + " events=0x" + Integer.toHexString(events)
+                + " myUid=" + myUid
+                + " callerUid=" + callerUid);
+        }
+        if (events != 0) {
+            /* Checks permission and throws Security exception */
+            checkListenerPermission(events);
+
+            synchronized (mRecords) {
+                // register
+                Record r = null;
+                find_and_add: {
+                    IBinder b = callback.asBinder();
+                    final int N = mRecords.size();
+                    for (int i = 0; i < N; i++) {
+                        r = mRecords.get(i);
+                        if (b == r.binder) {
+                            break find_and_add;
+                        }
+                    }
+                    r = new Record();
+                    r.binder = b;
+                    r.callback = callback;
+                    r.pkgForDebug = pkgForDebug;
+                    r.callerUid = callerUid;
+                    mRecords.add(r);
+                    if (DBG) Slog.i(TAG, "listen: add new record=" + r);
+                }
+                int send = events & (events ^ r.events);
+                r.events = events;
+                if (notifyNow) {
+                    if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
+                        try {
+                            r.callback.onServiceStateChanged(new ServiceState(mServiceState));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
+                        try {
+                            int gsmSignalStrength = mSignalStrength.getGsmSignalStrength();
+                            r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+                                    : gsmSignalStrength));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
+                        try {
+                            r.callback.onMessageWaitingIndicatorChanged(mMessageWaiting);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
+                        try {
+                            r.callback.onCallForwardingIndicatorChanged(mCallForwarding);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
+                        try {
+                            if (DBG_LOC) Slog.d(TAG, "listen: mCellLocation=" + mCellLocation);
+                            r.callback.onCellLocationChanged(new Bundle(mCellLocation));
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
+                        try {
+                            r.callback.onCallStateChanged(mCallState, mCallIncomingNumber);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+                        try {
+                            r.callback.onDataConnectionStateChanged(mDataConnectionState,
+                                mDataConnectionNetworkType);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
+                        try {
+                            r.callback.onDataActivity(mDataActivity);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+                        try {
+                            r.callback.onSignalStrengthsChanged(mSignalStrength);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if ((events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
+                        try {
+                            r.callback.onOtaspChanged(mOtaspMode);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                    if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
+                        try {
+                            if (DBG_LOC) Slog.d(TAG, "listen: mCellInfo=" + mCellInfo);
+                            r.callback.onCellInfoChanged(mCellInfo);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                }
+            }
+        } else {
+            remove(callback.asBinder());
+        }
+    }
+
+    private void remove(IBinder binder) {
+        synchronized (mRecords) {
+            final int recordCount = mRecords.size();
+            for (int i = 0; i < recordCount; i++) {
+                if (mRecords.get(i).binder == binder) {
+                    mRecords.remove(i);
+                    return;
+                }
+            }
+        }
+    }
+
+    public void notifyCallState(int state, String incomingNumber) {
+        if (!checkNotifyPermission("notifyCallState()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mCallState = state;
+            mCallIncomingNumber = incomingNumber;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_CALL_STATE) != 0) {
+                    try {
+                        r.callback.onCallStateChanged(state, incomingNumber);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+        broadcastCallStateChanged(state, incomingNumber);
+    }
+
+    public void notifyServiceState(ServiceState state) {
+        if (!checkNotifyPermission("notifyServiceState()")){
+            return;
+        }
+        synchronized (mRecords) {
+            mServiceState = state;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {
+                    try {
+                        r.callback.onServiceStateChanged(new ServiceState(state));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+        broadcastServiceStateChanged(state);
+    }
+
+    public void notifySignalStrength(SignalStrength signalStrength) {
+        if (!checkNotifyPermission("notifySignalStrength()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mSignalStrength = signalStrength;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+                    try {
+                        r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+                if ((r.events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
+                    try {
+                        int gsmSignalStrength = signalStrength.getGsmSignalStrength();
+                        r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+                                : gsmSignalStrength));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+        broadcastSignalStrengthChanged(signalStrength);
+    }
+
+    public void notifyCellInfo(List<CellInfo> cellInfo) {
+        if (!checkNotifyPermission("notifyCellInfo()")) {
+            return;
+        }
+
+        synchronized (mRecords) {
+            mCellInfo = cellInfo;
+            for (Record r : mRecords) {
+                if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO)) {
+                    try {
+                        if (DBG_LOC) {
+                            Slog.d(TAG, "notifyCellInfo: mCellInfo=" + mCellInfo + " r=" + r);
+                        }
+                        r.callback.onCellInfoChanged(cellInfo);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    public void notifyMessageWaitingChanged(boolean mwi) {
+        if (!checkNotifyPermission("notifyMessageWaitingChanged()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mMessageWaiting = mwi;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR) != 0) {
+                    try {
+                        r.callback.onMessageWaitingIndicatorChanged(mwi);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    public void notifyCallForwardingChanged(boolean cfi) {
+        if (!checkNotifyPermission("notifyCallForwardingChanged()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mCallForwarding = cfi;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR) != 0) {
+                    try {
+                        r.callback.onCallForwardingIndicatorChanged(cfi);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    public void notifyDataActivity(int state) {
+        if (!checkNotifyPermission("notifyDataActivity()" )) {
+            return;
+        }
+        synchronized (mRecords) {
+            mDataActivity = state;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_DATA_ACTIVITY) != 0) {
+                    try {
+                        r.callback.onDataActivity(state);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    public void notifyDataConnection(int state, boolean isDataConnectivityPossible,
+            String reason, String apn, String apnType, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities, int networkType, boolean roaming) {
+        if (!checkNotifyPermission("notifyDataConnection()" )) {
+            return;
+        }
+        if (DBG) {
+            Slog.i(TAG, "notifyDataConnection: state=" + state + " isDataConnectivityPossible="
+                + isDataConnectivityPossible + " reason='" + reason
+                + "' apn='" + apn + "' apnType=" + apnType + " networkType=" + networkType
+                + " mRecords.size()=" + mRecords.size() + " mRecords=" + mRecords);
+        }
+        synchronized (mRecords) {
+            boolean modified = false;
+            if (state == TelephonyManager.DATA_CONNECTED) {
+                if (!mConnectedApns.contains(apnType)) {
+                    mConnectedApns.add(apnType);
+                    if (mDataConnectionState != state) {
+                        mDataConnectionState = state;
+                        modified = true;
+                    }
+                }
+            } else {
+                if (mConnectedApns.remove(apnType)) {
+                    if (mConnectedApns.isEmpty()) {
+                        mDataConnectionState = state;
+                        modified = true;
+                    } else {
+                        // leave mDataConnectionState as is and
+                        // send out the new status for the APN in question.
+                    }
+                }
+            }
+            mDataConnectionPossible = isDataConnectivityPossible;
+            mDataConnectionReason = reason;
+            mDataConnectionLinkProperties = linkProperties;
+            mDataConnectionLinkCapabilities = linkCapabilities;
+            if (mDataConnectionNetworkType != networkType) {
+                mDataConnectionNetworkType = networkType;
+                // need to tell registered listeners about the new network type
+                modified = true;
+            }
+            if (modified) {
+                if (DBG) {
+                    Slog.d(TAG, "onDataConnectionStateChanged(" + mDataConnectionState
+                        + ", " + mDataConnectionNetworkType + ")");
+                }
+                for (Record r : mRecords) {
+                    if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_STATE) != 0) {
+                        try {
+                            r.callback.onDataConnectionStateChanged(mDataConnectionState,
+                                    mDataConnectionNetworkType);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+                handleRemoveListLocked();
+            }
+        }
+        broadcastDataConnectionStateChanged(state, isDataConnectivityPossible, reason, apn,
+                apnType, linkProperties, linkCapabilities, roaming);
+    }
+
+    public void notifyDataConnectionFailed(String reason, String apnType) {
+        if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
+            return;
+        }
+        /*
+         * This is commented out because there is no onDataConnectionFailed callback
+         * in PhoneStateListener. There should be.
+        synchronized (mRecords) {
+            mDataConnectionFailedReason = reason;
+            final int N = mRecords.size();
+            for (int i=N-1; i>=0; i--) {
+                Record r = mRecords.get(i);
+                if ((r.events & PhoneStateListener.LISTEN_DATA_CONNECTION_FAILED) != 0) {
+                    // XXX
+                }
+            }
+        }
+        */
+        broadcastDataConnectionFailed(reason, apnType);
+    }
+
+    public void notifyCellLocation(Bundle cellLocation) {
+        if (!checkNotifyPermission("notifyCellLocation()")) {
+            return;
+        }
+        synchronized (mRecords) {
+            mCellLocation = cellLocation;
+            for (Record r : mRecords) {
+                if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION)) {
+                    try {
+                        if (DBG_LOC) {
+                            Slog.d(TAG, "notifyCellLocation: mCellLocation=" + mCellLocation
+                                    + " r=" + r);
+                        }
+                        r.callback.onCellLocationChanged(new Bundle(cellLocation));
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    public void notifyOtaspChanged(int otaspMode) {
+        if (!checkNotifyPermission("notifyOtaspChanged()" )) {
+            return;
+        }
+        synchronized (mRecords) {
+            mOtaspMode = otaspMode;
+            for (Record r : mRecords) {
+                if ((r.events & PhoneStateListener.LISTEN_OTASP_CHANGED) != 0) {
+                    try {
+                        r.callback.onOtaspChanged(otaspMode);
+                    } catch (RemoteException ex) {
+                        mRemoveList.add(r.binder);
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
+    @Override
+    public 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 telephony.registry from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        synchronized (mRecords) {
+            final int recordCount = mRecords.size();
+            pw.println("last known state:");
+            pw.println("  mCallState=" + mCallState);
+            pw.println("  mCallIncomingNumber=" + mCallIncomingNumber);
+            pw.println("  mServiceState=" + mServiceState);
+            pw.println("  mSignalStrength=" + mSignalStrength);
+            pw.println("  mMessageWaiting=" + mMessageWaiting);
+            pw.println("  mCallForwarding=" + mCallForwarding);
+            pw.println("  mDataActivity=" + mDataActivity);
+            pw.println("  mDataConnectionState=" + mDataConnectionState);
+            pw.println("  mDataConnectionPossible=" + mDataConnectionPossible);
+            pw.println("  mDataConnectionReason=" + mDataConnectionReason);
+            pw.println("  mDataConnectionApn=" + mDataConnectionApn);
+            pw.println("  mDataConnectionLinkProperties=" + mDataConnectionLinkProperties);
+            pw.println("  mDataConnectionLinkCapabilities=" + mDataConnectionLinkCapabilities);
+            pw.println("  mCellLocation=" + mCellLocation);
+            pw.println("  mCellInfo=" + mCellInfo);
+            pw.println("registrations: count=" + recordCount);
+            for (Record r : mRecords) {
+                pw.println("  " + r.pkgForDebug + " 0x" + Integer.toHexString(r.events));
+            }
+        }
+    }
+
+    //
+    // the legacy intent broadcasting
+    //
+
+    private void broadcastServiceStateChanged(ServiceState state) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.notePhoneState(state.getState());
+        } catch (RemoteException re) {
+            // Can't do much
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+        Bundle data = new Bundle();
+        state.fillInNotifierBundle(data);
+        intent.putExtras(data);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void broadcastSignalStrengthChanged(SignalStrength signalStrength) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.notePhoneSignalStrength(signalStrength);
+        } catch (RemoteException e) {
+            /* The remote entity disappeared, we can safely ignore the exception. */
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+        Bundle data = new Bundle();
+        signalStrength.fillInNotifierBundle(data);
+        intent.putExtras(data);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void broadcastCallStateChanged(int state, String incomingNumber) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (state == TelephonyManager.CALL_STATE_IDLE) {
+                mBatteryStats.notePhoneOff();
+            } else {
+                mBatteryStats.notePhoneOn();
+            }
+        } catch (RemoteException e) {
+            /* The remote entity disappeared, we can safely ignore the exception. */
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        Intent intent = new Intent(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+        intent.putExtra(PhoneConstants.STATE_KEY,
+                DefaultPhoneNotifier.convertCallState(state).toString());
+        if (!TextUtils.isEmpty(incomingNumber)) {
+            intent.putExtra(TelephonyManager.EXTRA_INCOMING_NUMBER, incomingNumber);
+        }
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+                android.Manifest.permission.READ_PHONE_STATE);
+    }
+
+    private void broadcastDataConnectionStateChanged(int state,
+            boolean isDataConnectivityPossible,
+            String reason, String apn, String apnType, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities, boolean roaming) {
+        // Note: not reporting to the battery stats service here, because the
+        // status bar takes care of that after taking into account all of the
+        // required info.
+        Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+        intent.putExtra(PhoneConstants.STATE_KEY,
+                DefaultPhoneNotifier.convertDataState(state).toString());
+        if (!isDataConnectivityPossible) {
+            intent.putExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY, true);
+        }
+        if (reason != null) {
+            intent.putExtra(PhoneConstants.STATE_CHANGE_REASON_KEY, reason);
+        }
+        if (linkProperties != null) {
+            intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties);
+            String iface = linkProperties.getInterfaceName();
+            if (iface != null) {
+                intent.putExtra(PhoneConstants.DATA_IFACE_NAME_KEY, iface);
+            }
+        }
+        if (linkCapabilities != null) {
+            intent.putExtra(PhoneConstants.DATA_LINK_CAPABILITIES_KEY, linkCapabilities);
+        }
+        if (roaming) intent.putExtra(PhoneConstants.DATA_NETWORK_ROAMING_KEY, true);
+
+        intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
+        intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void broadcastDataConnectionFailed(String reason, String apnType) {
+        Intent intent = new Intent(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
+        intent.putExtra(PhoneConstants.FAILURE_REASON_KEY, reason);
+        intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+        mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private boolean checkNotifyPermission(String method) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        String msg = "Modify Phone State Permission Denial: " + method + " from pid="
+                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid();
+        if (DBG) Slog.w(TAG, msg);
+        return false;
+    }
+
+    private void checkListenerPermission(int events) {
+        if ((events & PhoneStateListener.LISTEN_CELL_LOCATION) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+
+        }
+
+        if ((events & PhoneStateListener.LISTEN_CELL_INFO) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+
+        }
+
+        if ((events & PHONE_STATE_PERMISSION_MASK) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.READ_PHONE_STATE, null);
+        }
+    }
+
+    private void handleRemoveListLocked() {
+        if (mRemoveList.size() > 0) {
+            for (IBinder b: mRemoveList) {
+                remove(b);
+            }
+            mRemoveList.clear();
+        }
+    }
+
+    private boolean validateEventsAndUserLocked(Record r, int events) {
+        int foregroundUser;
+        long callingIdentity = Binder.clearCallingIdentity();
+        boolean valid = false;
+        try {
+            foregroundUser = ActivityManager.getCurrentUser();
+            valid = r.callerUid ==  foregroundUser && (r.events & events) != 0;
+            if (DBG | DBG_LOC) {
+                Slog.d(TAG, "validateEventsAndUserLocked: valid=" + valid
+                        + " r.callerUid=" + r.callerUid + " foregroundUser=" + foregroundUser
+                        + " r.events=" + r.events + " events=" + events);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+        return valid;
+    }
+}
diff --git a/services/core/java/com/android/server/TextServicesManagerService.java b/services/core/java/com/android/server/TextServicesManagerService.java
new file mode 100644
index 0000000..0964767
--- /dev/null
+++ b/services/core/java/com/android/server/TextServicesManagerService.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.textservice.ISpellCheckerService;
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.IUserSwitchObserver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.textservice.SpellCheckerService;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.SpellCheckerSubtype;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class TextServicesManagerService extends ITextServicesManager.Stub {
+    private static final String TAG = TextServicesManagerService.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private final Context mContext;
+    private boolean mSystemReady;
+    private final TextServicesMonitor mMonitor;
+    private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap =
+            new HashMap<String, SpellCheckerInfo>();
+    private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>();
+    private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups =
+            new HashMap<String, SpellCheckerBindGroup>();
+    private final TextServicesSettings mSettings;
+
+    public void systemRunning() {
+        if (!mSystemReady) {
+            mSystemReady = true;
+        }
+    }
+
+    public TextServicesManagerService(Context context) {
+        mSystemReady = false;
+        mContext = context;
+        int userId = UserHandle.USER_OWNER;
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            synchronized(mSpellCheckerMap) {
+                                switchUserLocked(newUserId);
+                            }
+                            if (reply != null) {
+                                try {
+                                    reply.sendResult(null);
+                                } catch (RemoteException e) {
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+            userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+        }
+        mMonitor = new TextServicesMonitor();
+        mMonitor.register(context, null, true);
+        mSettings = new TextServicesSettings(context.getContentResolver(), userId);
+
+        // "switchUserLocked" initializes the states for the foreground user
+        switchUserLocked(userId);
+    }
+
+    private void switchUserLocked(int userId) {
+        mSettings.setCurrentUserId(userId);
+        unbindServiceLocked();
+        buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
+        SpellCheckerInfo sci = getCurrentSpellChecker(null);
+        if (sci == null) {
+            sci = findAvailSpellCheckerLocked(null, null);
+            if (sci != null) {
+                // Set the current spell checker if there is one or more spell checkers
+                // available. In this case, "sci" is the first one in the available spell
+                // checkers.
+                setCurrentSpellCheckerLocked(sci.getId());
+            }
+        }
+    }
+
+    private class TextServicesMonitor extends PackageMonitor {
+        private boolean isChangingPackagesOfCurrentUser() {
+            final int userId = getChangingUserId();
+            final boolean retval = userId == mSettings.getCurrentUserId();
+            if (DBG) {
+                Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
+            }
+            return retval;
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return;
+            }
+            synchronized (mSpellCheckerMap) {
+                buildSpellCheckerMapLocked(
+                        mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
+                // TODO: Update for each locale
+                SpellCheckerInfo sci = getCurrentSpellChecker(null);
+                // If no spell checker is enabled, just return. The user should explicitly
+                // enable the spell checker.
+                if (sci == null) return;
+                final String packageName = sci.getPackageName();
+                final int change = isPackageDisappearing(packageName);
+                if (// Package disappearing
+                        change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
+                        // Package modified
+                        || isPackageModified(packageName)) {
+                    sci = findAvailSpellCheckerLocked(null, packageName);
+                    if (sci != null) {
+                        setCurrentSpellCheckerLocked(sci.getId());
+                    }
+                }
+            }
+        }
+    }
+
+    private static void buildSpellCheckerMapLocked(Context context,
+            ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map,
+            TextServicesSettings settings) {
+        list.clear();
+        map.clear();
+        final PackageManager pm = context.getPackageManager();
+        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+                new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
+                settings.getCurrentUserId());
+        final int N = services.size();
+        for (int i = 0; i < N; ++i) {
+            final ResolveInfo ri = services.get(i);
+            final ServiceInfo si = ri.serviceInfo;
+            final ComponentName compName = new ComponentName(si.packageName, si.name);
+            if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
+                Slog.w(TAG, "Skipping text service " + compName
+                        + ": it does not require the permission "
+                        + android.Manifest.permission.BIND_TEXT_SERVICE);
+                continue;
+            }
+            if (DBG) Slog.d(TAG, "Add: " + compName);
+            try {
+                final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
+                if (sci.getSubtypeCount() <= 0) {
+                    Slog.w(TAG, "Skipping text service " + compName
+                            + ": it does not contain subtypes.");
+                    continue;
+                }
+                list.add(sci);
+                map.put(sci.getId(), sci);
+            } catch (XmlPullParserException e) {
+                Slog.w(TAG, "Unable to load the spell checker " + compName, e);
+            } catch (IOException e) {
+                Slog.w(TAG, "Unable to load the spell checker " + compName, e);
+            }
+        }
+        if (DBG) {
+            Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size());
+        }
+    }
+
+    // ---------------------------------------------------------------------------------------
+    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
+    // 1) it comes from the system process
+    // 2) the calling process' user id is identical to the current user id TSMS thinks.
+    private boolean calledFromValidUser() {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(uid);
+        if (DBG) {
+            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+                    + " calling userId = " + userId + ", foreground user id = "
+                    + mSettings.getCurrentUserId());
+            try {
+                final String[] packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
+                for (int i = 0; i < packageNames.length; ++i) {
+                    if (DBG) {
+                        Slog.d(TAG, "--- process name for "+ uid + " = " + packageNames[i]);
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+            return true;
+        } else {
+            Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
+            return false;
+        }
+    }
+
+    private boolean bindCurrentSpellCheckerService(
+            Intent service, ServiceConnection conn, int flags) {
+        if (service == null || conn == null) {
+            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
+            return false;
+        }
+        return mContext.bindServiceAsUser(service, conn, flags,
+                new UserHandle(mSettings.getCurrentUserId()));
+    }
+
+    private void unbindServiceLocked() {
+        for (SpellCheckerBindGroup scbg : mSpellCheckerBindGroups.values()) {
+            scbg.removeAll();
+        }
+        mSpellCheckerBindGroups.clear();
+    }
+
+    // TODO: find an appropriate spell checker for specified locale
+    private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
+        final int spellCheckersCount = mSpellCheckerList.size();
+        if (spellCheckersCount == 0) {
+            Slog.w(TAG, "no available spell checker services found");
+            return null;
+        }
+        if (prefPackage != null) {
+            for (int i = 0; i < spellCheckersCount; ++i) {
+                final SpellCheckerInfo sci = mSpellCheckerList.get(i);
+                if (prefPackage.equals(sci.getPackageName())) {
+                    if (DBG) {
+                        Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName());
+                    }
+                    return sci;
+                }
+            }
+        }
+        if (spellCheckersCount > 1) {
+            Slog.w(TAG, "more than one spell checker service found, picking first");
+        }
+        return mSpellCheckerList.get(0);
+    }
+
+    // TODO: Save SpellCheckerService by supported languages. Currently only one spell
+    // checker is saved.
+    @Override
+    public SpellCheckerInfo getCurrentSpellChecker(String locale) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mSpellCheckerMap) {
+            final String curSpellCheckerId = mSettings.getSelectedSpellChecker();
+            if (DBG) {
+                Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
+            }
+            if (TextUtils.isEmpty(curSpellCheckerId)) {
+                return null;
+            }
+            return mSpellCheckerMap.get(curSpellCheckerId);
+        }
+    }
+
+    // TODO: Respect allowImplicitlySelectedSubtype
+    // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
+    @Override
+    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
+            String locale, boolean allowImplicitlySelectedSubtype) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mSpellCheckerMap) {
+            final String subtypeHashCodeStr = mSettings.getSelectedSpellCheckerSubtype();
+            if (DBG) {
+                Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCodeStr);
+            }
+            final SpellCheckerInfo sci = getCurrentSpellChecker(null);
+            if (sci == null || sci.getSubtypeCount() == 0) {
+                if (DBG) {
+                    Slog.w(TAG, "Subtype not found.");
+                }
+                return null;
+            }
+            final int hashCode;
+            if (!TextUtils.isEmpty(subtypeHashCodeStr)) {
+                hashCode = Integer.valueOf(subtypeHashCodeStr);
+            } else {
+                hashCode = 0;
+            }
+            if (hashCode == 0 && !allowImplicitlySelectedSubtype) {
+                return null;
+            }
+            String candidateLocale = null;
+            if (hashCode == 0) {
+                // Spell checker language settings == "auto"
+                final InputMethodManager imm =
+                        (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
+                if (imm != null) {
+                    final InputMethodSubtype currentInputMethodSubtype =
+                            imm.getCurrentInputMethodSubtype();
+                    if (currentInputMethodSubtype != null) {
+                        final String localeString = currentInputMethodSubtype.getLocale();
+                        if (!TextUtils.isEmpty(localeString)) {
+                            // 1. Use keyboard locale if available in the spell checker
+                            candidateLocale = localeString;
+                        }
+                    }
+                }
+                if (candidateLocale == null) {
+                    // 2. Use System locale if available in the spell checker
+                    candidateLocale = mContext.getResources().getConfiguration().locale.toString();
+                }
+            }
+            SpellCheckerSubtype candidate = null;
+            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+                final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
+                if (hashCode == 0) {
+                    final String scsLocale = scs.getLocale();
+                    if (candidateLocale.equals(scsLocale)) {
+                        return scs;
+                    } else if (candidate == null) {
+                        if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
+                                && candidateLocale.startsWith(scsLocale)) {
+                            // Fall back to the applicable language
+                            candidate = scs;
+                        }
+                    }
+                } else if (scs.hashCode() == hashCode) {
+                    if (DBG) {
+                        Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
+                                + ", " + scs.getLocale());
+                    }
+                    // 3. Use the user specified spell check language
+                    return scs;
+                }
+            }
+            // 4. Fall back to the applicable language and return it if not null
+            // 5. Simply just return it even if it's null which means we could find no suitable
+            // spell check languages
+            return candidate;
+        }
+    }
+
+    @Override
+    public void getSpellCheckerService(String sciId, String locale,
+            ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
+            Bundle bundle) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        if (!mSystemReady) {
+            return;
+        }
+        if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
+            Slog.e(TAG, "getSpellCheckerService: Invalid input.");
+            return;
+        }
+        synchronized(mSpellCheckerMap) {
+            if (!mSpellCheckerMap.containsKey(sciId)) {
+                return;
+            }
+            final SpellCheckerInfo sci = mSpellCheckerMap.get(sciId);
+            final int uid = Binder.getCallingUid();
+            if (mSpellCheckerBindGroups.containsKey(sciId)) {
+                final SpellCheckerBindGroup bindGroup = mSpellCheckerBindGroups.get(sciId);
+                if (bindGroup != null) {
+                    final InternalDeathRecipient recipient =
+                            mSpellCheckerBindGroups.get(sciId).addListener(
+                                    tsListener, locale, scListener, uid, bundle);
+                    if (recipient == null) {
+                        if (DBG) {
+                            Slog.w(TAG, "Didn't create a death recipient.");
+                        }
+                        return;
+                    }
+                    if (bindGroup.mSpellChecker == null & bindGroup.mConnected) {
+                        Slog.e(TAG, "The state of the spell checker bind group is illegal.");
+                        bindGroup.removeAll();
+                    } else if (bindGroup.mSpellChecker != null) {
+                        if (DBG) {
+                            Slog.w(TAG, "Existing bind found. Return a spell checker session now. "
+                                    + "Listeners count = " + bindGroup.mListeners.size());
+                        }
+                        try {
+                            final ISpellCheckerSession session =
+                                    bindGroup.mSpellChecker.getISpellCheckerSession(
+                                            recipient.mScLocale, recipient.mScListener, bundle);
+                            if (session != null) {
+                                tsListener.onServiceConnected(session);
+                                return;
+                            } else {
+                                if (DBG) {
+                                    Slog.w(TAG, "Existing bind already expired. ");
+                                }
+                                bindGroup.removeAll();
+                            }
+                        } catch (RemoteException e) {
+                            Slog.e(TAG, "Exception in getting spell checker session: " + e);
+                            bindGroup.removeAll();
+                        }
+                    }
+                }
+            }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                startSpellCheckerServiceInnerLocked(
+                        sci, locale, tsListener, scListener, uid, bundle);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+        return;
+    }
+
+    @Override
+    public boolean isSpellCheckerEnabled() {
+        if (!calledFromValidUser()) {
+            return false;
+        }
+        synchronized(mSpellCheckerMap) {
+            return isSpellCheckerEnabledLocked();
+        }
+    }
+
+    private void startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, String locale,
+            ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
+            int uid, Bundle bundle) {
+        if (DBG) {
+            Slog.w(TAG, "Start spell checker session inner locked.");
+        }
+        final String sciId = info.getId();
+        final InternalServiceConnection connection = new InternalServiceConnection(
+                sciId, locale, bundle);
+        final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
+        serviceIntent.setComponent(info.getComponent());
+        if (DBG) {
+            Slog.w(TAG, "bind service: " + info.getId());
+        }
+        if (!bindCurrentSpellCheckerService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+            Slog.e(TAG, "Failed to get a spell checker service.");
+            return;
+        }
+        final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
+                connection, tsListener, locale, scListener, uid, bundle);
+        mSpellCheckerBindGroups.put(sciId, group);
+    }
+
+    @Override
+    public SpellCheckerInfo[] getEnabledSpellCheckers() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        if (DBG) {
+            Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size());
+            for (int i = 0; i < mSpellCheckerList.size(); ++i) {
+                Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName());
+            }
+        }
+        return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]);
+    }
+
+    @Override
+    public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        if (DBG) {
+            Slog.d(TAG, "FinishSpellCheckerService");
+        }
+        synchronized(mSpellCheckerMap) {
+            final ArrayList<SpellCheckerBindGroup> removeList =
+                    new ArrayList<SpellCheckerBindGroup>();
+            for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
+                if (group == null) continue;
+                // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
+                removeList.add(group);
+            }
+            final int removeSize = removeList.size();
+            for (int i = 0; i < removeSize; ++i) {
+                removeList.get(i).removeListener(listener);
+            }
+        }
+    }
+
+    @Override
+    public void setCurrentSpellChecker(String locale, String sciId) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized(mSpellCheckerMap) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Requires permission "
+                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
+            }
+            setCurrentSpellCheckerLocked(sciId);
+        }
+    }
+
+    @Override
+    public void setCurrentSpellCheckerSubtype(String locale, int hashCode) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized(mSpellCheckerMap) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Requires permission "
+                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
+            }
+            setCurrentSpellCheckerSubtypeLocked(hashCode);
+        }
+    }
+
+    @Override
+    public void setSpellCheckerEnabled(boolean enabled) {
+        if (!calledFromValidUser()) {
+            return;
+        }
+        synchronized(mSpellCheckerMap) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException(
+                        "Requires permission "
+                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
+            }
+            setSpellCheckerEnabledLocked(enabled);
+        }
+    }
+
+    private void setCurrentSpellCheckerLocked(String sciId) {
+        if (DBG) {
+            Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
+        }
+        if (TextUtils.isEmpty(sciId) || !mSpellCheckerMap.containsKey(sciId)) return;
+        final SpellCheckerInfo currentSci = getCurrentSpellChecker(null);
+        if (currentSci != null && currentSci.getId().equals(sciId)) {
+            // Do nothing if the current spell checker is same as new spell checker.
+            return;
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSettings.putSelectedSpellChecker(sciId);
+            setCurrentSpellCheckerSubtypeLocked(0);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setCurrentSpellCheckerSubtypeLocked(int hashCode) {
+        if (DBG) {
+            Slog.w(TAG, "setCurrentSpellCheckerSubtype: " + hashCode);
+        }
+        final SpellCheckerInfo sci = getCurrentSpellChecker(null);
+        int tempHashCode = 0;
+        for (int i = 0; sci != null && i < sci.getSubtypeCount(); ++i) {
+            if(sci.getSubtypeAt(i).hashCode() == hashCode) {
+                tempHashCode = hashCode;
+                break;
+            }
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSettings.putSelectedSpellCheckerSubtype(tempHashCode);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setSpellCheckerEnabledLocked(boolean enabled) {
+        if (DBG) {
+            Slog.w(TAG, "setSpellCheckerEnabled: " + enabled);
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mSettings.setSpellCheckerEnabled(enabled);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private boolean isSpellCheckerEnabledLocked() {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            final boolean retval = mSettings.isSpellCheckerEnabled();
+            if (DBG) {
+                Slog.w(TAG, "getSpellCheckerEnabled: " + retval);
+            }
+            return retval;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @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 TextServicesManagerService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized(mSpellCheckerMap) {
+            pw.println("Current Text Services Manager state:");
+            pw.println("  Spell Checker Map:");
+            for (Map.Entry<String, SpellCheckerInfo> ent : mSpellCheckerMap.entrySet()) {
+                pw.print("    "); pw.print(ent.getKey()); pw.println(":");
+                SpellCheckerInfo info = ent.getValue();
+                pw.print("      "); pw.print("id="); pw.println(info.getId());
+                pw.print("      "); pw.print("comp=");
+                        pw.println(info.getComponent().toShortString());
+                int NS = info.getSubtypeCount();
+                for (int i=0; i<NS; i++) {
+                    SpellCheckerSubtype st = info.getSubtypeAt(i);
+                    pw.print("      "); pw.print("Subtype #"); pw.print(i); pw.println(":");
+                    pw.print("        "); pw.print("locale="); pw.println(st.getLocale());
+                    pw.print("        "); pw.print("extraValue=");
+                            pw.println(st.getExtraValue());
+                }
+            }
+            pw.println("");
+            pw.println("  Spell Checker Bind Groups:");
+            for (Map.Entry<String, SpellCheckerBindGroup> ent
+                    : mSpellCheckerBindGroups.entrySet()) {
+                SpellCheckerBindGroup grp = ent.getValue();
+                pw.print("    "); pw.print(ent.getKey()); pw.print(" ");
+                        pw.print(grp); pw.println(":");
+                pw.print("      "); pw.print("mInternalConnection=");
+                        pw.println(grp.mInternalConnection);
+                pw.print("      "); pw.print("mSpellChecker=");
+                        pw.println(grp.mSpellChecker);
+                pw.print("      "); pw.print("mBound="); pw.print(grp.mBound);
+                        pw.print(" mConnected="); pw.println(grp.mConnected);
+                int NL = grp.mListeners.size();
+                for (int i=0; i<NL; i++) {
+                    InternalDeathRecipient listener = grp.mListeners.get(i);
+                    pw.print("      "); pw.print("Listener #"); pw.print(i); pw.println(":");
+                    pw.print("        "); pw.print("mTsListener=");
+                            pw.println(listener.mTsListener);
+                    pw.print("        "); pw.print("mScListener=");
+                            pw.println(listener.mScListener);
+                    pw.print("        "); pw.print("mGroup=");
+                            pw.println(listener.mGroup);
+                    pw.print("        "); pw.print("mScLocale=");
+                            pw.print(listener.mScLocale);
+                            pw.print(" mUid="); pw.println(listener.mUid);
+                }
+            }
+        }
+    }
+
+    // SpellCheckerBindGroup contains active text service session listeners.
+    // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
+    // mSpellCheckerBindGroups
+    private class SpellCheckerBindGroup {
+        private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
+        private final InternalServiceConnection mInternalConnection;
+        private final CopyOnWriteArrayList<InternalDeathRecipient> mListeners =
+                new CopyOnWriteArrayList<InternalDeathRecipient>();
+        public boolean mBound;
+        public ISpellCheckerService mSpellChecker;
+        public boolean mConnected;
+
+        public SpellCheckerBindGroup(InternalServiceConnection connection,
+                ITextServicesSessionListener listener, String locale,
+                ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
+            mInternalConnection = connection;
+            mBound = true;
+            mConnected = false;
+            addListener(listener, locale, scListener, uid, bundle);
+        }
+
+        public void onServiceConnected(ISpellCheckerService spellChecker) {
+            if (DBG) {
+                Slog.d(TAG, "onServiceConnected");
+            }
+
+            for (InternalDeathRecipient listener : mListeners) {
+                try {
+                    final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
+                            listener.mScLocale, listener.mScListener, listener.mBundle);
+                    synchronized(mSpellCheckerMap) {
+                        if (mListeners.contains(listener)) {
+                            listener.mTsListener.onServiceConnected(session);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Exception in getting the spell checker session."
+                            + "Reconnect to the spellchecker. ", e);
+                    removeAll();
+                    return;
+                }
+            }
+            synchronized(mSpellCheckerMap) {
+                mSpellChecker = spellChecker;
+                mConnected = true;
+            }
+        }
+
+        public InternalDeathRecipient addListener(ITextServicesSessionListener tsListener,
+                String locale, ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
+            if (DBG) {
+                Slog.d(TAG, "addListener: " + locale);
+            }
+            InternalDeathRecipient recipient = null;
+            synchronized(mSpellCheckerMap) {
+                try {
+                    final int size = mListeners.size();
+                    for (int i = 0; i < size; ++i) {
+                        if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
+                            // do not add the lister if the group already contains this.
+                            return null;
+                        }
+                    }
+                    recipient = new InternalDeathRecipient(
+                            this, tsListener, locale, scListener, uid, bundle);
+                    scListener.asBinder().linkToDeath(recipient, 0);
+                    mListeners.add(recipient);
+                } catch(RemoteException e) {
+                    // do nothing
+                }
+                cleanLocked();
+            }
+            return recipient;
+        }
+
+        public void removeListener(ISpellCheckerSessionListener listener) {
+            if (DBG) {
+                Slog.w(TAG, "remove listener: " + listener.hashCode());
+            }
+            synchronized(mSpellCheckerMap) {
+                final int size = mListeners.size();
+                final ArrayList<InternalDeathRecipient> removeList =
+                        new ArrayList<InternalDeathRecipient>();
+                for (int i = 0; i < size; ++i) {
+                    final InternalDeathRecipient tempRecipient = mListeners.get(i);
+                    if(tempRecipient.hasSpellCheckerListener(listener)) {
+                        if (DBG) {
+                            Slog.w(TAG, "found existing listener.");
+                        }
+                        removeList.add(tempRecipient);
+                    }
+                }
+                final int removeSize = removeList.size();
+                for (int i = 0; i < removeSize; ++i) {
+                    if (DBG) {
+                        Slog.w(TAG, "Remove " + removeList.get(i));
+                    }
+                    final InternalDeathRecipient idr = removeList.get(i);
+                    idr.mScListener.asBinder().unlinkToDeath(idr, 0);
+                    mListeners.remove(idr);
+                }
+                cleanLocked();
+            }
+        }
+
+        // cleanLocked may remove elements from mSpellCheckerBindGroups
+        private void cleanLocked() {
+            if (DBG) {
+                Slog.d(TAG, "cleanLocked");
+            }
+            // If there are no more active listeners, clean up.  Only do this
+            // once.
+            if (mBound && mListeners.isEmpty()) {
+                mBound = false;
+                final String sciId = mInternalConnection.mSciId;
+                SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
+                if (cur == this) {
+                    if (DBG) {
+                        Slog.d(TAG, "Remove bind group.");
+                    }
+                    mSpellCheckerBindGroups.remove(sciId);
+                }
+                mContext.unbindService(mInternalConnection);
+            }
+        }
+
+        public void removeAll() {
+            Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
+            synchronized(mSpellCheckerMap) {
+                final int size = mListeners.size();
+                for (int i = 0; i < size; ++i) {
+                    final InternalDeathRecipient idr = mListeners.get(i);
+                    idr.mScListener.asBinder().unlinkToDeath(idr, 0);
+                }
+                mListeners.clear();
+                cleanLocked();
+            }
+        }
+    }
+
+    private class InternalServiceConnection implements ServiceConnection {
+        private final String mSciId;
+        private final String mLocale;
+        private final Bundle mBundle;
+        public InternalServiceConnection(
+                String id, String locale, Bundle bundle) {
+            mSciId = id;
+            mLocale = locale;
+            mBundle = bundle;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized(mSpellCheckerMap) {
+                onServiceConnectedInnerLocked(name, service);
+            }
+        }
+
+        private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) {
+            if (DBG) {
+                Slog.w(TAG, "onServiceConnected: " + name);
+            }
+            final ISpellCheckerService spellChecker =
+                    ISpellCheckerService.Stub.asInterface(service);
+            final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
+            if (group != null && this == group.mInternalConnection) {
+                group.onServiceConnected(spellChecker);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized(mSpellCheckerMap) {
+                final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
+                if (group != null && this == group.mInternalConnection) {
+                    mSpellCheckerBindGroups.remove(mSciId);
+                }
+            }
+        }
+    }
+
+    private class InternalDeathRecipient implements IBinder.DeathRecipient {
+        public final ITextServicesSessionListener mTsListener;
+        public final ISpellCheckerSessionListener mScListener;
+        public final String mScLocale;
+        private final SpellCheckerBindGroup mGroup;
+        public final int mUid;
+        public final Bundle mBundle;
+        public InternalDeathRecipient(SpellCheckerBindGroup group,
+                ITextServicesSessionListener tsListener, String scLocale,
+                ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
+            mTsListener = tsListener;
+            mScListener = scListener;
+            mScLocale = scLocale;
+            mGroup = group;
+            mUid = uid;
+            mBundle = bundle;
+        }
+
+        public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
+            return listener.asBinder().equals(mScListener.asBinder());
+        }
+
+        @Override
+        public void binderDied() {
+            mGroup.removeListener(mScListener);
+        }
+    }
+
+    private static class TextServicesSettings {
+        private final ContentResolver mResolver;
+        private int mCurrentUserId;
+        public TextServicesSettings(ContentResolver resolver, int userId) {
+            mResolver = resolver;
+            mCurrentUserId = userId;
+        }
+
+        public void setCurrentUserId(int userId) {
+            if (DBG) {
+                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+                        + userId + ", new ime = " + getSelectedSpellChecker());
+            }
+            // TSMS settings are kept per user, so keep track of current user
+            mCurrentUserId = userId;
+        }
+
+        public int getCurrentUserId() {
+            return mCurrentUserId;
+        }
+
+        public void putSelectedSpellChecker(String sciId) {
+            Settings.Secure.putStringForUser(mResolver,
+                    Settings.Secure.SELECTED_SPELL_CHECKER, sciId, mCurrentUserId);
+        }
+
+        public void putSelectedSpellCheckerSubtype(int hashCode) {
+            Settings.Secure.putStringForUser(mResolver,
+                    Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(hashCode),
+                    mCurrentUserId);
+        }
+
+        public void setSpellCheckerEnabled(boolean enabled) {
+            Settings.Secure.putIntForUser(mResolver,
+                    Settings.Secure.SPELL_CHECKER_ENABLED, enabled ? 1 : 0, mCurrentUserId);
+        }
+
+        public String getSelectedSpellChecker() {
+            return Settings.Secure.getStringForUser(mResolver,
+                    Settings.Secure.SELECTED_SPELL_CHECKER, mCurrentUserId);
+        }
+
+        public String getSelectedSpellCheckerSubtype() {
+            return Settings.Secure.getStringForUser(mResolver,
+                    Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, mCurrentUserId);
+        }
+
+        public boolean isSpellCheckerEnabled() {
+            return Settings.Secure.getIntForUser(mResolver,
+                    Settings.Secure.SPELL_CHECKER_ENABLED, 1, mCurrentUserId) == 1;
+        }
+    }
+
+    // ----------------------------------------------------------------------
+    // Utilities for debug
+    private static String getStackTrace() {
+        final StringBuilder sb = new StringBuilder();
+        try {
+            throw new RuntimeException();
+        } catch (RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
+            // Start at 1 because the first frame is here and we don't care about it
+            for (int j = 1; j < frames.length; ++j) {
+                sb.append(frames[j].toString() + "\n");
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/TwilightCalculator.java b/services/core/java/com/android/server/TwilightCalculator.java
new file mode 100644
index 0000000..a5c93b5
--- /dev/null
+++ b/services/core/java/com/android/server/TwilightCalculator.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2010 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.text.format.DateUtils;
+import android.util.FloatMath;
+
+/** @hide */
+public class TwilightCalculator {
+
+    /** Value of {@link #mState} if it is currently day */
+    public static final int DAY = 0;
+
+    /** Value of {@link #mState} if it is currently night */
+    public static final int NIGHT = 1;
+
+    private static final float DEGREES_TO_RADIANS = (float) (Math.PI / 180.0f);
+
+    // element for calculating solar transit.
+    private static final float J0 = 0.0009f;
+
+    // correction for civil twilight
+    private static final float ALTIDUTE_CORRECTION_CIVIL_TWILIGHT = -0.104719755f;
+
+    // coefficients for calculating Equation of Center.
+    private static final float C1 = 0.0334196f;
+    private static final float C2 = 0.000349066f;
+    private static final float C3 = 0.000005236f;
+
+    private static final float OBLIQUITY = 0.40927971f;
+
+    // Java time on Jan 1, 2000 12:00 UTC.
+    private static final long UTC_2000 = 946728000000L;
+
+    /**
+     * Time of sunset (civil twilight) in milliseconds or -1 in the case the day
+     * or night never ends.
+     */
+    public long mSunset;
+
+    /**
+     * Time of sunrise (civil twilight) in milliseconds or -1 in the case the
+     * day or night never ends.
+     */
+    public long mSunrise;
+
+    /** Current state */
+    public int mState;
+
+    /**
+     * calculates the civil twilight bases on time and geo-coordinates.
+     *
+     * @param time time in milliseconds.
+     * @param latiude latitude in degrees.
+     * @param longitude latitude in degrees.
+     */
+    public void calculateTwilight(long time, double latiude, double longitude) {
+        final float daysSince2000 = (float) (time - UTC_2000) / DateUtils.DAY_IN_MILLIS;
+
+        // mean anomaly
+        final float meanAnomaly = 6.240059968f + daysSince2000 * 0.01720197f;
+
+        // true anomaly
+        final float trueAnomaly = meanAnomaly + C1 * FloatMath.sin(meanAnomaly) + C2
+                * FloatMath.sin(2 * meanAnomaly) + C3 * FloatMath.sin(3 * meanAnomaly);
+
+        // ecliptic longitude
+        final float solarLng = trueAnomaly + 1.796593063f + (float) Math.PI;
+
+        // solar transit in days since 2000
+        final double arcLongitude = -longitude / 360;
+        float n = Math.round(daysSince2000 - J0 - arcLongitude);
+        double solarTransitJ2000 = n + J0 + arcLongitude + 0.0053f * FloatMath.sin(meanAnomaly)
+                + -0.0069f * FloatMath.sin(2 * solarLng);
+
+        // declination of sun
+        double solarDec = Math.asin(FloatMath.sin(solarLng) * FloatMath.sin(OBLIQUITY));
+
+        final double latRad = latiude * DEGREES_TO_RADIANS;
+
+        double cosHourAngle = (FloatMath.sin(ALTIDUTE_CORRECTION_CIVIL_TWILIGHT) - Math.sin(latRad)
+                * Math.sin(solarDec)) / (Math.cos(latRad) * Math.cos(solarDec));
+        // The day or night never ends for the given date and location, if this value is out of
+        // range.
+        if (cosHourAngle >= 1) {
+            mState = NIGHT;
+            mSunset = -1;
+            mSunrise = -1;
+            return;
+        } else if (cosHourAngle <= -1) {
+            mState = DAY;
+            mSunset = -1;
+            mSunrise = -1;
+            return;
+        }
+
+        float hourAngle = (float) (Math.acos(cosHourAngle) / (2 * Math.PI));
+
+        mSunset = Math.round((solarTransitJ2000 + hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
+        mSunrise = Math.round((solarTransitJ2000 - hourAngle) * DateUtils.DAY_IN_MILLIS) + UTC_2000;
+
+        if (mSunrise < time && mSunset > time) {
+            mState = DAY;
+        } else {
+            mState = NIGHT;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
new file mode 100644
index 0000000..de912dc
--- /dev/null
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IUiModeManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.dreams.Sandman;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.R;
+import com.android.internal.app.DisableCarModeActivity;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+final class UiModeManagerService extends SystemService {
+    private static final String TAG = UiModeManager.class.getSimpleName();
+    private static final boolean LOG = false;
+
+    // Enable launching of applications when entering the dock.
+    private static final boolean ENABLE_LAUNCH_CAR_DOCK_APP = true;
+    private static final boolean ENABLE_LAUNCH_DESK_DOCK_APP = true;
+
+    final Object mLock = new Object();
+    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    int mNightMode = UiModeManager.MODE_NIGHT_NO;
+
+    private boolean mCarModeEnabled = false;
+    private boolean mCharging = false;
+    private int mDefaultUiModeType;
+    private boolean mCarModeKeepsScreenOn;
+    private boolean mDeskModeKeepsScreenOn;
+    private boolean mTelevision;
+    private boolean mComputedNightMode;
+
+    int mCurUiMode = 0;
+    private int mSetUiMode = 0;
+    private boolean mHoldingConfiguration = false;
+
+    private Configuration mConfiguration = new Configuration();
+    boolean mSystemReady;
+
+    private final Handler mHandler = new Handler();
+
+    private TwilightManager mTwilightManager;
+    private NotificationManager mNotificationManager;
+    private StatusBarManager mStatusBarManager;
+
+    private PowerManager.WakeLock mWakeLock;
+
+    private static Intent buildHomeIntent(String category) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(category);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        return intent;
+    }
+
+    // The broadcast receiver which receives the result of the ordered broadcast sent when
+    // the dock state changes. The original ordered broadcast is sent with an initial result
+    // code of RESULT_OK. If any of the registered broadcast receivers changes this value, e.g.,
+    // to RESULT_CANCELED, then the intent to start a dock app will not be sent.
+    private final BroadcastReceiver mResultReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getResultCode() != Activity.RESULT_OK) {
+                if (LOG) {
+                    Slog.v(TAG, "Handling broadcast result for action " + intent.getAction()
+                            + ": canceled: " + getResultCode());
+                }
+                return;
+            }
+
+            final int enableFlags = intent.getIntExtra("enableFlags", 0);
+            final int disableFlags = intent.getIntExtra("disableFlags", 0);
+            synchronized (mLock) {
+                updateAfterBroadcastLocked(intent.getAction(), enableFlags, disableFlags);
+            }
+        }
+    };
+
+    private final BroadcastReceiver mDockModeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                    Intent.EXTRA_DOCK_STATE_UNDOCKED);
+            updateDockState(state);
+        }
+    };
+
+    private final BroadcastReceiver mBatteryReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mCharging = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
+            synchronized (mLock) {
+                if (mSystemReady) {
+                    updateLocked(0, 0);
+                }
+            }
+        }
+    };
+
+    private final TwilightListener mTwilightListener = new TwilightListener() {
+        @Override
+        public void onTwilightStateChanged() {
+            updateTwilight();
+        }
+    };
+
+    @Override
+    public void onStart() {
+        final Context context = getContext();
+        mTwilightManager = getLocalService(TwilightManager.class);
+        final PowerManager powerManager =
+                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
+
+        context.registerReceiver(mDockModeReceiver,
+                new IntentFilter(Intent.ACTION_DOCK_EVENT));
+        context.registerReceiver(mBatteryReceiver,
+                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+        mConfiguration.setToDefaults();
+
+        mDefaultUiModeType = context.getResources().getInteger(
+                com.android.internal.R.integer.config_defaultUiModeType);
+        mCarModeKeepsScreenOn = (context.getResources().getInteger(
+                com.android.internal.R.integer.config_carDockKeepsScreenOn) == 1);
+        mDeskModeKeepsScreenOn = (context.getResources().getInteger(
+                com.android.internal.R.integer.config_deskDockKeepsScreenOn) == 1);
+        mTelevision = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEVISION);
+
+        mNightMode = Settings.Secure.getInt(context.getContentResolver(),
+                Settings.Secure.UI_NIGHT_MODE, UiModeManager.MODE_NIGHT_AUTO);
+
+        mTwilightManager.registerListener(mTwilightListener, mHandler);
+
+        publishBinderService(Context.UI_MODE_SERVICE, mService);
+    }
+
+    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);
+            }
+        }
+
+        @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);
+            }
+        }
+
+        @Override
+        public int getCurrentModeType() {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    return mCurUiMode & Configuration.UI_MODE_TYPE_MASK;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @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) {
+            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());
+        }
+    }
+
+    @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);
+            }
+        }
+    }
+
+    boolean isDoingNightModeLocked() {
+        return mCarModeEnabled || mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    }
+
+    void setCarModeLocked(boolean enabled) {
+        if (mCarModeEnabled != enabled) {
+            mCarModeEnabled = enabled;
+        }
+    }
+
+    private void updateDockState(int newState) {
+        synchronized (mLock) {
+            if (newState != mDockState) {
+                mDockState = newState;
+                setCarModeLocked(mDockState == Intent.EXTRA_DOCK_STATE_CAR);
+                if (mSystemReady) {
+                    updateLocked(UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME, 0);
+                }
+            }
+        }
+    }
+
+    private static boolean isDeskDockState(int state) {
+        switch (state) {
+            case Intent.EXTRA_DOCK_STATE_DESK:
+            case Intent.EXTRA_DOCK_STATE_LE_DESK:
+            case Intent.EXTRA_DOCK_STATE_HE_DESK:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void updateConfigurationLocked() {
+        int uiMode = mTelevision ? Configuration.UI_MODE_TYPE_TELEVISION : mDefaultUiModeType;
+        if (mCarModeEnabled) {
+            uiMode = Configuration.UI_MODE_TYPE_CAR;
+        } else if (isDeskDockState(mDockState)) {
+            uiMode = Configuration.UI_MODE_TYPE_DESK;
+        }
+        if (mCarModeEnabled) {
+            if (mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+                updateComputedNightModeLocked();
+                uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
+                        : Configuration.UI_MODE_NIGHT_NO;
+            } else {
+                uiMode |= mNightMode << 4;
+            }
+        } else {
+            // Disabling the car mode clears the night mode.
+            uiMode = (uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | Configuration.UI_MODE_NIGHT_NO;
+        }
+
+        if (LOG) {
+            Slog.d(TAG,
+                "updateConfigurationLocked: mDockState=" + mDockState
+                + "; mCarMode=" + mCarModeEnabled
+                + "; mNightMode=" + mNightMode
+                + "; uiMode=" + uiMode);
+        }
+
+        mCurUiMode = uiMode;
+        if (!mHoldingConfiguration) {
+            mConfiguration.uiMode = uiMode;
+        }
+    }
+
+    private void sendConfigurationLocked() {
+        if (mSetUiMode != mConfiguration.uiMode) {
+            mSetUiMode = mConfiguration.uiMode;
+
+            try {
+                ActivityManagerNative.getDefault().updateConfiguration(mConfiguration);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failure communicating with activity manager", e);
+            }
+        }
+    }
+
+    void updateLocked(int enableFlags, int disableFlags) {
+        String action = null;
+        String oldAction = null;
+        if (mLastBroadcastState == Intent.EXTRA_DOCK_STATE_CAR) {
+            adjustStatusBarCarModeLocked();
+            oldAction = UiModeManager.ACTION_EXIT_CAR_MODE;
+        } else if (isDeskDockState(mLastBroadcastState)) {
+            oldAction = UiModeManager.ACTION_EXIT_DESK_MODE;
+        }
+
+        if (mCarModeEnabled) {
+            if (mLastBroadcastState != Intent.EXTRA_DOCK_STATE_CAR) {
+                adjustStatusBarCarModeLocked();
+
+                if (oldAction != null) {
+                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+                }
+                mLastBroadcastState = Intent.EXTRA_DOCK_STATE_CAR;
+                action = UiModeManager.ACTION_ENTER_CAR_MODE;
+            }
+        } else if (isDeskDockState(mDockState)) {
+            if (!isDeskDockState(mLastBroadcastState)) {
+                if (oldAction != null) {
+                    getContext().sendBroadcastAsUser(new Intent(oldAction), UserHandle.ALL);
+                }
+                mLastBroadcastState = mDockState;
+                action = UiModeManager.ACTION_ENTER_DESK_MODE;
+            }
+        } else {
+            mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+            action = oldAction;
+        }
+
+        if (action != null) {
+            if (LOG) {
+                Slog.v(TAG, String.format(
+                    "updateLocked: preparing broadcast: action=%s enable=0x%08x disable=0x%08x",
+                    action, enableFlags, disableFlags));
+            }
+
+            // Send the ordered broadcast; the result receiver will receive after all
+            // broadcasts have been sent. If any broadcast receiver changes the result
+            // code from the initial value of RESULT_OK, then the result receiver will
+            // not launch the corresponding dock application. This gives apps a chance
+            // to override the behavior and stay in their app even when the device is
+            // placed into a dock.
+            Intent intent = new Intent(action);
+            intent.putExtra("enableFlags", enableFlags);
+            intent.putExtra("disableFlags", disableFlags);
+            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
+            // to hold off on doing a configuration change until we have finished
+            // the broadcast and started the home activity.
+            mHoldingConfiguration = true;
+            updateConfigurationLocked();
+        } else {
+            String category = null;
+            if (mCarModeEnabled) {
+                if (ENABLE_LAUNCH_CAR_DOCK_APP
+                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+                    category = Intent.CATEGORY_CAR_DOCK;
+                }
+            } else if (isDeskDockState(mDockState)) {
+                if (ENABLE_LAUNCH_DESK_DOCK_APP
+                        && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+                    category = Intent.CATEGORY_DESK_DOCK;
+                }
+            } else {
+                if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
+                    category = Intent.CATEGORY_HOME;
+                }
+            }
+
+            if (LOG) {
+                Slog.v(TAG, "updateLocked: null action, mDockState="
+                        + mDockState +", category=" + category);
+            }
+
+            sendConfigurationAndStartDreamOrDockAppLocked(category);
+        }
+
+        // keep screen on when charging and in car mode
+        boolean keepScreenOn = mCharging &&
+                ((mCarModeEnabled && mCarModeKeepsScreenOn) ||
+                 (mCurUiMode == Configuration.UI_MODE_TYPE_DESK && mDeskModeKeepsScreenOn));
+        if (keepScreenOn != mWakeLock.isHeld()) {
+            if (keepScreenOn) {
+                mWakeLock.acquire();
+            } else {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    private void updateAfterBroadcastLocked(String action, int enableFlags, int disableFlags) {
+        // Launch a dock activity
+        String category = null;
+        if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
+            // Only launch car home when car mode is enabled and the caller
+            // has asked us to switch to it.
+            if (ENABLE_LAUNCH_CAR_DOCK_APP
+                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+                category = Intent.CATEGORY_CAR_DOCK;
+            }
+        } else if (UiModeManager.ACTION_ENTER_DESK_MODE.equals(action)) {
+            // Only launch car home when desk mode is enabled and the caller
+            // has asked us to switch to it.  Currently re-using the car
+            // mode flag since we don't have a formal API for "desk mode".
+            if (ENABLE_LAUNCH_DESK_DOCK_APP
+                    && (enableFlags & UiModeManager.ENABLE_CAR_MODE_GO_CAR_HOME) != 0) {
+                category = Intent.CATEGORY_DESK_DOCK;
+            }
+        } else {
+            // Launch the standard home app if requested.
+            if ((disableFlags & UiModeManager.DISABLE_CAR_MODE_GO_HOME) != 0) {
+                category = Intent.CATEGORY_HOME;
+            }
+        }
+
+        if (LOG) {
+            Slog.v(TAG, String.format(
+                "Handling broadcast result for action %s: enable=0x%08x, disable=0x%08x, "
+                    + "category=%s",
+                action, enableFlags, disableFlags, category));
+        }
+
+        sendConfigurationAndStartDreamOrDockAppLocked(category);
+    }
+
+    private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
+        // Update the configuration but don't send it yet.
+        mHoldingConfiguration = false;
+        updateConfigurationLocked();
+
+        // Start the dock app, if there is one.
+        boolean dockAppStarted = false;
+        if (category != null) {
+            // Now we are going to be careful about switching the
+            // configuration and starting the activity -- we need to
+            // do this in a specific order under control of the
+            // activity manager, to do it cleanly.  So compute the
+            // new config, but don't set it yet, and let the
+            // activity manager take care of both the start and config
+            // change.
+            Intent homeIntent = buildHomeIntent(category);
+            if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
+                try {
+                    int result = ActivityManagerNative.getDefault().startActivityWithConfig(
+                            null, null, homeIntent, null, null, null, 0, 0,
+                            mConfiguration, null, UserHandle.USER_CURRENT);
+                    if (result >= ActivityManager.START_SUCCESS) {
+                        dockAppStarted = true;
+                    } else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
+                        Slog.e(TAG, "Could not start dock app: " + homeIntent
+                                + ", startActivityWithConfig result " + result);
+                    }
+                } catch (RemoteException ex) {
+                    Slog.e(TAG, "Could not start dock app: " + homeIntent, ex);
+                }
+            }
+        }
+
+        // Send the new configuration.
+        sendConfigurationLocked();
+
+        // If we did not start a dock app, then start dreaming if supported.
+        if (category != null && !dockAppStarted) {
+            Sandman.startDreamWhenDockedIfAppropriate(getContext());
+        }
+    }
+
+    private void adjustStatusBarCarModeLocked() {
+        final Context context = getContext();
+        if (mStatusBarManager == null) {
+            mStatusBarManager = (StatusBarManager)
+                    context.getSystemService(Context.STATUS_BAR_SERVICE);
+        }
+
+        // Fear not: StatusBarManagerService manages a list of requests to disable
+        // features of the status bar; these are ORed together to form the
+        // active disabled list. So if (for example) the device is locked and
+        // the status bar should be totally disabled, the calls below will
+        // have no effect until the device is unlocked.
+        if (mStatusBarManager != null) {
+            mStatusBarManager.disable(mCarModeEnabled
+                ? StatusBarManager.DISABLE_NOTIFICATION_TICKER
+                : StatusBarManager.DISABLE_NONE);
+        }
+
+        if (mNotificationManager == null) {
+            mNotificationManager = (NotificationManager)
+                    context.getSystemService(Context.NOTIFICATION_SERVICE);
+        }
+
+        if (mNotificationManager != null) {
+            if (mCarModeEnabled) {
+                Intent carModeOffIntent = new Intent(context, DisableCarModeActivity.class);
+
+                Notification n = new Notification();
+                n.icon = R.drawable.stat_notify_car_mode;
+                n.defaults = Notification.DEFAULT_LIGHTS;
+                n.flags = Notification.FLAG_ONGOING_EVENT;
+                n.when = 0;
+                n.setLatestEventInfo(
+                        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);
+            } else {
+                mNotificationManager.cancelAsUser(null,
+                        R.string.car_mode_disable_notification_title, UserHandle.ALL);
+            }
+        }
+    }
+
+    void updateTwilight() {
+        synchronized (mLock) {
+            if (isDoingNightModeLocked() && mNightMode == UiModeManager.MODE_NIGHT_AUTO) {
+                updateComputedNightModeLocked();
+                updateLocked(0, 0);
+            }
+        }
+    }
+
+    private void updateComputedNightModeLocked() {
+        TwilightState state = mTwilightManager.getCurrentState();
+        if (state != null) {
+            mComputedNightMode = state.isNight();
+        }
+    }
+
+
+}
diff --git a/services/core/java/com/android/server/UiThread.java b/services/core/java/com/android/server/UiThread.java
new file mode 100644
index 0000000..60d73aa
--- /dev/null
+++ b/services/core/java/com/android/server/UiThread.java
@@ -0,0 +1,71 @@
+/*
+ * 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.os.Handler;
+import android.os.HandlerThread;
+import android.os.StrictMode;
+import android.util.Slog;
+
+/**
+ * Shared singleton thread for showing UI.  This is a foreground thread, and in
+ * additional should not have operations that can take more than a few ms scheduled
+ * on it to avoid UI jank.
+ */
+public final class UiThread extends HandlerThread {
+    private static UiThread sInstance;
+    private static Handler sHandler;
+
+    private UiThread() {
+        super("android.ui", android.os.Process.THREAD_PRIORITY_FOREGROUND);
+    }
+
+    private static void ensureThreadLocked() {
+        if (sInstance == null) {
+            sInstance = new UiThread();
+            sInstance.start();
+            sHandler = new Handler(sInstance.getLooper());
+            sHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    //Looper.myLooper().setMessageLogging(new LogPrinter(
+                    //        Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM));
+                    android.os.Process.setCanSelfBackground(false);
+
+                    // For debug builds, log event loop stalls to dropbox for analysis.
+                    if (StrictMode.conditionallyEnableDebugLogging()) {
+                        Slog.i("UiThread", "Enabled StrictMode logging for UI thread");
+                    }
+                }
+            });
+        }
+    }
+
+    public static UiThread get() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sInstance;
+        }
+    }
+
+    public static Handler getHandler() {
+        synchronized (UiThread.class) {
+            ensureThreadLocked();
+            return sHandler;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/UpdateLockService.java b/services/core/java/com/android/server/UpdateLockService.java
new file mode 100644
index 0000000..0f778cd
--- /dev/null
+++ b/services/core/java/com/android/server/UpdateLockService.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IUpdateLock;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.TokenWatcher;
+import android.os.UpdateLock;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class UpdateLockService extends IUpdateLock.Stub {
+    static final boolean DEBUG = false;
+    static final String TAG = "UpdateLockService";
+
+    // signatureOrSystem required to use update locks
+    static final String PERMISSION = "android.permission.UPDATE_LOCK";
+
+    Context mContext;
+    LockWatcher mLocks;
+
+    class LockWatcher extends TokenWatcher {
+        LockWatcher(Handler h, String tag) {
+            super(h, tag);
+        }
+
+        public void acquired() {
+            if (DEBUG) {
+                Slog.d(TAG, "first acquire; broadcasting convenient=false");
+            }
+            sendLockChangedBroadcast(false);
+        }
+        public void released() {
+            if (DEBUG) {
+                Slog.d(TAG, "last release; broadcasting convenient=true");
+            }
+            sendLockChangedBroadcast(true);
+        }
+    }
+
+    UpdateLockService(Context context) {
+        mContext = context;
+        mLocks = new LockWatcher(new Handler(), "UpdateLocks");
+
+        // Consider just-booting to be a reasonable time to allow
+        // interruptions for update installation etc.
+        sendLockChangedBroadcast(true);
+    }
+
+    void sendLockChangedBroadcast(boolean state) {
+        // Safe early during boot because this broadcast only goes to registered receivers.
+        long oldIdent = Binder.clearCallingIdentity();
+        try {
+            Intent intent = new Intent(UpdateLock.UPDATE_LOCK_CHANGED)
+                    .putExtra(UpdateLock.NOW_IS_CONVENIENT, state)
+                    .putExtra(UpdateLock.TIMESTAMP, System.currentTimeMillis())
+                    .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        } finally {
+            Binder.restoreCallingIdentity(oldIdent);
+        }
+    }
+
+    @Override
+    public void acquireUpdateLock(IBinder token, String tag) throws RemoteException {
+        if (DEBUG) {
+            Slog.d(TAG, "acquire(" + token + ") by " + makeTag(tag));
+        }
+
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "acquireUpdateLock");
+        mLocks.acquire(token, makeTag(tag));
+    }
+
+    @Override
+    public void releaseUpdateLock(IBinder token) throws RemoteException {
+        if (DEBUG) {
+            Slog.d(TAG, "release(" + token + ')');
+        }
+
+        mContext.enforceCallingOrSelfPermission(PERMISSION, "releaseUpdateLock");
+        mLocks.release(token);
+    };
+
+    private String makeTag(String tag) {
+        return "{tag=" + tag
+                + " uid=" + Binder.getCallingUid()
+                + " pid=" + Binder.getCallingPid() + '}';
+    }
+
+    @Override
+    public 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 update lock service from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        mLocks.dump(pw);
+    }
+}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
new file mode 100644
index 0000000..28eb948
--- /dev/null
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.hardware.input.InputManager;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IVibratorService;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.os.Binder;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Slog;
+import android.view.InputDevice;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+public class VibratorService extends IVibratorService.Stub
+        implements InputManager.InputDeviceListener {
+    private static final String TAG = "VibratorService";
+
+    private final LinkedList<Vibration> mVibrations;
+    private Vibration mCurrentVibration;
+    private final WorkSource mTmpWorkSource = new WorkSource();
+    private final Handler mH = new Handler();
+
+    private final Context mContext;
+    private final PowerManager.WakeLock mWakeLock;
+    private final IAppOpsService mAppOpsService;
+    private final IBatteryStats mBatteryStatsService;
+    private InputManager mIm;
+
+    volatile VibrateThread mThread;
+
+    // mInputDeviceVibrators lock should be acquired after mVibrations lock, if both are
+    // to be acquired
+    private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<Vibrator>();
+    private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
+    private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators
+
+    private int mCurVibUid = -1;
+
+    native static boolean vibratorExists();
+    native static void vibratorOn(long milliseconds);
+    native static void vibratorOff();
+
+    private class Vibration implements IBinder.DeathRecipient {
+        private final IBinder mToken;
+        private final long    mTimeout;
+        private final long    mStartTime;
+        private final long[]  mPattern;
+        private final int     mRepeat;
+        private final int     mUid;
+        private final String  mPackageName;
+
+        Vibration(IBinder token, long millis, int uid, String packageName) {
+            this(token, millis, null, 0, uid, packageName);
+        }
+
+        Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
+            this(token, 0, pattern, repeat, uid, packageName);
+        }
+
+        private Vibration(IBinder token, long millis, long[] pattern,
+                int repeat, int uid, String packageName) {
+            mToken = token;
+            mTimeout = millis;
+            mStartTime = SystemClock.uptimeMillis();
+            mPattern = pattern;
+            mRepeat = repeat;
+            mUid = uid;
+            mPackageName = packageName;
+        }
+
+        public void binderDied() {
+            synchronized (mVibrations) {
+                mVibrations.remove(this);
+                if (this == mCurrentVibration) {
+                    doCancelVibrateLocked();
+                    startNextVibrationLocked();
+                }
+            }
+        }
+
+        public boolean hasLongerTimeout(long millis) {
+            if (mTimeout == 0) {
+                // This is a pattern, return false to play the simple
+                // vibration.
+                return false;
+            }
+            if ((mStartTime + mTimeout)
+                    < (SystemClock.uptimeMillis() + millis)) {
+                // If this vibration will end before the time passed in, let
+                // the new vibration play.
+                return false;
+            }
+            return true;
+        }
+    }
+
+    VibratorService(Context context) {
+        // Reset the hardware to a default state, in case this is a runtime
+        // restart instead of a fresh boot.
+        vibratorOff();
+
+        mContext = context;
+        PowerManager pm = (PowerManager)context.getSystemService(
+                Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
+        mWakeLock.setReferenceCounted(true);
+
+        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE));
+        mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                BatteryStats.SERVICE_NAME));
+
+        mVibrations = new LinkedList<Vibration>();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        context.registerReceiver(mIntentReceiver, filter);
+    }
+
+    public void systemReady() {
+        mIm = (InputManager)mContext.getSystemService(Context.INPUT_SERVICE);
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.VIBRATE_INPUT_DEVICES), true,
+                new ContentObserver(mH) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateInputDeviceVibrators();
+                    }
+                }, UserHandle.USER_ALL);
+
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updateInputDeviceVibrators();
+            }
+        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mH);
+
+        updateInputDeviceVibrators();
+    }
+
+    public boolean hasVibrator() {
+        return doVibratorExists();
+    }
+
+    private void verifyIncomingUid(int uid) {
+        if (uid == Binder.getCallingUid()) {
+            return;
+        }
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+
+    public void vibrate(int uid, String packageName, long milliseconds, IBinder token) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires VIBRATE permission");
+        }
+        verifyIncomingUid(uid);
+        // We're running in the system server so we cannot crash. Check for a
+        // timeout of 0 or negative. This will ensure that a vibration has
+        // either a timeout of > 0 or a non-null pattern.
+        if (milliseconds <= 0 || (mCurrentVibration != null
+                && mCurrentVibration.hasLongerTimeout(milliseconds))) {
+            // Ignore this vibration since the current vibration will play for
+            // longer than milliseconds.
+            return;
+        }
+
+        Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mVibrations) {
+                removeVibrationLocked(token);
+                doCancelVibrateLocked();
+                mCurrentVibration = vib;
+                startVibrationLocked(vib);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private boolean isAll0(long[] pattern) {
+        int N = pattern.length;
+        for (int i = 0; i < N; i++) {
+            if (pattern[i] != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
+            IBinder token) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires VIBRATE permission");
+        }
+        verifyIncomingUid(uid);
+        // so wakelock calls will succeed
+        long identity = Binder.clearCallingIdentity();
+        try {
+            if (false) {
+                String s = "";
+                int N = pattern.length;
+                for (int i=0; i<N; i++) {
+                    s += " " + pattern[i];
+                }
+                Slog.i(TAG, "vibrating with pattern: " + s);
+            }
+
+            // we're running in the server so we can't fail
+            if (pattern == null || pattern.length == 0
+                    || isAll0(pattern)
+                    || repeat >= pattern.length || token == null) {
+                return;
+            }
+
+            Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
+            try {
+                token.linkToDeath(vib, 0);
+            } catch (RemoteException e) {
+                return;
+            }
+
+            synchronized (mVibrations) {
+                removeVibrationLocked(token);
+                doCancelVibrateLocked();
+                if (repeat >= 0) {
+                    mVibrations.addFirst(vib);
+                    startNextVibrationLocked();
+                } else {
+                    // A negative repeat means that this pattern is not meant
+                    // to repeat. Treat it like a simple vibration.
+                    mCurrentVibration = vib;
+                    startVibrationLocked(vib);
+                }
+            }
+        }
+        finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    public void cancelVibrate(IBinder token) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.VIBRATE,
+                "cancelVibrate");
+
+        // so wakelock calls will succeed
+        long identity = Binder.clearCallingIdentity();
+        try {
+            synchronized (mVibrations) {
+                final Vibration vib = removeVibrationLocked(token);
+                if (vib == mCurrentVibration) {
+                    doCancelVibrateLocked();
+                    startNextVibrationLocked();
+                }
+            }
+        }
+        finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    private final Runnable mVibrationRunnable = new Runnable() {
+        public void run() {
+            synchronized (mVibrations) {
+                doCancelVibrateLocked();
+                startNextVibrationLocked();
+            }
+        }
+    };
+
+    // Lock held on mVibrations
+    private void doCancelVibrateLocked() {
+        if (mThread != null) {
+            synchronized (mThread) {
+                mThread.mDone = true;
+                mThread.notify();
+            }
+            mThread = null;
+        }
+        doVibratorOff();
+        mH.removeCallbacks(mVibrationRunnable);
+        reportFinishVibrationLocked();
+    }
+
+    // Lock held on mVibrations
+    private void startNextVibrationLocked() {
+        if (mVibrations.size() <= 0) {
+            reportFinishVibrationLocked();
+            mCurrentVibration = null;
+            return;
+        }
+        mCurrentVibration = mVibrations.getFirst();
+        startVibrationLocked(mCurrentVibration);
+    }
+
+    // Lock held on mVibrations
+    private void startVibrationLocked(final Vibration vib) {
+        try {
+            int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                    AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                if (mode == AppOpsManager.MODE_ERRORED) {
+                    Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
+                }
+                mH.post(mVibrationRunnable);
+                return;
+            }
+        } catch (RemoteException e) {
+        }
+        if (vib.mTimeout != 0) {
+            doVibratorOn(vib.mTimeout, vib.mUid);
+            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
+        } else {
+            // mThread better be null here. doCancelVibrate should always be
+            // called before startNextVibrationLocked or startVibrationLocked.
+            mThread = new VibrateThread(vib);
+            mThread.start();
+        }
+    }
+
+    private void reportFinishVibrationLocked() {
+        if (mCurrentVibration != null) {
+            try {
+                mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+                        AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid,
+                        mCurrentVibration.mPackageName);
+            } catch (RemoteException e) {
+            }
+            mCurrentVibration = null;
+        }
+    }
+
+    // Lock held on mVibrations
+    private Vibration removeVibrationLocked(IBinder token) {
+        ListIterator<Vibration> iter = mVibrations.listIterator(0);
+        while (iter.hasNext()) {
+            Vibration vib = iter.next();
+            if (vib.mToken == token) {
+                iter.remove();
+                unlinkVibration(vib);
+                return vib;
+            }
+        }
+        // We might be looking for a simple vibration which is only stored in
+        // mCurrentVibration.
+        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
+            unlinkVibration(mCurrentVibration);
+            return mCurrentVibration;
+        }
+        return null;
+    }
+
+    private void unlinkVibration(Vibration vib) {
+        if (vib.mPattern != null) {
+            // If Vibration object has a pattern,
+            // the Vibration object has also been linkedToDeath.
+            vib.mToken.unlinkToDeath(vib, 0);
+        }
+    }
+
+    private void updateInputDeviceVibrators() {
+        synchronized (mVibrations) {
+            doCancelVibrateLocked();
+
+            synchronized (mInputDeviceVibrators) {
+                mVibrateInputDevicesSetting = false;
+                try {
+                    mVibrateInputDevicesSetting = Settings.System.getIntForUser(
+                            mContext.getContentResolver(),
+                            Settings.System.VIBRATE_INPUT_DEVICES, UserHandle.USER_CURRENT) > 0;
+                } catch (SettingNotFoundException snfe) {
+                }
+
+                if (mVibrateInputDevicesSetting) {
+                    if (!mInputDeviceListenerRegistered) {
+                        mInputDeviceListenerRegistered = true;
+                        mIm.registerInputDeviceListener(this, mH);
+                    }
+                } else {
+                    if (mInputDeviceListenerRegistered) {
+                        mInputDeviceListenerRegistered = false;
+                        mIm.unregisterInputDeviceListener(this);
+                    }
+                }
+
+                mInputDeviceVibrators.clear();
+                if (mVibrateInputDevicesSetting) {
+                    int[] ids = mIm.getInputDeviceIds();
+                    for (int i = 0; i < ids.length; i++) {
+                        InputDevice device = mIm.getInputDevice(ids[i]);
+                        Vibrator vibrator = device.getVibrator();
+                        if (vibrator.hasVibrator()) {
+                            mInputDeviceVibrators.add(vibrator);
+                        }
+                    }
+                }
+            }
+
+            startNextVibrationLocked();
+        }
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        updateInputDeviceVibrators();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        updateInputDeviceVibrators();
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        updateInputDeviceVibrators();
+    }
+
+    private boolean doVibratorExists() {
+        // For now, we choose to ignore the presence of input devices that have vibrators
+        // when reporting whether the device has a vibrator.  Applications often use this
+        // information to decide whether to enable certain features so they expect the
+        // result of hasVibrator() to be constant.  For now, just report whether
+        // the device has a built-in vibrator.
+        //synchronized (mInputDeviceVibrators) {
+        //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();
+        //}
+        return vibratorExists();
+    }
+
+    private void doVibratorOn(long millis, int uid) {
+        synchronized (mInputDeviceVibrators) {
+            try {
+                mBatteryStatsService.noteVibratorOn(uid, millis);
+                mCurVibUid = uid;
+            } catch (RemoteException e) {
+            }
+            final int vibratorCount = mInputDeviceVibrators.size();
+            if (vibratorCount != 0) {
+                for (int i = 0; i < vibratorCount; i++) {
+                    mInputDeviceVibrators.get(i).vibrate(millis);
+                }
+            } else {
+                vibratorOn(millis);
+            }
+        }
+    }
+
+    private void doVibratorOff() {
+        synchronized (mInputDeviceVibrators) {
+            if (mCurVibUid >= 0) {
+                try {
+                    mBatteryStatsService.noteVibratorOff(mCurVibUid);
+                } catch (RemoteException e) {
+                }
+                mCurVibUid = -1;
+            }
+            final int vibratorCount = mInputDeviceVibrators.size();
+            if (vibratorCount != 0) {
+                for (int i = 0; i < vibratorCount; i++) {
+                    mInputDeviceVibrators.get(i).cancel();
+                }
+            } else {
+                vibratorOff();
+            }
+        }
+    }
+
+    private class VibrateThread extends Thread {
+        final Vibration mVibration;
+        boolean mDone;
+
+        VibrateThread(Vibration vib) {
+            mVibration = vib;
+            mTmpWorkSource.set(vib.mUid);
+            mWakeLock.setWorkSource(mTmpWorkSource);
+            mWakeLock.acquire();
+        }
+
+        private void delay(long duration) {
+            if (duration > 0) {
+                long bedtime = duration + SystemClock.uptimeMillis();
+                do {
+                    try {
+                        this.wait(duration);
+                    }
+                    catch (InterruptedException e) {
+                    }
+                    if (mDone) {
+                        break;
+                    }
+                    duration = bedtime - SystemClock.uptimeMillis();
+                } while (duration > 0);
+            }
+        }
+
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
+            synchronized (this) {
+                final long[] pattern = mVibration.mPattern;
+                final int len = pattern.length;
+                final int repeat = mVibration.mRepeat;
+                final int uid = mVibration.mUid;
+                int index = 0;
+                long duration = 0;
+
+                while (!mDone) {
+                    // add off-time duration to any accumulated on-time duration
+                    if (index < len) {
+                        duration += pattern[index++];
+                    }
+
+                    // sleep until it is time to start the vibrator
+                    delay(duration);
+                    if (mDone) {
+                        break;
+                    }
+
+                    if (index < len) {
+                        // read on-time duration and start the vibrator
+                        // duration is saved for delay() at top of loop
+                        duration = pattern[index++];
+                        if (duration > 0) {
+                            VibratorService.this.doVibratorOn(duration, uid);
+                        }
+                    } else {
+                        if (repeat < 0) {
+                            break;
+                        } else {
+                            index = repeat;
+                            duration = 0;
+                        }
+                    }
+                }
+                mWakeLock.release();
+            }
+            synchronized (mVibrations) {
+                if (mThread == this) {
+                    mThread = null;
+                }
+                if (!mDone) {
+                    // If this vibration finished naturally, start the next
+                    // vibration.
+                    mVibrations.remove(mVibration);
+                    unlinkVibration(mVibration);
+                    startNextVibrationLocked();
+                }
+            }
+        }
+    };
+
+    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                synchronized (mVibrations) {
+                    doCancelVibrateLocked();
+
+                    int size = mVibrations.size();
+                    for(int i = 0; i < size; i++) {
+                        unlinkVibration(mVibrations.get(i));
+                    }
+
+                    mVibrations.clear();
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
new file mode 100644
index 0000000..b249953
--- /dev/null
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.IActivityController;
+import android.os.Binder;
+import android.os.RemoteException;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.power.PowerManagerService;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/** This class calls its monitor every minute. Killing this process if they don't return **/
+public class Watchdog extends Thread {
+    static final String TAG = "Watchdog";
+    static final boolean localLOGV = false || false;
+
+    // Set this to true to use debug default values.
+    static final boolean DB = false;
+
+    // Set this to true to have the watchdog record kernel thread stacks when it fires
+    static final boolean RECORD_KERNEL_THREADS = true;
+
+    static final long DEFAULT_TIMEOUT = DB ? 10*1000 : 60*1000;
+    static final long CHECK_INTERVAL = DEFAULT_TIMEOUT / 2;
+
+    // These are temporally ordered: larger values as lateness increases
+    static final int COMPLETED = 0;
+    static final int WAITING = 1;
+    static final int WAITED_HALF = 2;
+    static final int OVERDUE = 3;
+
+    // Which native processes to dump into dropbox's stack traces
+    public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
+        "/system/bin/mediaserver",
+        "/system/bin/sdcard",
+        "/system/bin/surfaceflinger"
+    };
+
+    static Watchdog sWatchdog;
+
+    /* This handler will be used to post message back onto the main thread */
+    final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>();
+    final HandlerChecker mMonitorChecker;
+    ContentResolver mResolver;
+    ActivityManagerService mActivity;
+
+    int mPhonePid;
+    IActivityController mController;
+    boolean mAllowRestart = true;
+
+    /**
+     * Used for checking status of handle threads and scheduling monitor callbacks.
+     */
+    public final class HandlerChecker implements Runnable {
+        private final Handler mHandler;
+        private final String mName;
+        private final long mWaitMax;
+        private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>();
+        private boolean mCompleted;
+        private Monitor mCurrentMonitor;
+        private long mStartTime;
+
+        HandlerChecker(Handler handler, String name, long waitMaxMillis) {
+            mHandler = handler;
+            mName = name;
+            mWaitMax = waitMaxMillis;
+            mCompleted = true;
+        }
+
+        public void addMonitor(Monitor monitor) {
+            mMonitors.add(monitor);
+        }
+
+        public void scheduleCheckLocked() {
+            if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) {
+                // If the target looper is or just recently was idling, then
+                // there is no reason to enqueue our checker on it since that
+                // is as good as it not being deadlocked.  This avoid having
+                // to do a context switch to check the thread.  Note that we
+                // only do this if mCheckReboot is false and we have no
+                // monitors, since those would need to be executed at this point.
+                mCompleted = true;
+                return;
+            }
+
+            if (!mCompleted) {
+                // we already have a check in flight, so no need
+                return;
+            }
+
+            mCompleted = false;
+            mCurrentMonitor = null;
+            mStartTime = SystemClock.uptimeMillis();
+            mHandler.postAtFrontOfQueue(this);
+        }
+
+        public boolean isOverdueLocked() {
+            return (!mCompleted) && (SystemClock.uptimeMillis() > mStartTime + mWaitMax);
+        }
+
+        public int getCompletionStateLocked() {
+            if (mCompleted) {
+                return COMPLETED;
+            } else {
+                long latency = SystemClock.uptimeMillis() - mStartTime;
+                if (latency < mWaitMax/2) {
+                    return WAITING;
+                } else if (latency < mWaitMax) {
+                    return WAITED_HALF;
+                }
+            }
+            return OVERDUE;
+        }
+
+        public Thread getThread() {
+            return mHandler.getLooper().getThread();
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        public String describeBlockedStateLocked() {
+            if (mCurrentMonitor == null) {
+                return "Blocked in handler on " + mName + " (" + getThread().getName() + ")";
+            } else {
+                return "Blocked in monitor " + mCurrentMonitor.getClass().getName()
+                        + " on " + mName + " (" + getThread().getName() + ")";
+            }
+        }
+
+        @Override
+        public void run() {
+            final int size = mMonitors.size();
+            for (int i = 0 ; i < size ; i++) {
+                synchronized (Watchdog.this) {
+                    mCurrentMonitor = mMonitors.get(i);
+                }
+                mCurrentMonitor.monitor();
+            }
+
+            synchronized (Watchdog.this) {
+                mCompleted = true;
+                mCurrentMonitor = null;
+            }
+        }
+    }
+
+    final class RebootRequestReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context c, Intent intent) {
+            if (intent.getIntExtra("nowait", 0) != 0) {
+                rebootSystem("Received ACTION_REBOOT broadcast");
+                return;
+            }
+            Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent);
+        }
+    }
+
+    public interface Monitor {
+        void monitor();
+    }
+
+    public static Watchdog getInstance() {
+        if (sWatchdog == null) {
+            sWatchdog = new Watchdog();
+        }
+
+        return sWatchdog;
+    }
+
+    private Watchdog() {
+        super("watchdog");
+        // Initialize handler checkers for each common thread we want to check.  Note
+        // that we are not currently checking the background thread, since it can
+        // potentially hold longer running operations with no guarantees about the timeliness
+        // of operations there.
+
+        // The shared foreground thread is the main checker.  It is where we
+        // will also dispatch monitor checks and do other work.
+        mMonitorChecker = new HandlerChecker(FgThread.getHandler(),
+                "foreground thread", DEFAULT_TIMEOUT);
+        mHandlerCheckers.add(mMonitorChecker);
+        // Add checker for main thread.  We only do a quick check since there
+        // can be UI running on the thread.
+        mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()),
+                "main thread", DEFAULT_TIMEOUT));
+        // Add checker for shared UI thread.
+        mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(),
+                "ui thread", DEFAULT_TIMEOUT));
+        // And also check IO thread.
+        mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(),
+                "i/o thread", DEFAULT_TIMEOUT));
+    }
+
+    public void init(Context context, ActivityManagerService activity) {
+        mResolver = context.getContentResolver();
+        mActivity = activity;
+
+        context.registerReceiver(new RebootRequestReceiver(),
+                new IntentFilter(Intent.ACTION_REBOOT),
+                android.Manifest.permission.REBOOT, null);
+    }
+
+    public void processStarted(String name, int pid) {
+        synchronized (this) {
+            if ("com.android.phone".equals(name)) {
+                mPhonePid = pid;
+            }
+        }
+    }
+
+    public void setActivityController(IActivityController controller) {
+        synchronized (this) {
+            mController = controller;
+        }
+    }
+
+    public void setAllowRestart(boolean allowRestart) {
+        synchronized (this) {
+            mAllowRestart = allowRestart;
+        }
+    }
+
+    public void addMonitor(Monitor monitor) {
+        synchronized (this) {
+            if (isAlive()) {
+                throw new RuntimeException("Monitors can't be added once the Watchdog is running");
+            }
+            mMonitorChecker.addMonitor(monitor);
+        }
+    }
+
+    public void addThread(Handler thread, String name) {
+        addThread(thread, name, DEFAULT_TIMEOUT);
+    }
+
+    public void addThread(Handler thread, String name, long timeoutMillis) {
+        synchronized (this) {
+            if (isAlive()) {
+                throw new RuntimeException("Threads can't be added once the Watchdog is running");
+            }
+            mHandlerCheckers.add(new HandlerChecker(thread, name, timeoutMillis));
+        }
+    }
+
+    /**
+     * Perform a full reboot of the system.
+     */
+    void rebootSystem(String reason) {
+        Slog.i(TAG, "Rebooting system because: " + reason);
+        PowerManagerService pms = (PowerManagerService) ServiceManager.getService("power");
+        pms.reboot(false, reason, false);
+    }
+
+    private int evaluateCheckerCompletionLocked() {
+        int state = COMPLETED;
+        for (int i=0; i<mHandlerCheckers.size(); i++) {
+            HandlerChecker hc = mHandlerCheckers.get(i);
+            state = Math.max(state, hc.getCompletionStateLocked());
+        }
+        return state;
+    }
+
+    private ArrayList<HandlerChecker> getBlockedCheckersLocked() {
+        ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>();
+        for (int i=0; i<mHandlerCheckers.size(); i++) {
+            HandlerChecker hc = mHandlerCheckers.get(i);
+            if (hc.isOverdueLocked()) {
+                checkers.add(hc);
+            }
+        }
+        return checkers;
+    }
+
+    private String describeCheckersLocked(ArrayList<HandlerChecker> checkers) {
+        StringBuilder builder = new StringBuilder(128);
+        for (int i=0; i<checkers.size(); i++) {
+            if (builder.length() > 0) {
+                builder.append(", ");
+            }
+            builder.append(checkers.get(i).describeBlockedStateLocked());
+        }
+        return builder.toString();
+    }
+
+    @Override
+    public void run() {
+        boolean waitedHalf = false;
+        while (true) {
+            final ArrayList<HandlerChecker> blockedCheckers;
+            final String subject;
+            final boolean allowRestart;
+            synchronized (this) {
+                long timeout = CHECK_INTERVAL;
+                // Make sure we (re)spin the checkers that have become idle within
+                // this wait-and-check interval
+                for (int i=0; i<mHandlerCheckers.size(); i++) {
+                    HandlerChecker hc = mHandlerCheckers.get(i);
+                    hc.scheduleCheckLocked();
+                }
+
+                // NOTE: We use uptimeMillis() here because we do not want to increment the time we
+                // wait while asleep. If the device is asleep then the thing that we are waiting
+                // to timeout on is asleep as well and won't have a chance to run, causing a false
+                // positive on when to kill things.
+                long start = SystemClock.uptimeMillis();
+                while (timeout > 0) {
+                    try {
+                        wait(timeout);
+                    } catch (InterruptedException e) {
+                        Log.wtf(TAG, e);
+                    }
+                    timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
+                }
+
+                final int waitState = evaluateCheckerCompletionLocked();
+                if (waitState == COMPLETED) {
+                    // The monitors have returned; reset
+                    waitedHalf = false;
+                    continue;
+                } else if (waitState == WAITING) {
+                    // still waiting but within their configured intervals; back off and recheck
+                    continue;
+                } else if (waitState == WAITED_HALF) {
+                    if (!waitedHalf) {
+                        // We've waited half the deadlock-detection interval.  Pull a stack
+                        // trace and wait another half.
+                        ArrayList<Integer> pids = new ArrayList<Integer>();
+                        pids.add(Process.myPid());
+                        ActivityManagerService.dumpStackTraces(true, pids, null, null,
+                                NATIVE_STACKS_OF_INTEREST);
+                        waitedHalf = true;
+                    }
+                    continue;
+                }
+
+                // something is overdue!
+                blockedCheckers = getBlockedCheckersLocked();
+                subject = describeCheckersLocked(blockedCheckers);
+                allowRestart = mAllowRestart;
+            }
+
+            // If we got here, that means that the system is most likely hung.
+            // First collect stack traces from all threads of the system process.
+            // Then kill this process so that the system will restart.
+            EventLog.writeEvent(EventLogTags.WATCHDOG, subject);
+
+            ArrayList<Integer> pids = new ArrayList<Integer>();
+            pids.add(Process.myPid());
+            if (mPhonePid > 0) pids.add(mPhonePid);
+            // Pass !waitedHalf so that just in case we somehow wind up here without having
+            // dumped the halfway stacks, we properly re-initialize the trace file.
+            final File stack = ActivityManagerService.dumpStackTraces(
+                    !waitedHalf, pids, null, null, NATIVE_STACKS_OF_INTEREST);
+
+            // Give some extra time to make sure the stack traces get written.
+            // The system's been hanging for a minute, another second or two won't hurt much.
+            SystemClock.sleep(2000);
+
+            // Pull our own kernel thread stacks as well if we're configured for that
+            if (RECORD_KERNEL_THREADS) {
+                dumpKernelStackTraces();
+            }
+
+            // Trigger the kernel to dump all blocked threads to the kernel log
+            try {
+                FileWriter sysrq_trigger = new FileWriter("/proc/sysrq-trigger");
+                sysrq_trigger.write("w");
+                sysrq_trigger.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to write to /proc/sysrq-trigger");
+                Slog.e(TAG, e.getMessage());
+            }
+
+            // Try to add the error to the dropbox, but assuming that the ActivityManager
+            // itself may be deadlocked.  (which has happened, causing this statement to
+            // deadlock and the watchdog as a whole to be ineffective)
+            Thread dropboxThread = new Thread("watchdogWriteToDropbox") {
+                    public void run() {
+                        mActivity.addErrorToDropBox(
+                                "watchdog", null, "system_server", null, null,
+                                subject, null, stack, null);
+                    }
+                };
+            dropboxThread.start();
+            try {
+                dropboxThread.join(2000);  // wait up to 2 seconds for it to return.
+            } catch (InterruptedException ignored) {}
+
+            IActivityController controller;
+            synchronized (this) {
+                controller = mController;
+            }
+            if (controller != null) {
+                Slog.i(TAG, "Reporting stuck state to activity controller");
+                try {
+                    Binder.setDumpDisabled("Service dumps disabled due to hung system process.");
+                    // 1 = keep waiting, -1 = kill system
+                    int res = controller.systemNotResponding(subject);
+                    if (res >= 0) {
+                        Slog.i(TAG, "Activity controller requested to coninue to wait");
+                        waitedHalf = false;
+                        continue;
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+
+            // Only kill the process if the debugger is not attached.
+            if (Debug.isDebuggerConnected()) {
+                Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
+            } else if (!allowRestart) {
+                Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
+            } else {
+                Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
+                for (int i=0; i<blockedCheckers.size(); i++) {
+                    Slog.w(TAG, blockedCheckers.get(i).getName() + " stack trace:");
+                    StackTraceElement[] stackTrace
+                            = blockedCheckers.get(i).getThread().getStackTrace();
+                    for (StackTraceElement element: stackTrace) {
+                        Slog.w(TAG, "    at " + element);
+                    }
+                }
+                Slog.w(TAG, "*** GOODBYE!");
+                Process.killProcess(Process.myPid());
+                System.exit(10);
+            }
+
+            waitedHalf = false;
+        }
+    }
+
+    private File dumpKernelStackTraces() {
+        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
+        if (tracesPath == null || tracesPath.length() == 0) {
+            return null;
+        }
+
+        native_dumpKernelStacks(tracesPath);
+        return new File(tracesPath);
+    }
+
+    private native void native_dumpKernelStacks(String tracesPath);
+}
diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java
new file mode 100644
index 0000000..415fcc1
--- /dev/null
+++ b/services/core/java/com/android/server/WiredAccessoryManager.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.UEventObserver;
+import android.util.Slog;
+import android.media.AudioManager;
+import android.util.Log;
+import android.view.InputDevice;
+
+import com.android.internal.R;
+import com.android.server.input.InputManagerService;
+import com.android.server.input.InputManagerService.WiredAccessoryCallbacks;
+import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT;
+import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT;
+import static com.android.server.input.InputManagerService.SW_HEADPHONE_INSERT_BIT;
+import static com.android.server.input.InputManagerService.SW_MICROPHONE_INSERT_BIT;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using
+ * both the InputManagerService notifyWiredAccessoryChanged interface and the UEventObserver
+ * subsystem.
+ */
+final class WiredAccessoryManager implements WiredAccessoryCallbacks {
+    private static final String TAG = WiredAccessoryManager.class.getSimpleName();
+    private static final boolean LOG = true;
+
+    private static final int BIT_HEADSET = (1 << 0);
+    private static final int BIT_HEADSET_NO_MIC = (1 << 1);
+    private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
+    private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
+    private static final int BIT_HDMI_AUDIO = (1 << 4);
+    private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
+                                                   BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
+                                                   BIT_HDMI_AUDIO);
+
+    private static final String NAME_H2W = "h2w";
+    private static final String NAME_USB_AUDIO = "usb_audio";
+    private static final String NAME_HDMI_AUDIO = "hdmi_audio";
+    private static final String NAME_HDMI = "hdmi";
+
+    private static final int MSG_NEW_DEVICE_STATE = 1;
+
+    private final Object mLock = new Object();
+
+    private final WakeLock mWakeLock;  // held while there is a pending route change
+    private final AudioManager mAudioManager;
+
+    private int mHeadsetState;
+
+    private int mSwitchValues;
+
+    private final WiredAccessoryObserver mObserver;
+    private final InputManagerService mInputManager;
+
+    private final boolean mUseDevInputEventForAudioJack;
+
+    public WiredAccessoryManager(Context context, InputManagerService inputManager) {
+        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WiredAccessoryManager");
+        mWakeLock.setReferenceCounted(false);
+        mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
+        mInputManager = inputManager;
+
+        mUseDevInputEventForAudioJack =
+                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
+
+        mObserver = new WiredAccessoryObserver();
+
+        context.registerReceiver(new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context ctx, Intent intent) {
+                        bootCompleted();
+                    }
+                },
+                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, null);
+    }
+
+    private void bootCompleted() {
+        if (mUseDevInputEventForAudioJack) {
+            int switchValues = 0;
+            if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_HEADPHONE_INSERT) == 1) {
+                switchValues |= SW_HEADPHONE_INSERT_BIT;
+            }
+            if (mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY, SW_MICROPHONE_INSERT) == 1) {
+                switchValues |= SW_MICROPHONE_INSERT_BIT;
+            }
+            notifyWiredAccessoryChanged(0, switchValues,
+                    SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT);
+        }
+
+        mObserver.init();
+    }
+
+    @Override
+    public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask) {
+        if (LOG) Slog.v(TAG, "notifyWiredAccessoryChanged: when=" + whenNanos
+                + " bits=" + switchCodeToString(switchValues, switchMask)
+                + " mask=" + Integer.toHexString(switchMask));
+
+        synchronized (mLock) {
+            int headset;
+            mSwitchValues = (mSwitchValues & ~switchMask) | switchValues;
+            switch (mSwitchValues & (SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT)) {
+                case 0:
+                    headset = 0;
+                    break;
+
+                case SW_HEADPHONE_INSERT_BIT:
+                    headset = BIT_HEADSET_NO_MIC;
+                    break;
+
+                case SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT:
+                    headset = BIT_HEADSET;
+                    break;
+
+                case SW_MICROPHONE_INSERT_BIT:
+                    headset = BIT_HEADSET;
+                    break;
+
+                default:
+                    headset = 0;
+                    break;
+            }
+
+            updateLocked(NAME_H2W, (mHeadsetState & ~(BIT_HEADSET | BIT_HEADSET_NO_MIC)) | headset);
+        }
+    }
+
+    /**
+     * Compare the existing headset state with the new state and pass along accordingly. Note
+     * that this only supports a single headset at a time. Inserting both a usb and jacked headset
+     * results in support for the last one plugged in. Similarly, unplugging either is seen as
+     * unplugging all.
+     *
+     * @param newName One of the NAME_xxx variables defined above.
+     * @param newState 0 or one of the BIT_xxx variables defined above.
+     */
+    private void updateLocked(String newName, int newState) {
+        // Retain only relevant bits
+        int headsetState = newState & SUPPORTED_HEADSETS;
+        int usb_headset_anlg = headsetState & BIT_USB_HEADSET_ANLG;
+        int usb_headset_dgtl = headsetState & BIT_USB_HEADSET_DGTL;
+        int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
+        boolean h2wStateChange = true;
+        boolean usbStateChange = true;
+        if (LOG) Slog.v(TAG, "newName=" + newName
+                + " newState=" + newState
+                + " headsetState=" + headsetState
+                + " prev headsetState=" + mHeadsetState);
+
+        if (mHeadsetState == headsetState) {
+            Log.e(TAG, "No state change.");
+            return;
+        }
+
+        // reject all suspect transitions: only accept state changes from:
+        // - a: 0 headset to 1 headset
+        // - b: 1 headset to 0 headset
+        if (h2w_headset == (BIT_HEADSET | BIT_HEADSET_NO_MIC)) {
+            Log.e(TAG, "Invalid combination, unsetting h2w flag");
+            h2wStateChange = false;
+        }
+        // - c: 0 usb headset to 1 usb headset
+        // - d: 1 usb headset to 0 usb headset
+        if (usb_headset_anlg == BIT_USB_HEADSET_ANLG && usb_headset_dgtl == BIT_USB_HEADSET_DGTL) {
+            Log.e(TAG, "Invalid combination, unsetting usb flag");
+            usbStateChange = false;
+        }
+        if (!h2wStateChange && !usbStateChange) {
+            Log.e(TAG, "invalid transition, returning ...");
+            return;
+        }
+
+        mWakeLock.acquire();
+
+        Message msg = mHandler.obtainMessage(MSG_NEW_DEVICE_STATE, headsetState,
+                mHeadsetState, newName);
+        mHandler.sendMessage(msg);
+
+        mHeadsetState = headsetState;
+    }
+
+    private final Handler mHandler = new Handler(Looper.myLooper(), null, true) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_NEW_DEVICE_STATE:
+                    setDevicesState(msg.arg1, msg.arg2, (String)msg.obj);
+                    mWakeLock.release();
+            }
+        }
+    };
+
+    private void setDevicesState(
+            int headsetState, int prevHeadsetState, String headsetName) {
+        synchronized (mLock) {
+            int allHeadsets = SUPPORTED_HEADSETS;
+            for (int curHeadset = 1; allHeadsets != 0; curHeadset <<= 1) {
+                if ((curHeadset & allHeadsets) != 0) {
+                    setDeviceStateLocked(curHeadset, headsetState, prevHeadsetState, headsetName);
+                    allHeadsets &= ~curHeadset;
+                }
+            }
+        }
+    }
+
+    private void setDeviceStateLocked(int headset,
+            int headsetState, int prevHeadsetState, String headsetName) {
+        if ((headsetState & headset) != (prevHeadsetState & headset)) {
+            int device;
+            int state;
+
+            if ((headsetState & headset) != 0) {
+                state = 1;
+            } else {
+                state = 0;
+            }
+
+            if (headset == BIT_HEADSET) {
+                device = AudioManager.DEVICE_OUT_WIRED_HEADSET;
+            } else if (headset == BIT_HEADSET_NO_MIC){
+                device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
+            } else if (headset == BIT_USB_HEADSET_ANLG) {
+                device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
+            } else if (headset == BIT_USB_HEADSET_DGTL) {
+                device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
+            } else if (headset == BIT_HDMI_AUDIO) {
+                device = AudioManager.DEVICE_OUT_AUX_DIGITAL;
+            } else {
+                Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
+                return;
+            }
+
+            if (LOG)
+                Slog.v(TAG, "device "+headsetName+((state == 1) ? " connected" : " disconnected"));
+
+            mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);
+        }
+    }
+
+    private String switchCodeToString(int switchValues, int switchMask) {
+        StringBuffer sb = new StringBuffer();
+        if ((switchMask & SW_HEADPHONE_INSERT_BIT) != 0 &&
+                (switchValues & SW_HEADPHONE_INSERT_BIT) != 0) {
+            sb.append("SW_HEADPHONE_INSERT ");
+        }
+        if ((switchMask & SW_MICROPHONE_INSERT_BIT) != 0 &&
+                (switchValues & SW_MICROPHONE_INSERT_BIT) != 0) {
+            sb.append("SW_MICROPHONE_INSERT");
+        }
+        return sb.toString();
+    }
+
+    class WiredAccessoryObserver extends UEventObserver {
+        private final List<UEventInfo> mUEventInfo;
+
+        public WiredAccessoryObserver() {
+            mUEventInfo = makeObservedUEventList();
+        }
+
+        void init() {
+            synchronized (mLock) {
+                if (LOG) Slog.v(TAG, "init()");
+                char[] buffer = new char[1024];
+
+                for (int i = 0; i < mUEventInfo.size(); ++i) {
+                    UEventInfo uei = mUEventInfo.get(i);
+                    try {
+                        int curState;
+                        FileReader file = new FileReader(uei.getSwitchStatePath());
+                        int len = file.read(buffer, 0, 1024);
+                        file.close();
+                        curState = Integer.valueOf((new String(buffer, 0, len)).trim());
+
+                        if (curState > 0) {
+                            updateStateLocked(uei.getDevPath(), uei.getDevName(), curState);
+                        }
+                    } catch (FileNotFoundException e) {
+                        Slog.w(TAG, uei.getSwitchStatePath() +
+                                " not found while attempting to determine initial switch state");
+                    } catch (Exception e) {
+                        Slog.e(TAG, "" , e);
+                    }
+                }
+            }
+
+            // At any given time accessories could be inserted
+            // one on the board, one on the dock and one on HDMI:
+            // observe three UEVENTs
+            for (int i = 0; i < mUEventInfo.size(); ++i) {
+                UEventInfo uei = mUEventInfo.get(i);
+                startObserving("DEVPATH="+uei.getDevPath());
+            }
+        }
+
+        private List<UEventInfo> makeObservedUEventList() {
+            List<UEventInfo> retVal = new ArrayList<UEventInfo>();
+            UEventInfo uei;
+
+            // Monitor h2w
+            if (!mUseDevInputEventForAudioJack) {
+                uei = new UEventInfo(NAME_H2W, BIT_HEADSET, BIT_HEADSET_NO_MIC);
+                if (uei.checkSwitchExists()) {
+                    retVal.add(uei);
+                } else {
+                    Slog.w(TAG, "This kernel does not have wired headset support");
+                }
+            }
+
+            // Monitor USB
+            uei = new UEventInfo(NAME_USB_AUDIO, BIT_USB_HEADSET_ANLG, BIT_USB_HEADSET_DGTL);
+            if (uei.checkSwitchExists()) {
+                retVal.add(uei);
+            } else {
+                Slog.w(TAG, "This kernel does not have usb audio support");
+            }
+
+            // Monitor HDMI
+            //
+            // If the kernel has support for the "hdmi_audio" switch, use that.  It will be
+            // signalled only when the HDMI driver has a video mode configured, and the downstream
+            // sink indicates support for audio in its EDID.
+            //
+            // If the kernel does not have an "hdmi_audio" switch, just fall back on the older
+            // "hdmi" switch instead.
+            uei = new UEventInfo(NAME_HDMI_AUDIO, BIT_HDMI_AUDIO, 0);
+            if (uei.checkSwitchExists()) {
+                retVal.add(uei);
+            } else {
+                uei = new UEventInfo(NAME_HDMI, BIT_HDMI_AUDIO, 0);
+                if (uei.checkSwitchExists()) {
+                    retVal.add(uei);
+                } else {
+                    Slog.w(TAG, "This kernel does not have HDMI audio support");
+                }
+            }
+
+            return retVal;
+        }
+
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            if (LOG) Slog.v(TAG, "Headset UEVENT: " + event.toString());
+
+            try {
+                String devPath = event.get("DEVPATH");
+                String name = event.get("SWITCH_NAME");
+                int state = Integer.parseInt(event.get("SWITCH_STATE"));
+                synchronized (mLock) {
+                    updateStateLocked(devPath, name, state);
+                }
+            } catch (NumberFormatException e) {
+                Slog.e(TAG, "Could not parse switch state from event " + event);
+            }
+        }
+
+        private void updateStateLocked(String devPath, String name, int state) {
+            for (int i = 0; i < mUEventInfo.size(); ++i) {
+                UEventInfo uei = mUEventInfo.get(i);
+                if (devPath.equals(uei.getDevPath())) {
+                    updateLocked(name, uei.computeNewHeadsetState(mHeadsetState, state));
+                    return;
+                }
+            }
+        }
+
+        private final class UEventInfo {
+            private final String mDevName;
+            private final int mState1Bits;
+            private final int mState2Bits;
+
+            public UEventInfo(String devName, int state1Bits, int state2Bits) {
+                mDevName = devName;
+                mState1Bits = state1Bits;
+                mState2Bits = state2Bits;
+            }
+
+            public String getDevName() { return mDevName; }
+
+            public String getDevPath() {
+                return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName);
+            }
+
+            public String getSwitchStatePath() {
+                return String.format(Locale.US, "/sys/class/switch/%s/state", mDevName);
+            }
+
+            public boolean checkSwitchExists() {
+                File f = new File(getSwitchStatePath());
+                return f.exists();
+            }
+
+            public int computeNewHeadsetState(int headsetState, int switchState) {
+                int preserveMask = ~(mState1Bits | mState2Bits);
+                int setBits = ((switchState == 1) ? mState1Bits :
+                              ((switchState == 2) ? mState2Bits : 0));
+
+                return ((headsetState & preserveMask) | setBits);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
new file mode 100644
index 0000000..7552368
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accounts;
+
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.IAccountAuthenticator;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+
+/**
+ * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
+ * is built by interrogating the {@link PackageManager} and is updated as packages are added,
+ * removed and changed. The authenticators are referred to by their account type and
+ * are made available via the {@link RegisteredServicesCache#getServiceInfo} method.
+ * @hide
+ */
+/* package private */ class AccountAuthenticatorCache
+        extends RegisteredServicesCache<AuthenticatorDescription>
+        implements IAccountAuthenticatorCache {
+    private static final String TAG = "Account";
+    private static final MySerializer sSerializer = new MySerializer();
+
+    public AccountAuthenticatorCache(Context context) {
+        super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
+                AccountManager.AUTHENTICATOR_META_DATA_NAME,
+                AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
+    }
+
+    public AuthenticatorDescription parseServiceAttributes(Resources res,
+            String packageName, AttributeSet attrs) {
+        TypedArray sa = res.obtainAttributes(attrs,
+                com.android.internal.R.styleable.AccountAuthenticator);
+        try {
+            final String accountType =
+                    sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
+            final int labelId = sa.getResourceId(
+                    com.android.internal.R.styleable.AccountAuthenticator_label, 0);
+            final int iconId = sa.getResourceId(
+                    com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
+            final int smallIconId = sa.getResourceId(
+                    com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
+            final int prefId = sa.getResourceId(
+                    com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
+            final boolean customTokens = sa.getBoolean(
+                    com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
+            if (TextUtils.isEmpty(accountType)) {
+                return null;
+            }
+            return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
+                    smallIconId, prefId, customTokens);
+        } finally {
+            sa.recycle();
+        }
+    }
+
+    private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
+        public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
+                throws IOException {
+            out.attribute(null, "type", item.type);
+        }
+
+        public AuthenticatorDescription createFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
new file mode 100644
index 0000000..aa9849e
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -0,0 +1,3089 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accounts;
+
+import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.accounts.AuthenticatorDescription;
+import android.accounts.CantAddAccountActivity;
+import android.accounts.GrantCredentialsPermissionActivity;
+import android.accounts.IAccountAuthenticator;
+import android.accounts.IAccountAuthenticatorResponse;
+import android.accounts.IAccountManager;
+import android.accounts.IAccountManagerResponse;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.R;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.FgThread;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A system service that provides  account, password, and authtoken management for all
+ * accounts on the device. Some of these calls are implemented with the help of the corresponding
+ * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
+ * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
+ *    AccountManager accountManager = AccountManager.get(context);
+ * @hide
+ */
+public class AccountManagerService
+        extends IAccountManager.Stub
+        implements RegisteredServicesCacheListener<AuthenticatorDescription> {
+    private static final String TAG = "AccountManagerService";
+
+    private static final int TIMEOUT_DELAY_MS = 1000 * 60;
+    private static final String DATABASE_NAME = "accounts.db";
+    private static final int DATABASE_VERSION = 5;
+
+    private final Context mContext;
+
+    private final PackageManager mPackageManager;
+    private UserManager mUserManager;
+
+    private final MessageHandler mMessageHandler;
+
+    // Messages that can be sent on mHandler
+    private static final int MESSAGE_TIMED_OUT = 3;
+    private static final int MESSAGE_COPY_SHARED_ACCOUNT = 4;
+
+    private final IAccountAuthenticatorCache mAuthenticatorCache;
+
+    private static final String TABLE_ACCOUNTS = "accounts";
+    private static final String ACCOUNTS_ID = "_id";
+    private static final String ACCOUNTS_NAME = "name";
+    private static final String ACCOUNTS_TYPE = "type";
+    private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
+    private static final String ACCOUNTS_PASSWORD = "password";
+
+    private static final String TABLE_AUTHTOKENS = "authtokens";
+    private static final String AUTHTOKENS_ID = "_id";
+    private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
+    private static final String AUTHTOKENS_TYPE = "type";
+    private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
+
+    private static final String TABLE_GRANTS = "grants";
+    private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
+    private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
+    private static final String GRANTS_GRANTEE_UID = "uid";
+
+    private static final String TABLE_EXTRAS = "extras";
+    private static final String EXTRAS_ID = "_id";
+    private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
+    private static final String EXTRAS_KEY = "key";
+    private static final String EXTRAS_VALUE = "value";
+
+    private static final String TABLE_META = "meta";
+    private static final String META_KEY = "key";
+    private static final String META_VALUE = "value";
+
+    private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
+
+    private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
+            new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
+    private static final Intent ACCOUNTS_CHANGED_INTENT;
+
+    private static final String COUNT_OF_MATCHING_GRANTS = ""
+            + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
+            + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
+            + " AND " + GRANTS_GRANTEE_UID + "=?"
+            + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
+            + " AND " + ACCOUNTS_NAME + "=?"
+            + " AND " + ACCOUNTS_TYPE + "=?";
+
+    private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
+            AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+    private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
+            AUTHTOKENS_AUTHTOKEN};
+
+    private static final String SELECTION_USERDATA_BY_ACCOUNT =
+            EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+    private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
+
+    private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
+    private final AtomicInteger mNotificationIds = new AtomicInteger(1);
+
+    static class UserAccounts {
+        private final int userId;
+        private final DatabaseHelper openHelper;
+        private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
+                credentialsPermissionNotificationIds =
+                new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
+        private final HashMap<Account, Integer> signinRequiredNotificationIds =
+                new HashMap<Account, Integer>();
+        private final Object cacheLock = new Object();
+        /** protected by the {@link #cacheLock} */
+        private final HashMap<String, Account[]> accountCache =
+                new LinkedHashMap<String, Account[]>();
+        /** protected by the {@link #cacheLock} */
+        private final HashMap<Account, HashMap<String, String>> userDataCache =
+                new HashMap<Account, HashMap<String, String>>();
+        /** protected by the {@link #cacheLock} */
+        private final HashMap<Account, HashMap<String, String>> authTokenCache =
+                new HashMap<Account, HashMap<String, String>>();
+
+        UserAccounts(Context context, int userId) {
+            this.userId = userId;
+            synchronized (cacheLock) {
+                openHelper = new DatabaseHelper(context, userId);
+            }
+        }
+    }
+
+    private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
+
+    private static AtomicReference<AccountManagerService> sThis =
+            new AtomicReference<AccountManagerService>();
+    private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
+
+    static {
+        ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
+        ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+    }
+
+
+    /**
+     * This should only be called by system code. One should only call this after the service
+     * has started.
+     * @return a reference to the AccountManagerService instance
+     * @hide
+     */
+    public static AccountManagerService getSingleton() {
+        return sThis.get();
+    }
+
+    public AccountManagerService(Context context) {
+        this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
+    }
+
+    public AccountManagerService(Context context, PackageManager packageManager,
+            IAccountAuthenticatorCache authenticatorCache) {
+        mContext = context;
+        mPackageManager = packageManager;
+
+        mMessageHandler = new MessageHandler(FgThread.get().getLooper());
+
+        mAuthenticatorCache = authenticatorCache;
+        mAuthenticatorCache.setListener(this, null /* Handler */);
+
+        sThis.set(this);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context1, Intent intent) {
+                purgeOldGrantsAll();
+            }
+        }, intentFilter);
+
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_USER_STARTED);
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    onUserRemoved(intent);
+                } else if (Intent.ACTION_USER_STARTED.equals(action)) {
+                    onUserStarted(intent);
+                }
+            }
+        }, UserHandle.ALL, userFilter, null, null);
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The account manager only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Account Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    public void systemReady() {
+    }
+
+    private UserManager getUserManager() {
+        if (mUserManager == null) {
+            mUserManager = UserManager.get(mContext);
+        }
+        return mUserManager;
+    }
+
+    /* Caller should lock mUsers */
+    private UserAccounts initUserLocked(int userId) {
+        UserAccounts accounts = mUsers.get(userId);
+        if (accounts == null) {
+            accounts = new UserAccounts(mContext, userId);
+            mUsers.append(userId, accounts);
+            purgeOldGrants(accounts);
+            validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
+        }
+        return accounts;
+    }
+
+    private void purgeOldGrantsAll() {
+        synchronized (mUsers) {
+            for (int i = 0; i < mUsers.size(); i++) {
+                purgeOldGrants(mUsers.valueAt(i));
+            }
+        }
+    }
+
+    private void purgeOldGrants(UserAccounts accounts) {
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            final Cursor cursor = db.query(TABLE_GRANTS,
+                    new String[]{GRANTS_GRANTEE_UID},
+                    null, null, GRANTS_GRANTEE_UID, null, null);
+            try {
+                while (cursor.moveToNext()) {
+                    final int uid = cursor.getInt(0);
+                    final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
+                    if (packageExists) {
+                        continue;
+                    }
+                    Log.d(TAG, "deleting grants for UID " + uid
+                            + " because its package is no longer installed");
+                    db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
+                            new String[]{Integer.toString(uid)});
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Validate internal set of accounts against installed authenticators for
+     * given user. Clears cached authenticators before validating.
+     */
+    public void validateAccounts(int userId) {
+        final UserAccounts accounts = getUserAccounts(userId);
+
+        // Invalidate user-specific cache to make sure we catch any
+        // removed authenticators.
+        validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
+    }
+
+    /**
+     * Validate internal set of accounts against installed authenticators for
+     * given user. Clear cached authenticators before validating when requested.
+     */
+    private void validateAccountsInternal(
+            UserAccounts accounts, boolean invalidateAuthenticatorCache) {
+        if (invalidateAuthenticatorCache) {
+            mAuthenticatorCache.invalidateCache(accounts.userId);
+        }
+
+        final HashSet<AuthenticatorDescription> knownAuth = Sets.newHashSet();
+        for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> service :
+                mAuthenticatorCache.getAllServices(accounts.userId)) {
+            knownAuth.add(service.type);
+        }
+
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            boolean accountDeleted = false;
+            Cursor cursor = db.query(TABLE_ACCOUNTS,
+                    new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
+                    null, null, null, null, null);
+            try {
+                accounts.accountCache.clear();
+                final HashMap<String, ArrayList<String>> accountNamesByType =
+                        new LinkedHashMap<String, ArrayList<String>>();
+                while (cursor.moveToNext()) {
+                    final long accountId = cursor.getLong(0);
+                    final String accountType = cursor.getString(1);
+                    final String accountName = cursor.getString(2);
+
+                    if (!knownAuth.contains(AuthenticatorDescription.newKey(accountType))) {
+                        Slog.w(TAG, "deleting account " + accountName + " because type "
+                                + accountType + " no longer has a registered authenticator");
+                        db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+                        accountDeleted = true;
+                        final Account account = new Account(accountName, accountType);
+                        accounts.userDataCache.remove(account);
+                        accounts.authTokenCache.remove(account);
+                    } else {
+                        ArrayList<String> accountNames = accountNamesByType.get(accountType);
+                        if (accountNames == null) {
+                            accountNames = new ArrayList<String>();
+                            accountNamesByType.put(accountType, accountNames);
+                        }
+                        accountNames.add(accountName);
+                    }
+                }
+                for (Map.Entry<String, ArrayList<String>> cur
+                        : accountNamesByType.entrySet()) {
+                    final String accountType = cur.getKey();
+                    final ArrayList<String> accountNames = cur.getValue();
+                    final Account[] accountsForType = new Account[accountNames.size()];
+                    int i = 0;
+                    for (String accountName : accountNames) {
+                        accountsForType[i] = new Account(accountName, accountType);
+                        ++i;
+                    }
+                    accounts.accountCache.put(accountType, accountsForType);
+                }
+            } finally {
+                cursor.close();
+                if (accountDeleted) {
+                    sendAccountsChangedBroadcast(accounts.userId);
+                }
+            }
+        }
+    }
+
+    private UserAccounts getUserAccountsForCaller() {
+        return getUserAccounts(UserHandle.getCallingUserId());
+    }
+
+    protected UserAccounts getUserAccounts(int userId) {
+        synchronized (mUsers) {
+            UserAccounts accounts = mUsers.get(userId);
+            if (accounts == null) {
+                accounts = initUserLocked(userId);
+                mUsers.append(userId, accounts);
+            }
+            return accounts;
+        }
+    }
+
+    private void onUserRemoved(Intent intent) {
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+        if (userId < 1) return;
+
+        UserAccounts accounts;
+        synchronized (mUsers) {
+            accounts = mUsers.get(userId);
+            mUsers.remove(userId);
+        }
+        if (accounts == null) {
+            File dbFile = new File(getDatabaseName(userId));
+            dbFile.delete();
+            return;
+        }
+
+        synchronized (accounts.cacheLock) {
+            accounts.openHelper.close();
+            File dbFile = new File(getDatabaseName(userId));
+            dbFile.delete();
+        }
+    }
+
+    private void onUserStarted(Intent intent) {
+        int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+        if (userId < 1) return;
+
+        // Check if there's a shared account that needs to be created as an account
+        Account[] sharedAccounts = getSharedAccountsAsUser(userId);
+        if (sharedAccounts == null || sharedAccounts.length == 0) return;
+        Account[] accounts = getAccountsAsUser(null, userId);
+        for (Account sa : sharedAccounts) {
+            if (ArrayUtils.contains(accounts, sa)) continue;
+            // Account doesn't exist. Copy it now.
+            copyAccountToUser(sa, UserHandle.USER_OWNER, userId);
+        }
+    }
+
+    @Override
+    public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
+        validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
+    }
+
+    @Override
+    public String getPassword(Account account) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getPassword: " + account
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkAuthenticateAccountsPermission(account);
+
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            return readPasswordInternal(accounts, account);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private String readPasswordInternal(UserAccounts accounts, Account account) {
+        if (account == null) {
+            return null;
+        }
+
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+            Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
+                    ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                    new String[]{account.name, account.type}, null, null, null);
+            try {
+                if (cursor.moveToNext()) {
+                    return cursor.getString(0);
+                }
+                return null;
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    @Override
+    public String getUserData(Account account, String key) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getUserData: " + account
+                    + ", key " + key
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (key == null) throw new IllegalArgumentException("key is null");
+        checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            return readUserDataInternal(accounts, account, key);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public AuthenticatorDescription[] getAuthenticatorTypes() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getAuthenticatorTypes: "
+                    + "caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        final int userId = UserHandle.getCallingUserId();
+        final long identityToken = clearCallingIdentity();
+        try {
+            Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
+                    authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
+            AuthenticatorDescription[] types =
+                    new AuthenticatorDescription[authenticatorCollection.size()];
+            int i = 0;
+            for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
+                    : authenticatorCollection) {
+                types[i] = authenticator.type;
+                i++;
+            }
+            return types;
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public boolean addAccountExplicitly(Account account, String password, Bundle extras) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "addAccountExplicitly: " + account
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkAuthenticateAccountsPermission(account);
+        /*
+         * Child users are not allowed to add accounts. Only the accounts that are
+         * shared by the parent profile can be added to child profile.
+         *
+         * TODO: Only allow accounts that were shared to be added by
+         *     a limited user.
+         */
+
+        UserAccounts accounts = getUserAccountsForCaller();
+        // fails if the account already exists
+        long identityToken = clearCallingIdentity();
+        try {
+            return addAccountInternal(accounts, account, password, extras, false);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private boolean copyAccountToUser(final Account account, int userFrom, int userTo) {
+        final UserAccounts fromAccounts = getUserAccounts(userFrom);
+        final UserAccounts toAccounts = getUserAccounts(userTo);
+        if (fromAccounts == null || toAccounts == null) {
+            return false;
+        }
+
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(fromAccounts, null, account.type, false,
+                    false /* stripAuthTokenFromResult */) {
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", getAccountCredentialsForClone"
+                            + ", " + account.type;
+                }
+
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.getAccountCredentialsForCloning(this, account);
+                }
+
+                @Override
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                            // Create a Session for the target user and pass in the bundle
+                            completeCloningAccount(result, account, toAccounts);
+                        }
+                        return;
+                    } else {
+                        super.onResult(result);
+                    }
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return true;
+    }
+
+    void completeCloningAccount(final Bundle result, final Account account,
+            final UserAccounts targetUser) {
+        long id = clearCallingIdentity();
+        try {
+            new Session(targetUser, null, account.type, false,
+                    false /* stripAuthTokenFromResult */) {
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", getAccountCredentialsForClone"
+                            + ", " + account.type;
+                }
+
+                @Override
+                public void run() throws RemoteException {
+                    // Confirm that the owner's account still exists before this step.
+                    UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER);
+                    synchronized (owner.cacheLock) {
+                        Account[] ownerAccounts = getAccounts(UserHandle.USER_OWNER);
+                        for (Account acc : ownerAccounts) {
+                            if (acc.equals(account)) {
+                                mAuthenticator.addAccountFromCredentials(this, account, result);
+                                break;
+                            }
+                        }
+                    }
+                }
+
+                @Override
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                            // TODO: Anything?
+                        } else {
+                            // TODO: Show error notification
+                            // TODO: Should we remove the shadow account to avoid retries?
+                        }
+                        return;
+                    } else {
+                        super.onResult(result);
+                    }
+                }
+
+                @Override
+                public void onError(int errorCode, String errorMessage) {
+                    super.onError(errorCode,  errorMessage);
+                    // TODO: Show error notification to user
+                    // TODO: Should we remove the shadow account so that it doesn't keep trying?
+                }
+
+            }.bind();
+        } finally {
+            restoreCallingIdentity(id);
+        }
+    }
+
+    private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
+            Bundle extras, boolean restricted) {
+        if (account == null) {
+            return false;
+        }
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                long numMatches = DatabaseUtils.longForQuery(db,
+                        "select count(*) from " + TABLE_ACCOUNTS
+                                + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                        new String[]{account.name, account.type});
+                if (numMatches > 0) {
+                    Log.w(TAG, "insertAccountIntoDatabase: " + account
+                            + ", skipping since the account already exists");
+                    return false;
+                }
+                ContentValues values = new ContentValues();
+                values.put(ACCOUNTS_NAME, account.name);
+                values.put(ACCOUNTS_TYPE, account.type);
+                values.put(ACCOUNTS_PASSWORD, password);
+                long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
+                if (accountId < 0) {
+                    Log.w(TAG, "insertAccountIntoDatabase: " + account
+                            + ", skipping the DB insert failed");
+                    return false;
+                }
+                if (extras != null) {
+                    for (String key : extras.keySet()) {
+                        final String value = extras.getString(key);
+                        if (insertExtraLocked(db, accountId, key, value) < 0) {
+                            Log.w(TAG, "insertAccountIntoDatabase: " + account
+                                    + ", skipping since insertExtra failed for key " + key);
+                            return false;
+                        }
+                    }
+                }
+                db.setTransactionSuccessful();
+                insertAccountIntoCacheLocked(accounts, account);
+            } finally {
+                db.endTransaction();
+            }
+            sendAccountsChangedBroadcast(accounts.userId);
+        }
+        if (accounts.userId == UserHandle.USER_OWNER) {
+            addAccountToLimitedUsers(account);
+        }
+        return true;
+    }
+
+    /**
+     * Adds the account to all limited users as shared accounts. If the user is currently
+     * running, then clone the account too.
+     * @param account the account to share with limited users
+     */
+    private void addAccountToLimitedUsers(Account account) {
+        List<UserInfo> users = getUserManager().getUsers();
+        for (UserInfo user : users) {
+            if (user.isRestricted()) {
+                addSharedAccountAsUser(account, user.id);
+                try {
+                    if (ActivityManagerNative.getDefault().isUserRunning(user.id, false)) {
+                        mMessageHandler.sendMessage(mMessageHandler.obtainMessage(
+                                MESSAGE_COPY_SHARED_ACCOUNT, UserHandle.USER_OWNER, user.id,
+                                account));
+                    }
+                } catch (RemoteException re) {
+                    // Shouldn't happen
+                }
+            }
+        }
+    }
+
+    private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) {
+        ContentValues values = new ContentValues();
+        values.put(EXTRAS_KEY, key);
+        values.put(EXTRAS_ACCOUNTS_ID, accountId);
+        values.put(EXTRAS_VALUE, value);
+        return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
+    }
+
+    @Override
+    public void hasFeatures(IAccountManagerResponse response,
+            Account account, String[] features) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "hasFeatures: " + account
+                    + ", response " + response
+                    + ", features " + stringArrayToString(features)
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (features == null) throw new IllegalArgumentException("features is null");
+        checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            new TestFeaturesSession(accounts, response, account, features).bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private class TestFeaturesSession extends Session {
+        private final String[] mFeatures;
+        private final Account mAccount;
+
+        public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
+                Account account, String[] features) {
+            super(accounts, response, account.type, false /* expectActivityLaunch */,
+                    true /* stripAuthTokenFromResult */);
+            mFeatures = features;
+            mAccount = account;
+        }
+
+        @Override
+        public void run() throws RemoteException {
+            try {
+                mAuthenticator.hasFeatures(this, mAccount, mFeatures);
+            } catch (RemoteException e) {
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+            }
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    if (result == null) {
+                        response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+                        return;
+                    }
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                    }
+                    final Bundle newResult = new Bundle();
+                    newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
+                            result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
+                    response.onResult(newResult);
+                } catch (RemoteException e) {
+                    // if the caller is dead then there is no one to care about remote exceptions
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "failure while notifying response", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected String toDebugString(long now) {
+            return super.toDebugString(now) + ", hasFeatures"
+                    + ", " + mAccount
+                    + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+        }
+    }
+
+    @Override
+    public void removeAccount(IAccountManagerResponse response, Account account) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "removeAccount: " + account
+                    + ", response " + response
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkManageAccountsPermission();
+        UserHandle user = Binder.getCallingUserHandle();
+        UserAccounts accounts = getUserAccountsForCaller();
+        if (!canUserModifyAccounts(Binder.getCallingUid())) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION,
+                        "User cannot modify accounts");
+            } catch (RemoteException re) {
+            }
+        }
+
+        long identityToken = clearCallingIdentity();
+
+        cancelNotification(getSigninRequiredNotificationId(accounts, account), user);
+        synchronized(accounts.credentialsPermissionNotificationIds) {
+            for (Pair<Pair<Account, String>, Integer> pair:
+                accounts.credentialsPermissionNotificationIds.keySet()) {
+                if (account.equals(pair.first.first)) {
+                    int id = accounts.credentialsPermissionNotificationIds.get(pair);
+                    cancelNotification(id, user);
+                }
+            }
+        }
+
+        try {
+            new RemoveAccountSession(accounts, response, account).bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private class RemoveAccountSession extends Session {
+        final Account mAccount;
+        public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+                Account account) {
+            super(accounts, response, account.type, false /* expectActivityLaunch */,
+                    true /* stripAuthTokenFromResult */);
+            mAccount = account;
+        }
+
+        @Override
+        protected String toDebugString(long now) {
+            return super.toDebugString(now) + ", removeAccount"
+                    + ", account " + mAccount;
+        }
+
+        @Override
+        public void run() throws RemoteException {
+            mAuthenticator.getAccountRemovalAllowed(this, mAccount);
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
+                    && !result.containsKey(AccountManager.KEY_INTENT)) {
+                final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
+                if (removalAllowed) {
+                    removeAccountInternal(mAccounts, mAccount);
+                }
+                IAccountManagerResponse response = getResponseAndClose();
+                if (response != null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                    }
+                    Bundle result2 = new Bundle();
+                    result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
+                    try {
+                        response.onResult(result2);
+                    } catch (RemoteException e) {
+                        // ignore
+                    }
+                }
+            }
+            super.onResult(result);
+        }
+    }
+
+    /* For testing */
+    protected void removeAccountInternal(Account account) {
+        removeAccountInternal(getUserAccountsForCaller(), account);
+    }
+
+    private void removeAccountInternal(UserAccounts accounts, Account account) {
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                    new String[]{account.name, account.type});
+            removeAccountFromCacheLocked(accounts, account);
+            sendAccountsChangedBroadcast(accounts.userId);
+        }
+        if (accounts.userId == UserHandle.USER_OWNER) {
+            // Owner's account was removed, remove from any users that are sharing
+            // this account.
+            long id = Binder.clearCallingIdentity();
+            try {
+                List<UserInfo> users = mUserManager.getUsers(true);
+                for (UserInfo user : users) {
+                    if (!user.isPrimary() && user.isRestricted()) {
+                        removeSharedAccountAsUser(account, user.id);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(id);
+            }
+        }
+    }
+
+    @Override
+    public void invalidateAuthToken(String accountType, String authToken) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "invalidateAuthToken: accountType " + accountType
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        if (authToken == null) throw new IllegalArgumentException("authToken is null");
+        checkManageAccountsOrUseCredentialsPermissions();
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            synchronized (accounts.cacheLock) {
+                final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+                db.beginTransaction();
+                try {
+                    invalidateAuthTokenLocked(accounts, db, accountType, authToken);
+                    db.setTransactionSuccessful();
+                } finally {
+                    db.endTransaction();
+                }
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
+            String accountType, String authToken) {
+        if (authToken == null || accountType == null) {
+            return;
+        }
+        Cursor cursor = db.rawQuery(
+                "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+                        + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+                        + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+                        + " FROM " + TABLE_ACCOUNTS
+                        + " JOIN " + TABLE_AUTHTOKENS
+                        + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+                        + " = " + AUTHTOKENS_ACCOUNTS_ID
+                        + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
+                        + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
+                new String[]{authToken, accountType});
+        try {
+            while (cursor.moveToNext()) {
+                long authTokenId = cursor.getLong(0);
+                String accountName = cursor.getString(1);
+                String authTokenType = cursor.getString(2);
+                db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
+                writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
+                        authTokenType, null);
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
+            String authToken) {
+        if (account == null || type == null) {
+            return false;
+        }
+        cancelNotification(getSigninRequiredNotificationId(accounts, account),
+                new UserHandle(accounts.userId));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                long accountId = getAccountIdLocked(db, account);
+                if (accountId < 0) {
+                    return false;
+                }
+                db.delete(TABLE_AUTHTOKENS,
+                        AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
+                        new String[]{type});
+                ContentValues values = new ContentValues();
+                values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
+                values.put(AUTHTOKENS_TYPE, type);
+                values.put(AUTHTOKENS_AUTHTOKEN, authToken);
+                if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
+                    db.setTransactionSuccessful();
+                    writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
+                    return true;
+                }
+                return false;
+            } finally {
+                db.endTransaction();
+            }
+        }
+    }
+
+    @Override
+    public String peekAuthToken(Account account, String authTokenType) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "peekAuthToken: " + account
+                    + ", authTokenType " + authTokenType
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            return readAuthTokenInternal(accounts, account, authTokenType);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void setAuthToken(Account account, String authTokenType, String authToken) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setAuthToken: " + account
+                    + ", authTokenType " + authTokenType
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void setPassword(Account account, String password) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setAuthToken: " + account
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            setPasswordInternal(accounts, account, password);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private void setPasswordInternal(UserAccounts accounts, Account account, String password) {
+        if (account == null) {
+            return;
+        }
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                final ContentValues values = new ContentValues();
+                values.put(ACCOUNTS_PASSWORD, password);
+                final long accountId = getAccountIdLocked(db, account);
+                if (accountId >= 0) {
+                    final String[] argsAccountId = {String.valueOf(accountId)};
+                    db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
+                    db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
+                    accounts.authTokenCache.remove(account);
+                    db.setTransactionSuccessful();
+                }
+            } finally {
+                db.endTransaction();
+            }
+            sendAccountsChangedBroadcast(accounts.userId);
+        }
+    }
+
+    private void sendAccountsChangedBroadcast(int userId) {
+        Log.i(TAG, "the accounts changed, sending broadcast of "
+                + ACCOUNTS_CHANGED_INTENT.getAction());
+        mContext.sendBroadcastAsUser(ACCOUNTS_CHANGED_INTENT, new UserHandle(userId));
+    }
+
+    @Override
+    public void clearPassword(Account account) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "clearPassword: " + account
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            setPasswordInternal(accounts, account, null);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void setUserData(Account account, String key, String value) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "setUserData: " + account
+                    + ", key " + key
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (key == null) throw new IllegalArgumentException("key is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            setUserdataInternal(accounts, account, key, value);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+            String value) {
+        if (account == null || key == null) {
+            return;
+        }
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                long accountId = getAccountIdLocked(db, account);
+                if (accountId < 0) {
+                    return;
+                }
+                long extrasId = getExtrasIdLocked(db, accountId, key);
+                if (extrasId < 0 ) {
+                    extrasId = insertExtraLocked(db, accountId, key, value);
+                    if (extrasId < 0) {
+                        return;
+                    }
+                } else {
+                    ContentValues values = new ContentValues();
+                    values.put(EXTRAS_VALUE, value);
+                    if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
+                        return;
+                    }
+
+                }
+                writeUserDataIntoCacheLocked(accounts, db, account, key, value);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+        }
+    }
+
+    private void onResult(IAccountManagerResponse response, Bundle result) {
+        if (result == null) {
+            Log.e(TAG, "the result is unexpectedly null", new Exception());
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                    + response);
+        }
+        try {
+            response.onResult(result);
+        } catch (RemoteException e) {
+            // if the caller is dead then there is no one to care about remote
+            // exceptions
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "failure while notifying response", e);
+            }
+        }
+    }
+
+    @Override
+    public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType,
+                                  final String authTokenType)
+            throws RemoteException {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+
+        final int callingUid = getCallingUid();
+        clearCallingIdentity();
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException("can only call from system");
+        }
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(accounts, response, accountType, false,
+                    false /* stripAuthTokenFromResult */) {
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", getAuthTokenLabel"
+                            + ", " + accountType
+                            + ", authTokenType " + authTokenType;
+                }
+
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.getAuthTokenLabel(this, authTokenType);
+                }
+
+                @Override
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
+                        Bundle bundle = new Bundle();
+                        bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
+                        super.onResult(bundle);
+                        return;
+                    } else {
+                        super.onResult(result);
+                    }
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void getAuthToken(IAccountManagerResponse response, final Account account,
+            final String authTokenType, final boolean notifyOnAuthFailure,
+            final boolean expectActivityLaunch, Bundle loginOptionsIn) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getAuthToken: " + account
+                    + ", response " + response
+                    + ", authTokenType " + authTokenType
+                    + ", notifyOnAuthFailure " + notifyOnAuthFailure
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        try {
+            if (account == null) {
+                Slog.w(TAG, "getAuthToken called with null account");
+                response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "account is null");
+                return;
+            }
+            if (authTokenType == null) {
+                Slog.w(TAG, "getAuthToken called with null authTokenType");
+                response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, "authTokenType is null");
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to report error back to the client." + e);
+            return;
+        }
+
+        checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
+        final UserAccounts accounts = getUserAccountsForCaller();
+        final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
+        authenticatorInfo = mAuthenticatorCache.getServiceInfo(
+                AuthenticatorDescription.newKey(account.type), accounts.userId);
+        final boolean customTokens =
+            authenticatorInfo != null && authenticatorInfo.type.customTokens;
+
+        // skip the check if customTokens
+        final int callerUid = Binder.getCallingUid();
+        final boolean permissionGranted = customTokens ||
+            permissionIsGranted(account, authTokenType, callerUid);
+
+        final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
+            loginOptionsIn;
+        // let authenticator know the identity of the caller
+        loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
+        loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
+        if (notifyOnAuthFailure) {
+            loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
+        }
+
+        long identityToken = clearCallingIdentity();
+        try {
+            // if the caller has permission, do the peek. otherwise go the more expensive
+            // route of starting a Session
+            if (!customTokens && permissionGranted) {
+                String authToken = readAuthTokenInternal(accounts, account, authTokenType);
+                if (authToken != null) {
+                    Bundle result = new Bundle();
+                    result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+                    result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+                    result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+                    onResult(response, result);
+                    return;
+                }
+            }
+
+            new Session(accounts, response, account.type, expectActivityLaunch,
+                    false /* stripAuthTokenFromResult */) {
+                @Override
+                protected String toDebugString(long now) {
+                    if (loginOptions != null) loginOptions.keySet();
+                    return super.toDebugString(now) + ", getAuthToken"
+                            + ", " + account
+                            + ", authTokenType " + authTokenType
+                            + ", loginOptions " + loginOptions
+                            + ", notifyOnAuthFailure " + notifyOnAuthFailure;
+                }
+
+                @Override
+                public void run() throws RemoteException {
+                    // If the caller doesn't have permission then create and return the
+                    // "grant permission" intent instead of the "getAuthToken" intent.
+                    if (!permissionGranted) {
+                        mAuthenticator.getAuthTokenLabel(this, authTokenType);
+                    } else {
+                        mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
+                    }
+                }
+
+                @Override
+                public void onResult(Bundle result) {
+                    if (result != null) {
+                        if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
+                            Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
+                                    new AccountAuthenticatorResponse(this),
+                                    authTokenType,
+                                    result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
+                            Bundle bundle = new Bundle();
+                            bundle.putParcelable(AccountManager.KEY_INTENT, intent);
+                            onResult(bundle);
+                            return;
+                        }
+                        String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
+                        if (authToken != null) {
+                            String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+                            String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                            if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
+                                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                        "the type and name should not be empty");
+                                return;
+                            }
+                            if (!customTokens) {
+                                saveAuthTokenToDatabase(mAccounts, new Account(name, type),
+                                        authTokenType, authToken);
+                            }
+                        }
+
+                        Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
+                        if (intent != null && notifyOnAuthFailure && !customTokens) {
+                            doNotification(mAccounts,
+                                    account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
+                                    intent, accounts.userId);
+                        }
+                    }
+                    super.onResult(result);
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private void createNoCredentialsPermissionNotification(Account account, Intent intent,
+            int userId) {
+        int uid = intent.getIntExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
+        String authTokenType = intent.getStringExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
+        String authTokenLabel = intent.getStringExtra(
+                GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
+
+        Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+                0 /* when */);
+        final String titleAndSubtitle =
+                mContext.getString(R.string.permission_request_notification_with_subtitle,
+                account.name);
+        final int index = titleAndSubtitle.indexOf('\n');
+        String title = titleAndSubtitle;
+        String subtitle = "";
+        if (index > 0) {
+            title = titleAndSubtitle.substring(0, index);
+            subtitle = titleAndSubtitle.substring(index + 1);
+        }
+        UserHandle user = new UserHandle(userId);
+        n.setLatestEventInfo(mContext, title, subtitle,
+                PendingIntent.getActivityAsUser(mContext, 0, intent,
+                        PendingIntent.FLAG_CANCEL_CURRENT, null, user));
+        installNotification(getCredentialPermissionNotificationId(
+                account, authTokenType, uid), n, user);
+    }
+
+    private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
+            AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
+
+        Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
+        // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
+        // Since it was set in Eclair+ we can't change it without breaking apps using
+        // the intent from a non-Activity context.
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addCategory(
+                String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
+
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
+        intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
+
+        return intent;
+    }
+
+    private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
+            int uid) {
+        Integer id;
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+        synchronized (accounts.credentialsPermissionNotificationIds) {
+            final Pair<Pair<Account, String>, Integer> key =
+                    new Pair<Pair<Account, String>, Integer>(
+                            new Pair<Account, String>(account, authTokenType), uid);
+            id = accounts.credentialsPermissionNotificationIds.get(key);
+            if (id == null) {
+                id = mNotificationIds.incrementAndGet();
+                accounts.credentialsPermissionNotificationIds.put(key, id);
+            }
+        }
+        return id;
+    }
+
+    private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) {
+        Integer id;
+        synchronized (accounts.signinRequiredNotificationIds) {
+            id = accounts.signinRequiredNotificationIds.get(account);
+            if (id == null) {
+                id = mNotificationIds.incrementAndGet();
+                accounts.signinRequiredNotificationIds.put(account, id);
+            }
+        }
+        return id;
+    }
+
+    @Override
+    public void addAccount(final IAccountManagerResponse response, final String accountType,
+            final String authTokenType, final String[] requiredFeatures,
+            final boolean expectActivityLaunch, final Bundle optionsIn) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "addAccount: accountType " + accountType
+                    + ", response " + response
+                    + ", authTokenType " + authTokenType
+                    + ", requiredFeatures " + stringArrayToString(requiredFeatures)
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        checkManageAccountsPermission();
+
+        // Is user disallowed from modifying accounts?
+        if (!canUserModifyAccounts(Binder.getCallingUid())) {
+            try {
+                response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+                        "User is not allowed to add an account!");
+            } catch (RemoteException re) {
+            }
+            Intent cantAddAccount = new Intent(mContext, CantAddAccountActivity.class);
+            cantAddAccount.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            long identityToken = clearCallingIdentity();
+            try {
+                mContext.startActivityAsUser(cantAddAccount, UserHandle.CURRENT);
+            } finally {
+                restoreCallingIdentity(identityToken);
+            }
+            return;
+        }
+
+        UserAccounts accounts = getUserAccountsForCaller();
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
+        options.putInt(AccountManager.KEY_CALLER_UID, uid);
+        options.putInt(AccountManager.KEY_CALLER_PID, pid);
+
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(accounts, response, accountType, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
+                            options);
+                }
+
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", addAccount"
+                            + ", accountType " + accountType
+                            + ", requiredFeatures "
+                            + (requiredFeatures != null
+                              ? TextUtils.join(",", requiredFeatures)
+                              : null);
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void confirmCredentialsAsUser(IAccountManagerResponse response,
+            final Account account, final Bundle options, final boolean expectActivityLaunch,
+            int userId) {
+        // Only allow the system process to read accounts of other users
+        if (userId != UserHandle.getCallingUserId()
+                && Binder.getCallingUid() != Process.myUid()
+                && mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("User " + UserHandle.getCallingUserId()
+                    + " trying to confirm account credentials for " + userId);
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "confirmCredentials: " + account
+                    + ", response " + response
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccounts(userId);
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(accounts, response, account.type, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.confirmCredentials(this, account, options);
+                }
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", confirmCredentials"
+                            + ", " + account;
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void updateCredentials(IAccountManagerResponse response, final Account account,
+            final String authTokenType, final boolean expectActivityLaunch,
+            final Bundle loginOptions) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "updateCredentials: " + account
+                    + ", response " + response
+                    + ", authTokenType " + authTokenType
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(accounts, response, account.type, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
+                }
+                @Override
+                protected String toDebugString(long now) {
+                    if (loginOptions != null) loginOptions.keySet();
+                    return super.toDebugString(now) + ", updateCredentials"
+                            + ", " + account
+                            + ", authTokenType " + authTokenType
+                            + ", loginOptions " + loginOptions;
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void editProperties(IAccountManagerResponse response, final String accountType,
+            final boolean expectActivityLaunch) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "editProperties: accountType " + accountType
+                    + ", response " + response
+                    + ", expectActivityLaunch " + expectActivityLaunch
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
+        long identityToken = clearCallingIdentity();
+        try {
+            new Session(accounts, response, accountType, expectActivityLaunch,
+                    true /* stripAuthTokenFromResult */) {
+                @Override
+                public void run() throws RemoteException {
+                    mAuthenticator.editProperties(this, mAccountType);
+                }
+                @Override
+                protected String toDebugString(long now) {
+                    return super.toDebugString(now) + ", editProperties"
+                            + ", accountType " + accountType;
+                }
+            }.bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private class GetAccountsByTypeAndFeatureSession extends Session {
+        private final String[] mFeatures;
+        private volatile Account[] mAccountsOfType = null;
+        private volatile ArrayList<Account> mAccountsWithFeatures = null;
+        private volatile int mCurrentAccount = 0;
+        private final int mCallingUid;
+
+        public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
+                IAccountManagerResponse response, String type, String[] features, int callingUid) {
+            super(accounts, response, type, false /* expectActivityLaunch */,
+                    true /* stripAuthTokenFromResult */);
+            mCallingUid = callingUid;
+            mFeatures = features;
+        }
+
+        @Override
+        public void run() throws RemoteException {
+            synchronized (mAccounts.cacheLock) {
+                mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid,
+                        null);
+            }
+            // check whether each account matches the requested features
+            mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
+            mCurrentAccount = 0;
+
+            checkAccount();
+        }
+
+        public void checkAccount() {
+            if (mCurrentAccount >= mAccountsOfType.length) {
+                sendResult();
+                return;
+            }
+
+            final IAccountAuthenticator accountAuthenticator = mAuthenticator;
+            if (accountAuthenticator == null) {
+                // It is possible that the authenticator has died, which is indicated by
+                // mAuthenticator being set to null. If this happens then just abort.
+                // There is no need to send back a result or error in this case since
+                // that already happened when mAuthenticator was cleared.
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "checkAccount: aborting session since we are no longer"
+                            + " connected to the authenticator, " + toDebugString());
+                }
+                return;
+            }
+            try {
+                accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
+            } catch (RemoteException e) {
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
+            }
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            mNumResults++;
+            if (result == null) {
+                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
+                return;
+            }
+            if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
+                mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
+            }
+            mCurrentAccount++;
+            checkAccount();
+        }
+
+        public void sendResult() {
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    Account[] accounts = new Account[mAccountsWithFeatures.size()];
+                    for (int i = 0; i < accounts.length; i++) {
+                        accounts[i] = mAccountsWithFeatures.get(i);
+                    }
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
+                                + response);
+                    }
+                    Bundle result = new Bundle();
+                    result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+                    response.onResult(result);
+                } catch (RemoteException e) {
+                    // if the caller is dead then there is no one to care about remote exceptions
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "failure while notifying response", e);
+                    }
+                }
+            }
+        }
+
+
+        @Override
+        protected String toDebugString(long now) {
+            return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
+                    + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
+        }
+    }
+
+    /**
+     * Returns the accounts for a specific user
+     * @hide
+     */
+    public Account[] getAccounts(int userId) {
+        checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccounts(userId);
+        int callingUid = Binder.getCallingUid();
+        long identityToken = clearCallingIdentity();
+        try {
+            synchronized (accounts.cacheLock) {
+                return getAccountsFromCacheLocked(accounts, null, callingUid, null);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Returns accounts for all running users.
+     *
+     * @hide
+     */
+    public AccountAndUser[] getRunningAccounts() {
+        final int[] runningUserIds;
+        try {
+            runningUserIds = ActivityManagerNative.getDefault().getRunningUserIds();
+        } catch (RemoteException e) {
+            // Running in system_server; should never happen
+            throw new RuntimeException(e);
+        }
+        return getAccounts(runningUserIds);
+    }
+
+    /** {@hide} */
+    public AccountAndUser[] getAllAccounts() {
+        final List<UserInfo> users = getUserManager().getUsers();
+        final int[] userIds = new int[users.size()];
+        for (int i = 0; i < userIds.length; i++) {
+            userIds[i] = users.get(i).id;
+        }
+        return getAccounts(userIds);
+    }
+
+    private AccountAndUser[] getAccounts(int[] userIds) {
+        final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList();
+        for (int userId : userIds) {
+            UserAccounts userAccounts = getUserAccounts(userId);
+            if (userAccounts == null) continue;
+            synchronized (userAccounts.cacheLock) {
+                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
+                        Binder.getCallingUid(), null);
+                for (int a = 0; a < accounts.length; a++) {
+                    runningAccounts.add(new AccountAndUser(accounts[a], userId));
+                }
+            }
+        }
+
+        AccountAndUser[] accountsArray = new AccountAndUser[runningAccounts.size()];
+        return runningAccounts.toArray(accountsArray);
+    }
+
+    @Override
+    public Account[] getAccountsAsUser(String type, int userId) {
+        return getAccountsAsUser(type, userId, null, -1);
+    }
+
+    private Account[] getAccountsAsUser(String type, int userId, String callingPackage,
+            int packageUid) {
+        int callingUid = Binder.getCallingUid();
+        // Only allow the system process to read accounts of other users
+        if (userId != UserHandle.getCallingUserId()
+                && callingUid != Process.myUid()
+                && mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                    != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("User " + UserHandle.getCallingUserId()
+                    + " trying to get account for " + userId);
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getAccounts: accountType " + type
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        // If the original calling app was using the framework account chooser activity, we'll
+        // be passed in the original caller's uid here, which is what should be used for filtering.
+        if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
+            callingUid = packageUid;
+        }
+        checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccounts(userId);
+        long identityToken = clearCallingIdentity();
+        try {
+            synchronized (accounts.cacheLock) {
+                return getAccountsFromCacheLocked(accounts, type, callingUid, callingPackage);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public boolean addSharedAccountAsUser(Account account, int userId) {
+        userId = handleIncomingUser(userId);
+        SQLiteDatabase db = getUserAccounts(userId).openHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(ACCOUNTS_NAME, account.name);
+        values.put(ACCOUNTS_TYPE, account.type);
+        db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                new String[] {account.name, account.type});
+        long accountId = db.insert(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
+        if (accountId < 0) {
+            Log.w(TAG, "insertAccountIntoDatabase: " + account
+                    + ", skipping the DB insert failed");
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean removeSharedAccountAsUser(Account account, int userId) {
+        userId = handleIncomingUser(userId);
+        UserAccounts accounts = getUserAccounts(userId);
+        SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+        int r = db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
+                new String[] {account.name, account.type});
+        if (r > 0) {
+            removeAccountInternal(accounts, account);
+        }
+        return r > 0;
+    }
+
+    @Override
+    public Account[] getSharedAccountsAsUser(int userId) {
+        userId = handleIncomingUser(userId);
+        UserAccounts accounts = getUserAccounts(userId);
+        ArrayList<Account> accountList = new ArrayList<Account>();
+        Cursor cursor = null;
+        try {
+            cursor = accounts.openHelper.getReadableDatabase()
+                    .query(TABLE_SHARED_ACCOUNTS, new String[]{ACCOUNTS_NAME, ACCOUNTS_TYPE},
+                    null, null, null, null, null);
+            if (cursor != null && cursor.moveToFirst()) {
+                int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
+                int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
+                do {
+                    accountList.add(new Account(cursor.getString(nameIndex),
+                            cursor.getString(typeIndex)));
+                } while (cursor.moveToNext());
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        Account[] accountArray = new Account[accountList.size()];
+        accountList.toArray(accountArray);
+        return accountArray;
+    }
+
+    @Override
+    public Account[] getAccounts(String type) {
+        return getAccountsAsUser(type, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public Account[] getAccountsForPackage(String packageName, int uid) {
+        int callingUid = Binder.getCallingUid();
+        if (!UserHandle.isSameApp(callingUid, Process.myUid())) {
+            throw new SecurityException("getAccountsForPackage() called from unauthorized uid "
+                    + callingUid + " with uid=" + uid);
+        }
+        return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid);
+    }
+
+    @Override
+    public Account[] getAccountsByTypeForPackage(String type, String packageName) {
+        checkBinderPermission(android.Manifest.permission.INTERACT_ACROSS_USERS);
+        int packageUid = -1;
+        try {
+            packageUid = AppGlobals.getPackageManager().getPackageUid(
+                    packageName, UserHandle.getCallingUserId());
+        } catch (RemoteException re) {
+            Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re);
+            return new Account[0];
+        }
+        return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid);
+    }
+
+    @Override
+    public void getAccountsByFeatures(IAccountManagerResponse response,
+            String type, String[] features) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "getAccounts: accountType " + type
+                    + ", response " + response
+                    + ", features " + stringArrayToString(features)
+                    + ", caller's uid " + Binder.getCallingUid()
+                    + ", pid " + Binder.getCallingPid());
+        }
+        if (response == null) throw new IllegalArgumentException("response is null");
+        if (type == null) throw new IllegalArgumentException("accountType is null");
+        checkReadAccountsPermission();
+        UserAccounts userAccounts = getUserAccountsForCaller();
+        int callingUid = Binder.getCallingUid();
+        long identityToken = clearCallingIdentity();
+        try {
+            if (features == null || features.length == 0) {
+                Account[] accounts;
+                synchronized (userAccounts.cacheLock) {
+                    accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid, null);
+                }
+                Bundle result = new Bundle();
+                result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
+                onResult(response, result);
+                return;
+            }
+            new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features,
+                    callingUid).bind();
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    private long getAccountIdLocked(SQLiteDatabase db, Account account) {
+        Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
+                "name=? AND type=?", new String[]{account.name, account.type}, null, null, null);
+        try {
+            if (cursor.moveToNext()) {
+                return cursor.getLong(0);
+            }
+            return -1;
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
+        Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
+                EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
+                new String[]{key}, null, null, null);
+        try {
+            if (cursor.moveToNext()) {
+                return cursor.getLong(0);
+            }
+            return -1;
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private abstract class Session extends IAccountAuthenticatorResponse.Stub
+            implements IBinder.DeathRecipient, ServiceConnection {
+        IAccountManagerResponse mResponse;
+        final String mAccountType;
+        final boolean mExpectActivityLaunch;
+        final long mCreationTime;
+
+        public int mNumResults = 0;
+        private int mNumRequestContinued = 0;
+        private int mNumErrors = 0;
+
+        IAccountAuthenticator mAuthenticator = null;
+
+        private final boolean mStripAuthTokenFromResult;
+        protected final UserAccounts mAccounts;
+
+        public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
+                boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
+            super();
+            //if (response == null) throw new IllegalArgumentException("response is null");
+            if (accountType == null) throw new IllegalArgumentException("accountType is null");
+            mAccounts = accounts;
+            mStripAuthTokenFromResult = stripAuthTokenFromResult;
+            mResponse = response;
+            mAccountType = accountType;
+            mExpectActivityLaunch = expectActivityLaunch;
+            mCreationTime = SystemClock.elapsedRealtime();
+            synchronized (mSessions) {
+                mSessions.put(toString(), this);
+            }
+            if (response != null) {
+                try {
+                    response.asBinder().linkToDeath(this, 0 /* flags */);
+                } catch (RemoteException e) {
+                    mResponse = null;
+                    binderDied();
+                }
+            }
+        }
+
+        IAccountManagerResponse getResponseAndClose() {
+            if (mResponse == null) {
+                // this session has already been closed
+                return null;
+            }
+            IAccountManagerResponse response = mResponse;
+            close(); // this clears mResponse so we need to save the response before this call
+            return response;
+        }
+
+        private void close() {
+            synchronized (mSessions) {
+                if (mSessions.remove(toString()) == null) {
+                    // the session was already closed, so bail out now
+                    return;
+                }
+            }
+            if (mResponse != null) {
+                // stop listening for response deaths
+                mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
+
+                // clear this so that we don't accidentally send any further results
+                mResponse = null;
+            }
+            cancelTimeout();
+            unbind();
+        }
+
+        @Override
+        public void binderDied() {
+            mResponse = null;
+            close();
+        }
+
+        protected String toDebugString() {
+            return toDebugString(SystemClock.elapsedRealtime());
+        }
+
+        protected String toDebugString(long now) {
+            return "Session: expectLaunch " + mExpectActivityLaunch
+                    + ", connected " + (mAuthenticator != null)
+                    + ", stats (" + mNumResults + "/" + mNumRequestContinued
+                    + "/" + mNumErrors + ")"
+                    + ", lifetime " + ((now - mCreationTime) / 1000.0);
+        }
+
+        void bind() {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
+            }
+            if (!bindToAuthenticator(mAccountType)) {
+                Log.d(TAG, "bind attempt failed for " + toDebugString());
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
+            }
+        }
+
+        private void unbind() {
+            if (mAuthenticator != null) {
+                mAuthenticator = null;
+                mContext.unbindService(this);
+            }
+        }
+
+        public void scheduleTimeout() {
+            mMessageHandler.sendMessageDelayed(
+                    mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
+        }
+
+        public void cancelTimeout() {
+            mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
+            try {
+                run();
+            } catch (RemoteException e) {
+                onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                        "remote exception");
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mAuthenticator = null;
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "disconnected");
+                } catch (RemoteException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Session.onServiceDisconnected: "
+                                + "caught RemoteException while responding", e);
+                    }
+                }
+            }
+        }
+
+        public abstract void run() throws RemoteException;
+
+        public void onTimedOut() {
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                try {
+                    response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
+                            "timeout");
+                } catch (RemoteException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
+                                e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onResult(Bundle result) {
+            mNumResults++;
+            Intent intent = null;
+            if (result != null
+                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+                /*
+                 * The Authenticator API allows third party authenticators to
+                 * supply arbitrary intents to other apps that they can run,
+                 * this can be very bad when those apps are in the system like
+                 * the System Settings.
+                 */
+                int authenticatorUid = Binder.getCallingUid();
+                long bid = Binder.clearCallingIdentity();
+                try {
+                    PackageManager pm = mContext.getPackageManager();
+                    ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                    int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+                    if (PackageManager.SIGNATURE_MATCH !=
+                            pm.checkSignatures(authenticatorUid, targetUid)) {
+                        throw new SecurityException(
+                                "Activity to be started with KEY_INTENT must " +
+                               "share Authenticator's signatures");
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(bid);
+                }
+            }
+            if (result != null
+                    && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
+                String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
+                String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
+                if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+                    Account account = new Account(accountName, accountType);
+                    cancelNotification(getSigninRequiredNotificationId(mAccounts, account),
+                            new UserHandle(mAccounts.userId));
+                }
+            }
+            IAccountManagerResponse response;
+            if (mExpectActivityLaunch && result != null
+                    && result.containsKey(AccountManager.KEY_INTENT)) {
+                response = mResponse;
+            } else {
+                response = getResponseAndClose();
+            }
+            if (response != null) {
+                try {
+                    if (result == null) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, getClass().getSimpleName()
+                                    + " calling onError() on response " + response);
+                        }
+                        response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                "null bundle returned");
+                    } else {
+                        if (mStripAuthTokenFromResult) {
+                            result.remove(AccountManager.KEY_AUTHTOKEN);
+                        }
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, getClass().getSimpleName()
+                                    + " calling onResult() on response " + response);
+                        }
+                        if ((result.getInt(AccountManager.KEY_ERROR_CODE, -1) > 0) &&
+                                (intent == null)) {
+                            // All AccountManager error codes are greater than 0
+                            response.onError(result.getInt(AccountManager.KEY_ERROR_CODE),
+                                    result.getString(AccountManager.KEY_ERROR_MESSAGE));
+                        } else {
+                            response.onResult(result);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    // if the caller is dead then there is no one to care about remote exceptions
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "failure while notifying response", e);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onRequestContinued() {
+            mNumRequestContinued++;
+        }
+
+        @Override
+        public void onError(int errorCode, String errorMessage) {
+            mNumErrors++;
+            IAccountManagerResponse response = getResponseAndClose();
+            if (response != null) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, getClass().getSimpleName()
+                            + " calling onError() on response " + response);
+                }
+                try {
+                    response.onError(errorCode, errorMessage);
+                } catch (RemoteException e) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
+                    }
+                }
+            } else {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Session.onError: already closed");
+                }
+            }
+        }
+
+        /**
+         * find the component name for the authenticator and initiate a bind
+         * if no authenticator or the bind fails then return false, otherwise return true
+         */
+        private boolean bindToAuthenticator(String authenticatorType) {
+            final AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
+            authenticatorInfo = mAuthenticatorCache.getServiceInfo(
+                    AuthenticatorDescription.newKey(authenticatorType), mAccounts.userId);
+            if (authenticatorInfo == null) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "there is no authenticator for " + authenticatorType
+                            + ", bailing out");
+                }
+                return false;
+            }
+
+            Intent intent = new Intent();
+            intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
+            intent.setComponent(authenticatorInfo.componentName);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
+            }
+            if (!mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
+                    new UserHandle(mAccounts.userId))) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
+                }
+                return false;
+            }
+
+
+            return true;
+        }
+    }
+
+    private class MessageHandler extends Handler {
+        MessageHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_TIMED_OUT:
+                    Session session = (Session)msg.obj;
+                    session.onTimedOut();
+                    break;
+
+                case MESSAGE_COPY_SHARED_ACCOUNT:
+                    copyAccountToUser((Account) msg.obj, msg.arg1, msg.arg2);
+                    break;
+
+                default:
+                    throw new IllegalStateException("unhandled message: " + msg.what);
+            }
+        }
+    }
+
+    private static String getDatabaseName(int userId) {
+        File systemDir = Environment.getSystemSecureDirectory();
+        File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
+        if (userId == 0) {
+            // Migrate old file, if it exists, to the new location.
+            // Make sure the new file doesn't already exist. A dummy file could have been
+            // accidentally created in the old location, causing the new one to become corrupted
+            // as well.
+            File oldFile = new File(systemDir, DATABASE_NAME);
+            if (oldFile.exists() && !databaseFile.exists()) {
+                // Check for use directory; create if it doesn't exist, else renameTo will fail
+                File userDir = Environment.getUserSystemDirectory(userId);
+                if (!userDir.exists()) {
+                    if (!userDir.mkdirs()) {
+                        throw new IllegalStateException("User dir cannot be created: " + userDir);
+                    }
+                }
+                if (!oldFile.renameTo(databaseFile)) {
+                    throw new IllegalStateException("User dir cannot be migrated: " + databaseFile);
+                }
+            }
+        }
+        return databaseFile.getPath();
+    }
+
+    static class DatabaseHelper extends SQLiteOpenHelper {
+
+        public DatabaseHelper(Context context, int userId) {
+            super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
+        }
+
+        /**
+         * This call needs to be made while the mCacheLock is held. The way to
+         * ensure this is to get the lock any time a method is called ont the DatabaseHelper
+         * @param db The database.
+         */
+        @Override
+        public void onCreate(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
+                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
+                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+                    + ACCOUNTS_PASSWORD + " TEXT, "
+                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+
+            db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
+                    + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,  "
+                    + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+                    + AUTHTOKENS_TYPE + " TEXT NOT NULL,  "
+                    + AUTHTOKENS_AUTHTOKEN + " TEXT,  "
+                    + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
+
+            createGrantsTable(db);
+
+            db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
+                    + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + EXTRAS_ACCOUNTS_ID + " INTEGER, "
+                    + EXTRAS_KEY + " TEXT NOT NULL, "
+                    + EXTRAS_VALUE + " TEXT, "
+                    + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
+
+            db.execSQL("CREATE TABLE " + TABLE_META + " ( "
+                    + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
+                    + META_VALUE + " TEXT)");
+
+            createSharedAccountsTable(db);
+
+            createAccountsDeletionTrigger(db);
+        }
+
+        private void createSharedAccountsTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
+                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
+                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
+                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
+        }
+
+        private void createAccountsDeletionTrigger(SQLiteDatabase db) {
+            db.execSQL(""
+                    + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
+                    + " BEGIN"
+                    + "   DELETE FROM " + TABLE_AUTHTOKENS
+                    + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                    + "   DELETE FROM " + TABLE_EXTRAS
+                    + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                    + "   DELETE FROM " + TABLE_GRANTS
+                    + "     WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                    + " END");
+        }
+
+        private void createGrantsTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
+                    + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+                    + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL,  "
+                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
+                    + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
+                    +   "," + GRANTS_GRANTEE_UID + "))");
+        }
+
+        @Override
+        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
+
+            if (oldVersion == 1) {
+                // no longer need to do anything since the work is done
+                // when upgrading from version 2
+                oldVersion++;
+            }
+
+            if (oldVersion == 2) {
+                createGrantsTable(db);
+                db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
+                createAccountsDeletionTrigger(db);
+                oldVersion++;
+            }
+
+            if (oldVersion == 3) {
+                db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
+                        " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
+                oldVersion++;
+            }
+
+            if (oldVersion == 4) {
+                createSharedAccountsTable(db);
+                oldVersion++;
+            }
+
+            if (oldVersion != newVersion) {
+                Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
+            }
+        }
+
+        @Override
+        public void onOpen(SQLiteDatabase db) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
+        }
+    }
+
+    public IBinder onBind(Intent intent) {
+        return asBinder();
+    }
+
+    /**
+     * Searches array of arguments for the specified string
+     * @param args array of argument strings
+     * @param value value to search for
+     * @return true if the value is contained in the array
+     */
+    private static boolean scanArgs(String[] args, String value) {
+        if (args != null) {
+            for (String arg : args) {
+                if (value.equals(arg)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            fout.println("Permission Denial: can't dump AccountsManager from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+        final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(fout, "  ");
+
+        final List<UserInfo> users = getUserManager().getUsers();
+        for (UserInfo user : users) {
+            ipw.println("User " + user + ":");
+            ipw.increaseIndent();
+            dumpUser(getUserAccounts(user.id), fd, ipw, args, isCheckinRequest);
+            ipw.println();
+            ipw.decreaseIndent();
+        }
+    }
+
+    private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
+            String[] args, boolean isCheckinRequest) {
+        synchronized (userAccounts.cacheLock) {
+            final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
+
+            if (isCheckinRequest) {
+                // This is a checkin request. *Only* upload the account types and the count of each.
+                Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
+                        null, null, ACCOUNTS_TYPE, null, null);
+                try {
+                    while (cursor.moveToNext()) {
+                        // print type,count
+                        fout.println(cursor.getString(0) + "," + cursor.getString(1));
+                    }
+                } finally {
+                    if (cursor != null) {
+                        cursor.close();
+                    }
+                }
+            } else {
+                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+                        Process.myUid(), null);
+                fout.println("Accounts: " + accounts.length);
+                for (Account account : accounts) {
+                    fout.println("  " + account);
+                }
+
+                fout.println();
+                synchronized (mSessions) {
+                    final long now = SystemClock.elapsedRealtime();
+                    fout.println("Active Sessions: " + mSessions.size());
+                    for (Session session : mSessions.values()) {
+                        fout.println("  " + session.toDebugString(now));
+                    }
+                }
+
+                fout.println();
+                mAuthenticatorCache.dump(fd, fout, args, userAccounts.userId);
+            }
+        }
+    }
+
+    private void doNotification(UserAccounts accounts, Account account, CharSequence message,
+            Intent intent, int userId) {
+        long identityToken = clearCallingIdentity();
+        try {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "doNotification: " + message + " intent:" + intent);
+            }
+
+            if (intent.getComponent() != null &&
+                    GrantCredentialsPermissionActivity.class.getName().equals(
+                            intent.getComponent().getClassName())) {
+                createNoCredentialsPermissionNotification(account, intent, userId);
+            } else {
+                final Integer notificationId = getSigninRequiredNotificationId(accounts, account);
+                intent.addCategory(String.valueOf(notificationId));
+                Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
+                        0 /* when */);
+                UserHandle user = new UserHandle(userId);
+                final String notificationTitleFormat =
+                        mContext.getText(R.string.notification_title).toString();
+                n.setLatestEventInfo(mContext,
+                        String.format(notificationTitleFormat, account.name),
+                        message, PendingIntent.getActivityAsUser(
+                        mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT,
+                        null, user));
+                installNotification(notificationId, n, user);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    protected void installNotification(final int notificationId, final Notification n,
+            UserHandle user) {
+        ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .notifyAsUser(null, notificationId, n, user);
+    }
+
+    protected void cancelNotification(int id, UserHandle user) {
+        long identityToken = clearCallingIdentity();
+        try {
+            ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .cancelAsUser(null, id, user);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /** Succeeds if any of the specified permissions are granted. */
+    private void checkBinderPermission(String... permissions) {
+        final int uid = Binder.getCallingUid();
+
+        for (String perm : permissions) {
+            if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "  caller uid " + uid + " has " + perm);
+                }
+                return;
+            }
+        }
+
+        String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
+        Log.w(TAG, "  " + msg);
+        throw new SecurityException(msg);
+    }
+
+    private int handleIncomingUser(int userId) {
+        try {
+            return ActivityManagerNative.getDefault().handleIncomingUser(
+                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, true, "", null);
+        } catch (RemoteException re) {
+            // Shouldn't happen, local.
+        }
+        return userId;
+    }
+
+    private boolean isPrivileged(int callingUid) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+
+        final PackageManager userPackageManager;
+        try {
+            userPackageManager = mContext.createPackageContextAsUser(
+                    "android", 0, new UserHandle(callingUserId)).getPackageManager();
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+
+        String[] packages = userPackageManager.getPackagesForUid(callingUid);
+        for (String name : packages) {
+            try {
+                PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */);
+                if (packageInfo != null
+                        && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
+        final boolean isPrivileged = isPrivileged(callerUid);
+        final boolean fromAuthenticator = account != null
+                && hasAuthenticatorUid(account.type, callerUid);
+        final boolean hasExplicitGrants = account != null
+                && hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
+                    + callerUid + ", " + account
+                    + ": is authenticator? " + fromAuthenticator
+                    + ", has explicit permission? " + hasExplicitGrants);
+        }
+        return fromAuthenticator || hasExplicitGrants || isPrivileged;
+    }
+
+    private boolean hasAuthenticatorUid(String accountType, int callingUid) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
+                mAuthenticatorCache.getAllServices(callingUserId)) {
+            if (serviceInfo.type.type.equals(accountType)) {
+                return (serviceInfo.uid == callingUid) ||
+                        (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
+                                == PackageManager.SIGNATURE_MATCH);
+            }
+        }
+        return false;
+    }
+
+    private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
+            int callerUid) {
+        if (callerUid == Process.SYSTEM_UID) {
+            return true;
+        }
+        UserAccounts accounts = getUserAccountsForCaller();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+            String[] args = { String.valueOf(callerUid), authTokenType,
+                    account.name, account.type};
+            final boolean permissionGranted =
+                    DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
+            if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
+                // TODO: Skip this check when running automated tests. Replace this
+                // with a more general solution.
+                Log.d(TAG, "no credentials permission for usage of " + account + ", "
+                        + authTokenType + " by uid " + callerUid
+                        + " but ignoring since device is in test harness.");
+                return true;
+            }
+            return permissionGranted;
+        }
+    }
+
+    private void checkCallingUidAgainstAuthenticator(Account account) {
+        final int uid = Binder.getCallingUid();
+        if (account == null || !hasAuthenticatorUid(account.type, uid)) {
+            String msg = "caller uid " + uid + " is different than the authenticator's uid";
+            Log.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
+        }
+    }
+
+    private void checkAuthenticateAccountsPermission(Account account) {
+        checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
+        checkCallingUidAgainstAuthenticator(account);
+    }
+
+    private void checkReadAccountsPermission() {
+        checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
+    }
+
+    private void checkManageAccountsPermission() {
+        checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
+    }
+
+    private void checkManageAccountsOrUseCredentialsPermissions() {
+        checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
+                Manifest.permission.USE_CREDENTIALS);
+    }
+
+    private boolean canUserModifyAccounts(int callingUid) {
+        if (callingUid != Process.myUid()) {
+            if (getUserManager().getUserRestrictions(
+                    new UserHandle(UserHandle.getUserId(callingUid)))
+                    .getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void updateAppPermission(Account account, String authTokenType, int uid, boolean value)
+            throws RemoteException {
+        final int callingUid = getCallingUid();
+
+        if (callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException();
+        }
+
+        if (value) {
+            grantAppPermission(account, authTokenType, uid);
+        } else {
+            revokeAppPermission(account, authTokenType, uid);
+        }
+    }
+
+    /**
+     * Allow callers with the given uid permission to get credentials for account/authTokenType.
+     * <p>
+     * Although this is public it can only be accessed via the AccountManagerService object
+     * which is in the system. This means we don't need to protect it with permissions.
+     * @hide
+     */
+    private void grantAppPermission(Account account, String authTokenType, int uid) {
+        if (account == null || authTokenType == null) {
+            Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
+            return;
+        }
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                long accountId = getAccountIdLocked(db, account);
+                if (accountId >= 0) {
+                    ContentValues values = new ContentValues();
+                    values.put(GRANTS_ACCOUNTS_ID, accountId);
+                    values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
+                    values.put(GRANTS_GRANTEE_UID, uid);
+                    db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
+                    db.setTransactionSuccessful();
+                }
+            } finally {
+                db.endTransaction();
+            }
+            cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+                    new UserHandle(accounts.userId));
+        }
+    }
+
+    /**
+     * Don't allow callers with the given uid permission to get credentials for
+     * account/authTokenType.
+     * <p>
+     * Although this is public it can only be accessed via the AccountManagerService object
+     * which is in the system. This means we don't need to protect it with permissions.
+     * @hide
+     */
+    private void revokeAppPermission(Account account, String authTokenType, int uid) {
+        if (account == null || authTokenType == null) {
+            Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
+            return;
+        }
+        UserAccounts accounts = getUserAccounts(UserHandle.getUserId(uid));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            db.beginTransaction();
+            try {
+                long accountId = getAccountIdLocked(db, account);
+                if (accountId >= 0) {
+                    db.delete(TABLE_GRANTS,
+                            GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
+                                    + GRANTS_GRANTEE_UID + "=?",
+                            new String[]{String.valueOf(accountId), authTokenType,
+                                    String.valueOf(uid)});
+                    db.setTransactionSuccessful();
+                }
+            } finally {
+                db.endTransaction();
+            }
+            cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
+                    new UserHandle(accounts.userId));
+        }
+    }
+
+    static final private String stringArrayToString(String[] value) {
+        return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
+    }
+
+    private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) {
+        final Account[] oldAccountsForType = accounts.accountCache.get(account.type);
+        if (oldAccountsForType != null) {
+            ArrayList<Account> newAccountsList = new ArrayList<Account>();
+            for (Account curAccount : oldAccountsForType) {
+                if (!curAccount.equals(account)) {
+                    newAccountsList.add(curAccount);
+                }
+            }
+            if (newAccountsList.isEmpty()) {
+                accounts.accountCache.remove(account.type);
+            } else {
+                Account[] newAccountsForType = new Account[newAccountsList.size()];
+                newAccountsForType = newAccountsList.toArray(newAccountsForType);
+                accounts.accountCache.put(account.type, newAccountsForType);
+            }
+        }
+        accounts.userDataCache.remove(account);
+        accounts.authTokenCache.remove(account);
+    }
+
+    /**
+     * This assumes that the caller has already checked that the account is not already present.
+     */
+    private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) {
+        Account[] accountsForType = accounts.accountCache.get(account.type);
+        int oldLength = (accountsForType != null) ? accountsForType.length : 0;
+        Account[] newAccountsForType = new Account[oldLength + 1];
+        if (accountsForType != null) {
+            System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
+        }
+        newAccountsForType[oldLength] = account;
+        accounts.accountCache.put(account.type, newAccountsForType);
+    }
+
+    private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
+            int callingUid, String callingPackage) {
+        if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
+                || callingUid == Process.myUid()) {
+            return unfiltered;
+        }
+        UserInfo user = mUserManager.getUserInfo(userAccounts.userId);
+        if (user != null && user.isRestricted()) {
+            String[] packages = mPackageManager.getPackagesForUid(callingUid);
+            // If any of the packages is a white listed package, return the full set,
+            // otherwise return non-shared accounts only.
+            // This might be a temporary way to specify a whitelist
+            String whiteList = mContext.getResources().getString(
+                    com.android.internal.R.string.config_appsAuthorizedForSharedAccounts);
+            for (String packageName : packages) {
+                if (whiteList.contains(";" + packageName + ";")) {
+                    return unfiltered;
+                }
+            }
+            ArrayList<Account> allowed = new ArrayList<Account>();
+            Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
+            if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+            String requiredAccountType = "";
+            try {
+                // If there's an explicit callingPackage specified, check if that package
+                // opted in to see restricted accounts.
+                if (callingPackage != null) {
+                    PackageInfo pi = mPackageManager.getPackageInfo(callingPackage, 0);
+                    if (pi != null && pi.restrictedAccountType != null) {
+                        requiredAccountType = pi.restrictedAccountType;
+                    }
+                } else {
+                    // Otherwise check if the callingUid has a package that has opted in
+                    for (String packageName : packages) {
+                        PackageInfo pi = mPackageManager.getPackageInfo(packageName, 0);
+                        if (pi != null && pi.restrictedAccountType != null) {
+                            requiredAccountType = pi.restrictedAccountType;
+                            break;
+                        }
+                    }
+                }
+            } catch (NameNotFoundException nnfe) {
+            }
+            for (Account account : unfiltered) {
+                if (account.type.equals(requiredAccountType)) {
+                    allowed.add(account);
+                } else {
+                    boolean found = false;
+                    for (Account shared : sharedAccounts) {
+                        if (shared.equals(account)) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        allowed.add(account);
+                    }
+                }
+            }
+            Account[] filtered = new Account[allowed.size()];
+            allowed.toArray(filtered);
+            return filtered;
+        } else {
+            return unfiltered;
+        }
+    }
+
+    /*
+     * packageName can be null. If not null, it should be used to filter out restricted accounts
+     * that the package is not allowed to access.
+     */
+    protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
+            int callingUid, String callingPackage) {
+        if (accountType != null) {
+            final Account[] accounts = userAccounts.accountCache.get(accountType);
+            if (accounts == null) {
+                return EMPTY_ACCOUNT_ARRAY;
+            } else {
+                return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+                        callingUid, callingPackage);
+            }
+        } else {
+            int totalLength = 0;
+            for (Account[] accounts : userAccounts.accountCache.values()) {
+                totalLength += accounts.length;
+            }
+            if (totalLength == 0) {
+                return EMPTY_ACCOUNT_ARRAY;
+            }
+            Account[] accounts = new Account[totalLength];
+            totalLength = 0;
+            for (Account[] accountsOfType : userAccounts.accountCache.values()) {
+                System.arraycopy(accountsOfType, 0, accounts, totalLength,
+                        accountsOfType.length);
+                totalLength += accountsOfType.length;
+            }
+            return filterSharedAccounts(userAccounts, accounts, callingUid, callingPackage);
+        }
+    }
+
+    protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+            Account account, String key, String value) {
+        HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+        if (userDataForAccount == null) {
+            userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+            accounts.userDataCache.put(account, userDataForAccount);
+        }
+        if (value == null) {
+            userDataForAccount.remove(key);
+        } else {
+            userDataForAccount.put(key, value);
+        }
+    }
+
+    protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+            Account account, String key, String value) {
+        HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+        if (authTokensForAccount == null) {
+            authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
+            accounts.authTokenCache.put(account, authTokensForAccount);
+        }
+        if (value == null) {
+            authTokensForAccount.remove(key);
+        } else {
+            authTokensForAccount.put(key, value);
+        }
+    }
+
+    protected String readAuthTokenInternal(UserAccounts accounts, Account account,
+            String authTokenType) {
+        synchronized (accounts.cacheLock) {
+            HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
+            if (authTokensForAccount == null) {
+                // need to populate the cache for this account
+                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+                authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
+                accounts.authTokenCache.put(account, authTokensForAccount);
+            }
+            return authTokensForAccount.get(authTokenType);
+        }
+    }
+
+    protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
+        synchronized (accounts.cacheLock) {
+            HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
+            if (userDataForAccount == null) {
+                // need to populate the cache for this account
+                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+                userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
+                accounts.userDataCache.put(account, userDataForAccount);
+            }
+            return userDataForAccount.get(key);
+        }
+    }
+
+    protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
+            final SQLiteDatabase db, Account account) {
+        HashMap<String, String> userDataForAccount = new HashMap<String, String>();
+        Cursor cursor = db.query(TABLE_EXTRAS,
+                COLUMNS_EXTRAS_KEY_AND_VALUE,
+                SELECTION_USERDATA_BY_ACCOUNT,
+                new String[]{account.name, account.type},
+                null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                final String tmpkey = cursor.getString(0);
+                final String value = cursor.getString(1);
+                userDataForAccount.put(tmpkey, value);
+            }
+        } finally {
+            cursor.close();
+        }
+        return userDataForAccount;
+    }
+
+    protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
+            final SQLiteDatabase db, Account account) {
+        HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
+        Cursor cursor = db.query(TABLE_AUTHTOKENS,
+                COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
+                SELECTION_AUTHTOKENS_BY_ACCOUNT,
+                new String[]{account.name, account.type},
+                null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                final String type = cursor.getString(0);
+                final String authToken = cursor.getString(1);
+                authTokensForAccount.put(type, authToken);
+            }
+        } finally {
+            cursor.close();
+        }
+        return authTokensForAccount;
+    }
+}
diff --git a/services/core/java/com/android/server/accounts/IAccountAuthenticatorCache.java b/services/core/java/com/android/server/accounts/IAccountAuthenticatorCache.java
new file mode 100644
index 0000000..bb09687
--- /dev/null
+++ b/services/core/java/com/android/server/accounts/IAccountAuthenticatorCache.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2010 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.accounts;
+
+import android.accounts.AuthenticatorDescription;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.os.Handler;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * An interface to the Authenticator specialization of RegisteredServicesCache. The use of
+ * this interface by the AccountManagerService makes it easier to unit test it.
+ * @hide
+ */
+public interface IAccountAuthenticatorCache {
+    /**
+     * Accessor for the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
+     * matched the specified {@link android.accounts.AuthenticatorDescription} or null
+     * if none match.
+     * @param type the authenticator type to return
+     * @return the {@link android.content.pm.RegisteredServicesCache.ServiceInfo} that
+     * matches the account type or null if none is present
+     */
+    RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> getServiceInfo(
+            AuthenticatorDescription type, int userId);
+
+    /**
+     * @return A copy of a Collection of all the current Authenticators.
+     */
+    Collection<RegisteredServicesCache.ServiceInfo<AuthenticatorDescription>> getAllServices(
+            int userId);
+
+    /**
+     * Dumps the state of the cache. See
+     * {@link android.os.Binder#dump(java.io.FileDescriptor, java.io.PrintWriter, String[])}
+     */
+    void dump(FileDescriptor fd, PrintWriter fout, String[] args, int userId);
+
+    /**
+     * Sets a listener that will be notified whenever the authenticator set changes
+     * @param listener the listener to notify, or null
+     * @param handler the {@link Handler} on which the notification will be posted. If null
+     * the notification will be posted on the main thread.
+     */
+    void setListener(RegisteredServicesCacheListener<AuthenticatorDescription> listener,
+            Handler handler);
+
+    void invalidateCache(int userId);
+}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
new file mode 100644
index 0000000..89bddc6
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -0,0 +1,2663 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.util.ArrayMap;
+import com.android.internal.app.ProcessStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.TransferPipe;
+import com.android.server.am.ActivityManagerService.ItemMatcher;
+import com.android.server.am.ActivityManagerService.NeededUriGrants;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.IApplicationThread;
+import android.app.IServiceConnection;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+public final class ActiveServices {
+    static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE;
+    static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING;
+    static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE;
+    static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE;
+    static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
+    static final String TAG = ActivityManagerService.TAG;
+    static final String TAG_MU = ActivityManagerService.TAG_MU;
+
+    // How long we wait for a service to finish executing.
+    static final int SERVICE_TIMEOUT = 20*1000;
+
+    // How long we wait for a service to finish executing.
+    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
+
+    // How long a service needs to be running until restarting its process
+    // is no longer considered to be a relaunch of the service.
+    static final int SERVICE_RESTART_DURATION = 1*1000;
+
+    // How long a service needs to be running until it will start back at
+    // SERVICE_RESTART_DURATION after being killed.
+    static final int SERVICE_RESET_RUN_DURATION = 60*1000;
+
+    // Multiplying factor to increase restart duration time by, for each time
+    // a service is killed before it has run for SERVICE_RESET_RUN_DURATION.
+    static final int SERVICE_RESTART_DURATION_FACTOR = 4;
+
+    // The minimum amount of time between restarting services that we allow.
+    // That is, when multiple services are restarting, we won't allow each
+    // to restart less than this amount of time from the last one.
+    static final int SERVICE_MIN_RESTART_TIME_BETWEEN = 10*1000;
+
+    // Maximum amount of time for there to be no activity on a service before
+    // we consider it non-essential and allow its process to go on the
+    // LRU background list.
+    static final int MAX_SERVICE_INACTIVITY = 30*60*1000;
+
+    // How long we wait for a background started service to stop itself before
+    // allowing the next pending start to run.
+    static final int BG_START_TIMEOUT = 15*1000;
+
+    final ActivityManagerService mAm;
+
+    // Maximum number of services that we allow to start in the background
+    // at the same time.
+    final int mMaxStartingBackground;
+
+    final SparseArray<ServiceMap> mServiceMap = new SparseArray<ServiceMap>();
+
+    /**
+     * All currently bound service connections.  Keys are the IBinder of
+     * the client's IServiceConnection.
+     */
+    final ArrayMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections
+            = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>();
+
+    /**
+     * List of services that we have been asked to start,
+     * but haven't yet been able to.  It is used to hold start requests
+     * while waiting for their corresponding application thread to get
+     * going.
+     */
+    final ArrayList<ServiceRecord> mPendingServices
+            = new ArrayList<ServiceRecord>();
+
+    /**
+     * List of services that are scheduled to restart following a crash.
+     */
+    final ArrayList<ServiceRecord> mRestartingServices
+            = new ArrayList<ServiceRecord>();
+
+    /**
+     * List of services that are in the process of being destroyed.
+     */
+    final ArrayList<ServiceRecord> mDestroyingServices
+            = new ArrayList<ServiceRecord>();
+
+    static final class DelayingProcess extends ArrayList<ServiceRecord> {
+        long timeoout;
+    }
+
+    /**
+     * Information about services for a single user.
+     */
+    class ServiceMap extends Handler {
+        final int mUserId;
+        final ArrayMap<ComponentName, ServiceRecord> mServicesByName
+                = new ArrayMap<ComponentName, ServiceRecord>();
+        final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent
+                = new ArrayMap<Intent.FilterComparison, ServiceRecord>();
+
+        final ArrayList<ServiceRecord> mDelayedStartList
+                = new ArrayList<ServiceRecord>();
+        /* XXX eventually I'd like to have this based on processes instead of services.
+         * That is, if we try to start two services in a row both running in the same
+         * process, this should be one entry in mStartingBackground for that one process
+         * that remains until all services in it are done.
+        final ArrayMap<ProcessRecord, DelayingProcess> mStartingBackgroundMap
+                = new ArrayMap<ProcessRecord, DelayingProcess>();
+        final ArrayList<DelayingProcess> mStartingProcessList
+                = new ArrayList<DelayingProcess>();
+        */
+
+        final ArrayList<ServiceRecord> mStartingBackground
+                = new ArrayList<ServiceRecord>();
+
+        static final int MSG_BG_START_TIMEOUT = 1;
+
+        ServiceMap(Looper looper, int userId) {
+            super(looper);
+            mUserId = userId;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_BG_START_TIMEOUT: {
+                    synchronized (mAm) {
+                        rescheduleDelayedStarts();
+                    }
+                } break;
+            }
+        }
+
+        void ensureNotStartingBackground(ServiceRecord r) {
+            if (mStartingBackground.remove(r)) {
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r);
+                rescheduleDelayedStarts();
+            }
+            if (mDelayedStartList.remove(r)) {
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer delaying start: " + r);
+            }
+        }
+
+        void rescheduleDelayedStarts() {
+            removeMessages(MSG_BG_START_TIMEOUT);
+            final long now = SystemClock.uptimeMillis();
+            for (int i=0, N=mStartingBackground.size(); i<N; i++) {
+                ServiceRecord r = mStartingBackground.get(i);
+                if (r.startingBgTimeout <= now) {
+                    Slog.i(TAG, "Waited long enough for: " + r);
+                    mStartingBackground.remove(i);
+                    N--;
+                }
+            }
+            while (mDelayedStartList.size() > 0
+                    && mStartingBackground.size() < mMaxStartingBackground) {
+                ServiceRecord r = mDelayedStartList.remove(0);
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r);
+                if (r.pendingStarts.size() <= 0) {
+                    Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested
+                            + " delayedStop=" + r.delayedStop);
+                }
+                if (DEBUG_DELAYED_SERVICE) {
+                    if (mDelayedStartList.size() > 0) {
+                        Slog.v(TAG, "Remaining delayed list:");
+                        for (int i=0; i<mDelayedStartList.size(); i++) {
+                            Slog.v(TAG, "  #" + i + ": " + mDelayedStartList.get(i));
+                        }
+                    }
+                }
+                r.delayed = false;
+                startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
+            }
+            if (mStartingBackground.size() > 0) {
+                ServiceRecord next = mStartingBackground.get(0);
+                long when = next.startingBgTimeout > now ? next.startingBgTimeout : now;
+                if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Top bg start is " + next
+                        + ", can delay others up to " + when);
+                Message msg = obtainMessage(MSG_BG_START_TIMEOUT);
+                sendMessageAtTime(msg, when);
+            }
+            if (mStartingBackground.size() < mMaxStartingBackground) {
+                mAm.backgroundServicesFinishedLocked(mUserId);
+            }
+        }
+    }
+
+    public ActiveServices(ActivityManagerService service) {
+        mAm = service;
+        int maxBg = 0;
+        try {
+            maxBg = Integer.parseInt(SystemProperties.get("ro.config.max_starting_bg", "0"));
+        } catch(RuntimeException e) {
+        }
+        mMaxStartingBackground = maxBg > 0 ? maxBg : ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
+    }
+
+    ServiceRecord getServiceByName(ComponentName name, int callingUser) {
+        // TODO: Deal with global services
+        if (DEBUG_MU)
+            Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser);
+        return getServiceMap(callingUser).mServicesByName.get(name);
+    }
+
+    boolean hasBackgroundServices(int callingUser) {
+        ServiceMap smap = mServiceMap.get(callingUser);
+        return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false;
+    }
+
+    private ServiceMap getServiceMap(int callingUser) {
+        ServiceMap smap = mServiceMap.get(callingUser);
+        if (smap == null) {
+            smap = new ServiceMap(mAm.mHandler.getLooper(), callingUser);
+            mServiceMap.put(callingUser, smap);
+        }
+        return smap;
+    }
+
+    ArrayMap<ComponentName, ServiceRecord> getServices(int callingUser) {
+        return getServiceMap(callingUser).mServicesByName;
+    }
+
+    ComponentName startServiceLocked(IApplicationThread caller,
+            Intent service, String resolvedType,
+            int callingPid, int callingUid, int userId) {
+        if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service
+                + " type=" + resolvedType + " args=" + service.getExtras());
+
+        final boolean callerFg;
+        if (caller != null) {
+            final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+            if (callerApp == null) {
+                throw new SecurityException(
+                        "Unable to find app for caller " + caller
+                        + " (pid=" + Binder.getCallingPid()
+                        + ") when starting service " + service);
+            }
+            callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
+        } else {
+            callerFg = true;
+        }
+
+
+        ServiceLookupResult res =
+            retrieveServiceLocked(service, resolvedType,
+                    callingPid, callingUid, userId, true, callerFg);
+        if (res == null) {
+            return null;
+        }
+        if (res.record == null) {
+            return new ComponentName("!", res.permission != null
+                    ? res.permission : "private to package");
+        }
+        ServiceRecord r = res.record;
+        NeededUriGrants neededGrants = mAm.checkGrantUriPermissionFromIntentLocked(
+                callingUid, r.packageName, service, service.getFlags(), null);
+        if (unscheduleServiceRestartLocked(r, callingUid, false)) {
+            if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r);
+        }
+        r.lastActivity = SystemClock.uptimeMillis();
+        r.startRequested = true;
+        r.delayedStop = false;
+        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
+                service, neededGrants));
+
+        final ServiceMap smap = getServiceMap(r.userId);
+        boolean addToStarting = false;
+        if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) {
+            ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
+            if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
+                // If this is not coming from a foreground caller, then we may want
+                // to delay the start if there are already other background services
+                // that are starting.  This is to avoid process start spam when lots
+                // of applications are all handling things like connectivity broadcasts.
+                // We only do this for cached processes, because otherwise an application
+                // can have assumptions about calling startService() for a service to run
+                // in its own process, and for that process to not be killed before the
+                // service is started.  This is especially the case for receivers, which
+                // may start a service in onReceive() to do some additional work and have
+                // initialized some global state as part of that.
+                if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Potential start delay of " + r + " in "
+                        + proc);
+                if (r.delayed) {
+                    // This service is already scheduled for a delayed start; just leave
+                    // it still waiting.
+                    if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r);
+                    return r.name;
+                }
+                if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
+                    // Something else is starting, delay!
+                    Slog.i(TAG, "Delaying start of: " + r);
+                    smap.mDelayedStartList.add(r);
+                    r.delayed = true;
+                    return r.name;
+                }
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r);
+                addToStarting = true;
+            } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
+                // We slightly loosen when we will enqueue this new service as a background
+                // starting service we are waiting for, to also include processes that are
+                // currently running other services or receivers.
+                addToStarting = true;
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r);
+            } else if (DEBUG_DELAYED_STATS) {
+                StringBuilder sb = new StringBuilder(128);
+                sb.append("Not potential delay (state=").append(proc.curProcState)
+                        .append(' ').append(proc.adjType);
+                String reason = proc.makeAdjReason();
+                if (reason != null) {
+                    sb.append(' ');
+                    sb.append(reason);
+                }
+                sb.append("): ");
+                sb.append(r.toString());
+                Slog.v(TAG, sb.toString());
+            }
+        } else if (DEBUG_DELAYED_STATS) {
+            if (callerFg) {
+                Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid="
+                        + callingUid + " pid=" + callingPid + "): " + r);
+            } else if (r.app != null) {
+                Slog.v(TAG, "Not potential delay (cur app=" + r.app + "): " + r);
+            } else {
+                Slog.v(TAG, "Not potential delay (user " + r.userId + " not started): " + r);
+            }
+        }
+
+        return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
+    }
+
+    ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
+            ServiceRecord r, boolean callerFg, boolean addToStarting) {
+        ProcessStats.ServiceState stracker = r.getTracker();
+        if (stracker != null) {
+            stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
+        }
+        r.callStart = false;
+        synchronized (r.stats.getBatteryStats()) {
+            r.stats.startRunningLocked();
+        }
+        String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
+        if (error != null) {
+            return new ComponentName("!!", error);
+        }
+
+        if (r.startRequested && addToStarting) {
+            boolean first = smap.mStartingBackground.size() == 0;
+            smap.mStartingBackground.add(r);
+            r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT;
+            if (DEBUG_DELAYED_SERVICE) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.v(TAG, "Starting background (first=" + first + "): " + r, here);
+            } else if (DEBUG_DELAYED_STATS) {
+                Slog.v(TAG, "Starting background (first=" + first + "): " + r);
+            }
+            if (first) {
+                smap.rescheduleDelayedStarts();
+            }
+        } else if (callerFg) {
+            smap.ensureNotStartingBackground(r);
+        }
+
+        return r.name;
+    }
+
+    private void stopServiceLocked(ServiceRecord service) {
+        if (service.delayed) {
+            // If service isn't actually running, but is is being held in the
+            // delayed list, then we need to keep it started but note that it
+            // should be stopped once no longer delayed.
+            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service);
+            service.delayedStop = true;
+            return;
+        }
+        synchronized (service.stats.getBatteryStats()) {
+            service.stats.stopRunningLocked();
+        }
+        service.startRequested = false;
+        if (service.tracker != null) {
+            service.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
+                    SystemClock.uptimeMillis());
+        }
+        service.callStart = false;
+        bringDownServiceIfNeededLocked(service, false, false);
+    }
+
+    int stopServiceLocked(IApplicationThread caller, Intent service,
+            String resolvedType, int userId) {
+        if (DEBUG_SERVICE) Slog.v(TAG, "stopService: " + service
+                + " type=" + resolvedType);
+
+        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+        if (caller != null && callerApp == null) {
+            throw new SecurityException(
+                    "Unable to find app for caller " + caller
+                    + " (pid=" + Binder.getCallingPid()
+                    + ") when stopping service " + service);
+        }
+
+        // If this service is active, make sure it is stopped.
+        ServiceLookupResult r = retrieveServiceLocked(service, resolvedType,
+                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false);
+        if (r != null) {
+            if (r.record != null) {
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    stopServiceLocked(r.record);
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+                return 1;
+            }
+            return -1;
+        }
+
+        return 0;
+    }
+
+    IBinder peekServiceLocked(Intent service, String resolvedType) {
+        ServiceLookupResult r = retrieveServiceLocked(service, resolvedType,
+                Binder.getCallingPid(), Binder.getCallingUid(),
+                UserHandle.getCallingUserId(), false, false);
+
+        IBinder ret = null;
+        if (r != null) {
+            // r.record is null if findServiceLocked() failed the caller permission check
+            if (r.record == null) {
+                throw new SecurityException(
+                        "Permission Denial: Accessing service " + r.record.name
+                        + " from pid=" + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + " requires " + r.permission);
+            }
+            IntentBindRecord ib = r.record.bindings.get(r.record.intent);
+            if (ib != null) {
+                ret = ib.binder;
+            }
+        }
+
+        return ret;
+    }
+
+    boolean stopServiceTokenLocked(ComponentName className, IBinder token,
+            int startId) {
+        if (DEBUG_SERVICE) Slog.v(TAG, "stopServiceToken: " + className
+                + " " + token + " startId=" + startId);
+        ServiceRecord r = findServiceLocked(className, token, UserHandle.getCallingUserId());
+        if (r != null) {
+            if (startId >= 0) {
+                // Asked to only stop if done with all work.  Note that
+                // to avoid leaks, we will take this as dropping all
+                // start items up to and including this one.
+                ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
+                if (si != null) {
+                    while (r.deliveredStarts.size() > 0) {
+                        ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
+                        cur.removeUriPermissionsLocked();
+                        if (cur == si) {
+                            break;
+                        }
+                    }
+                }
+
+                if (r.getLastStartId() != startId) {
+                    return false;
+                }
+
+                if (r.deliveredStarts.size() > 0) {
+                    Slog.w(TAG, "stopServiceToken startId " + startId
+                            + " is last, but have " + r.deliveredStarts.size()
+                            + " remaining args");
+                }
+            }
+
+            synchronized (r.stats.getBatteryStats()) {
+                r.stats.stopRunningLocked();
+            }
+            r.startRequested = false;
+            if (r.tracker != null) {
+                r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+            }
+            r.callStart = false;
+            final long origId = Binder.clearCallingIdentity();
+            bringDownServiceIfNeededLocked(r, false, false);
+            Binder.restoreCallingIdentity(origId);
+            return true;
+        }
+        return false;
+    }
+
+    public void setServiceForegroundLocked(ComponentName className, IBinder token,
+            int id, Notification notification, boolean removeNotification) {
+        final int userId = UserHandle.getCallingUserId();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            ServiceRecord r = findServiceLocked(className, token, userId);
+            if (r != null) {
+                if (id != 0) {
+                    if (notification == null) {
+                        throw new IllegalArgumentException("null notification");
+                    }
+                    if (r.foregroundId != id) {
+                        r.cancelNotification();
+                        r.foregroundId = id;
+                    }
+                    notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
+                    r.foregroundNoti = notification;
+                    r.isForeground = true;
+                    r.postNotification();
+                    if (r.app != null) {
+                        updateServiceForegroundLocked(r.app, true);
+                    }
+                    getServiceMap(r.userId).ensureNotStartingBackground(r);
+                } else {
+                    if (r.isForeground) {
+                        r.isForeground = false;
+                        if (r.app != null) {
+                            mAm.updateLruProcessLocked(r.app, false, null);
+                            updateServiceForegroundLocked(r.app, true);
+                        }
+                    }
+                    if (removeNotification) {
+                        r.cancelNotification();
+                        r.foregroundId = 0;
+                        r.foregroundNoti = null;
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
+        boolean anyForeground = false;
+        for (int i=proc.services.size()-1; i>=0; i--) {
+            ServiceRecord sr = proc.services.valueAt(i);
+            if (sr.isForeground) {
+                anyForeground = true;
+                break;
+            }
+        }
+        if (anyForeground != proc.foregroundServices) {
+            proc.foregroundServices = anyForeground;
+            if (oomAdj) {
+                mAm.updateOomAdjLocked();
+            }
+        }
+    }
+
+    private boolean updateServiceClientActivitiesLocked(ProcessRecord proc,
+            ConnectionRecord modCr) {
+        if (modCr != null && modCr.binding.client != null) {
+            if (modCr.binding.client.activities.size() <= 0) {
+                // This connection is from a client without activities, so adding
+                // and removing is not interesting.
+                return false;
+            }
+        }
+
+        boolean anyClientActivities = false;
+        for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) {
+            ServiceRecord sr = proc.services.valueAt(i);
+            for (int conni=sr.connections.size()-1; conni>=0 && !anyClientActivities; conni--) {
+                ArrayList<ConnectionRecord> clist = sr.connections.valueAt(conni);
+                for (int cri=clist.size()-1; cri>=0; cri--) {
+                    ConnectionRecord cr = clist.get(cri);
+                    if (cr.binding.client == null || cr.binding.client == proc) {
+                        // Binding to ourself is not interesting.
+                        continue;
+                    }
+                    if (cr.binding.client.activities.size() > 0) {
+                        anyClientActivities = true;
+                        break;
+                    }
+                }
+            }
+        }
+        if (anyClientActivities != proc.hasClientActivities) {
+            proc.hasClientActivities = anyClientActivities;
+            mAm.updateLruProcessLocked(proc, anyClientActivities, null);
+            return true;
+        }
+        return false;
+    }
+
+    int bindServiceLocked(IApplicationThread caller, IBinder token,
+            Intent service, String resolvedType,
+            IServiceConnection connection, int flags, int userId) {
+        if (DEBUG_SERVICE) Slog.v(TAG, "bindService: " + service
+                + " type=" + resolvedType + " conn=" + connection.asBinder()
+                + " flags=0x" + Integer.toHexString(flags));
+        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
+        if (callerApp == null) {
+            throw new SecurityException(
+                    "Unable to find app for caller " + caller
+                    + " (pid=" + Binder.getCallingPid()
+                    + ") when binding service " + service);
+        }
+
+        ActivityRecord activity = null;
+        if (token != null) {
+            activity = ActivityRecord.isInStackLocked(token);
+            if (activity == null) {
+                Slog.w(TAG, "Binding with unknown activity: " + token);
+                return 0;
+            }
+        }
+
+        int clientLabel = 0;
+        PendingIntent clientIntent = null;
+
+        if (callerApp.info.uid == Process.SYSTEM_UID) {
+            // Hacky kind of thing -- allow system stuff to tell us
+            // what they are, so we can report this elsewhere for
+            // others to know why certain services are running.
+            try {
+                clientIntent = (PendingIntent)service.getParcelableExtra(
+                        Intent.EXTRA_CLIENT_INTENT);
+            } catch (RuntimeException e) {
+            }
+            if (clientIntent != null) {
+                clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0);
+                if (clientLabel != 0) {
+                    // There are no useful extras in the intent, trash them.
+                    // System code calling with this stuff just needs to know
+                    // this will happen.
+                    service = service.cloneFilter();
+                }
+            }
+        }
+
+        final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
+
+        ServiceLookupResult res =
+            retrieveServiceLocked(service, resolvedType,
+                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
+        if (res == null) {
+            return 0;
+        }
+        if (res.record == null) {
+            return -1;
+        }
+        ServiceRecord s = res.record;
+
+        final long origId = Binder.clearCallingIdentity();
+
+        try {
+            if (unscheduleServiceRestartLocked(s, callerApp.info.uid, false)) {
+                if (DEBUG_SERVICE) Slog.v(TAG, "BIND SERVICE WHILE RESTART PENDING: "
+                        + s);
+            }
+
+            if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+                s.lastActivity = SystemClock.uptimeMillis();
+                if (!s.hasAutoCreateConnections()) {
+                    // This is the first binding, let the tracker know.
+                    ProcessStats.ServiceState stracker = s.getTracker();
+                    if (stracker != null) {
+                        stracker.setBound(true, mAm.mProcessStats.getMemFactorLocked(),
+                                s.lastActivity);
+                    }
+                }
+            }
+
+            AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
+            ConnectionRecord c = new ConnectionRecord(b, activity,
+                    connection, flags, clientLabel, clientIntent);
+
+            IBinder binder = connection.asBinder();
+            ArrayList<ConnectionRecord> clist = s.connections.get(binder);
+            if (clist == null) {
+                clist = new ArrayList<ConnectionRecord>();
+                s.connections.put(binder, clist);
+            }
+            clist.add(c);
+            b.connections.add(c);
+            if (activity != null) {
+                if (activity.connections == null) {
+                    activity.connections = new HashSet<ConnectionRecord>();
+                }
+                activity.connections.add(c);
+            }
+            b.client.connections.add(c);
+            if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
+                b.client.hasAboveClient = true;
+            }
+            if (s.app != null) {
+                updateServiceClientActivitiesLocked(s.app, c);
+            }
+            clist = mServiceConnections.get(binder);
+            if (clist == null) {
+                clist = new ArrayList<ConnectionRecord>();
+                mServiceConnections.put(binder, clist);
+            }
+            clist.add(c);
+
+            if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+                s.lastActivity = SystemClock.uptimeMillis();
+                if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
+                    return 0;
+                }
+            }
+
+            if (s.app != null) {
+                // This could have made the service more important.
+                mAm.updateLruProcessLocked(s.app, s.app.hasClientActivities, b.client);
+                mAm.updateOomAdjLocked(s.app);
+            }
+
+            if (DEBUG_SERVICE) Slog.v(TAG, "Bind " + s + " with " + b
+                    + ": received=" + b.intent.received
+                    + " apps=" + b.intent.apps.size()
+                    + " doRebind=" + b.intent.doRebind);
+
+            if (s.app != null && b.intent.received) {
+                // Service is already running, so we can immediately
+                // publish the connection.
+                try {
+                    c.conn.connected(s.name, b.intent.binder);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failure sending service " + s.shortName
+                            + " to connection " + c.conn.asBinder()
+                            + " (in " + c.binding.client.processName + ")", e);
+                }
+
+                // If this is the first app connected back to this binding,
+                // and the service had previously asked to be told when
+                // rebound, then do so.
+                if (b.intent.apps.size() == 1 && b.intent.doRebind) {
+                    requestServiceBindingLocked(s, b.intent, callerFg, true);
+                }
+            } else if (!b.intent.requested) {
+                requestServiceBindingLocked(s, b.intent, callerFg, false);
+            }
+
+            getServiceMap(s.userId).ensureNotStartingBackground(s);
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return 1;
+    }
+
+    void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            if (DEBUG_SERVICE) Slog.v(TAG, "PUBLISHING " + r
+                    + " " + intent + ": " + service);
+            if (r != null) {
+                Intent.FilterComparison filter
+                        = new Intent.FilterComparison(intent);
+                IntentBindRecord b = r.bindings.get(filter);
+                if (b != null && !b.received) {
+                    b.binder = service;
+                    b.requested = true;
+                    b.received = true;
+                    for (int conni=r.connections.size()-1; conni>=0; conni--) {
+                        ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
+                        for (int i=0; i<clist.size(); i++) {
+                            ConnectionRecord c = clist.get(i);
+                            if (!filter.equals(c.binding.intent.intent)) {
+                                if (DEBUG_SERVICE) Slog.v(
+                                        TAG, "Not publishing to: " + c);
+                                if (DEBUG_SERVICE) Slog.v(
+                                        TAG, "Bound intent: " + c.binding.intent.intent);
+                                if (DEBUG_SERVICE) Slog.v(
+                                        TAG, "Published intent: " + intent);
+                                continue;
+                            }
+                            if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c);
+                            try {
+                                c.conn.connected(r.name, service);
+                            } catch (Exception e) {
+                                Slog.w(TAG, "Failure sending service " + r.name +
+                                      " to connection " + c.conn.asBinder() +
+                                      " (in " + c.binding.client.processName + ")", e);
+                            }
+                        }
+                    }
+                }
+
+                serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    boolean unbindServiceLocked(IServiceConnection connection) {
+        IBinder binder = connection.asBinder();
+        if (DEBUG_SERVICE) Slog.v(TAG, "unbindService: conn=" + binder);
+        ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
+        if (clist == null) {
+            Slog.w(TAG, "Unbind failed: could not find connection for "
+                  + connection.asBinder());
+            return false;
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            while (clist.size() > 0) {
+                ConnectionRecord r = clist.get(0);
+                removeConnectionLocked(r, null, null);
+
+                if (r.binding.service.app != null) {
+                    // This could have made the service less important.
+                    mAm.updateOomAdjLocked(r.binding.service.app);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return true;
+    }
+
+    void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            if (r != null) {
+                Intent.FilterComparison filter
+                        = new Intent.FilterComparison(intent);
+                IntentBindRecord b = r.bindings.get(filter);
+                if (DEBUG_SERVICE) Slog.v(TAG, "unbindFinished in " + r
+                        + " at " + b + ": apps="
+                        + (b != null ? b.apps.size() : 0));
+
+                boolean inDestroying = mDestroyingServices.contains(r);
+                if (b != null) {
+                    if (b.apps.size() > 0 && !inDestroying) {
+                        // Applications have already bound since the last
+                        // unbind, so just rebind right here.
+                        boolean inFg = false;
+                        for (int i=b.apps.size()-1; i>=0; i--) {
+                            ProcessRecord client = b.apps.valueAt(i).client;
+                            if (client != null && client.setSchedGroup
+                                    != Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+                                inFg = true;
+                                break;
+                            }
+                        }
+                        requestServiceBindingLocked(r, b, inFg, true);
+                    } else {
+                        // Note to tell the service the next time there is
+                        // a new client.
+                        b.doRebind = true;
+                    }
+                }
+
+                serviceDoneExecutingLocked(r, inDestroying, false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private final ServiceRecord findServiceLocked(ComponentName name,
+            IBinder token, int userId) {
+        ServiceRecord r = getServiceByName(name, userId);
+        return r == token ? r : null;
+    }
+
+    private final class ServiceLookupResult {
+        final ServiceRecord record;
+        final String permission;
+
+        ServiceLookupResult(ServiceRecord _record, String _permission) {
+            record = _record;
+            permission = _permission;
+        }
+    }
+
+    private class ServiceRestarter implements Runnable {
+        private ServiceRecord mService;
+
+        void setService(ServiceRecord service) {
+            mService = service;
+        }
+
+        public void run() {
+            synchronized(mAm) {
+                performServiceRestartLocked(mService);
+            }
+        }
+    }
+
+    private ServiceLookupResult retrieveServiceLocked(Intent service,
+            String resolvedType, int callingPid, int callingUid, int userId,
+            boolean createIfNeeded, boolean callingFromFg) {
+        ServiceRecord r = null;
+        if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service
+                + " type=" + resolvedType + " callingUid=" + callingUid);
+
+        userId = mAm.handleIncomingUser(callingPid, callingUid, userId,
+                false, true, "service", null);
+
+        ServiceMap smap = getServiceMap(userId);
+        final ComponentName comp = service.getComponent();
+        if (comp != null) {
+            r = smap.mServicesByName.get(comp);
+        }
+        if (r == null) {
+            Intent.FilterComparison filter = new Intent.FilterComparison(service);
+            r = smap.mServicesByIntent.get(filter);
+        }
+        if (r == null) {
+            try {
+                ResolveInfo rInfo =
+                    AppGlobals.getPackageManager().resolveService(
+                                service, resolvedType,
+                                ActivityManagerService.STOCK_PM_FLAGS, userId);
+                ServiceInfo sInfo =
+                    rInfo != null ? rInfo.serviceInfo : null;
+                if (sInfo == null) {
+                    Slog.w(TAG, "Unable to start service " + service + " U=" + userId +
+                          ": not found");
+                    return null;
+                }
+                ComponentName name = new ComponentName(
+                        sInfo.applicationInfo.packageName, sInfo.name);
+                if (userId > 0) {
+                    if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
+                            sInfo.name, sInfo.flags)) {
+                        userId = 0;
+                        smap = getServiceMap(0);
+                    }
+                    sInfo = new ServiceInfo(sInfo);
+                    sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId);
+                }
+                r = smap.mServicesByName.get(name);
+                if (r == null && createIfNeeded) {
+                    Intent.FilterComparison filter
+                            = new Intent.FilterComparison(service.cloneFilter());
+                    ServiceRestarter res = new ServiceRestarter();
+                    BatteryStatsImpl.Uid.Pkg.Serv ss = null;
+                    BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();
+                    synchronized (stats) {
+                        ss = stats.getServiceStatsLocked(
+                                sInfo.applicationInfo.uid, sInfo.packageName,
+                                sInfo.name);
+                    }
+                    r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);
+                    res.setService(r);
+                    smap.mServicesByName.put(name, r);
+                    smap.mServicesByIntent.put(filter, r);
+
+                    // Make sure this component isn't in the pending list.
+                    for (int i=mPendingServices.size()-1; i>=0; i--) {
+                        ServiceRecord pr = mPendingServices.get(i);
+                        if (pr.serviceInfo.applicationInfo.uid == sInfo.applicationInfo.uid
+                                && pr.name.equals(name)) {
+                            mPendingServices.remove(i);
+                        }
+                    }
+                }
+            } catch (RemoteException ex) {
+                // pm is in same process, this will never happen.
+            }
+        }
+        if (r != null) {
+            if (mAm.checkComponentPermission(r.permission,
+                    callingPid, callingUid, r.appInfo.uid, r.exported)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (!r.exported) {
+                    Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+                            + " from pid=" + callingPid
+                            + ", uid=" + callingUid
+                            + " that is not exported from uid " + r.appInfo.uid);
+                    return new ServiceLookupResult(null, "not exported from uid "
+                            + r.appInfo.uid);
+                }
+                Slog.w(TAG, "Permission Denial: Accessing service " + r.name
+                        + " from pid=" + callingPid
+                        + ", uid=" + callingUid
+                        + " requires " + r.permission);
+                return new ServiceLookupResult(null, r.permission);
+            }
+            if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid,
+                    resolvedType, r.appInfo)) {
+                return null;
+            }
+            return new ServiceLookupResult(r, null);
+        }
+        return null;
+    }
+
+    private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
+        if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING "
+                + why + " of " + r + " in app " + r.app);
+        else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, ">>> EXECUTING "
+                + why + " of " + r.shortName);
+        long now = SystemClock.uptimeMillis();
+        if (r.executeNesting == 0) {
+            r.executeFg = fg;
+            ProcessStats.ServiceState stracker = r.getTracker();
+            if (stracker != null) {
+                stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);
+            }
+            if (r.app != null) {
+                r.app.executingServices.add(r);
+                r.app.execServicesFg |= fg;
+                if (r.app.executingServices.size() == 1) {
+                    scheduleServiceTimeoutLocked(r.app);
+                }
+            }
+        } else if (r.app != null && fg && !r.app.execServicesFg) {
+            r.app.execServicesFg = true;
+            scheduleServiceTimeoutLocked(r.app);
+        }
+        r.executeFg |= fg;
+        r.executeNesting++;
+        r.executingStart = now;
+    }
+
+    private final boolean requestServiceBindingLocked(ServiceRecord r,
+            IntentBindRecord i, boolean execInFg, boolean rebind) {
+        if (r.app == null || r.app.thread == null) {
+            // If service is not currently running, can't yet bind.
+            return false;
+        }
+        if ((!i.requested || rebind) && i.apps.size() > 0) {
+            try {
+                bumpServiceExecutingLocked(r, execInFg, "bind");
+                r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+                r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
+                        r.app.repProcState);
+                if (!rebind) {
+                    i.requested = true;
+                }
+                i.hasBound = true;
+                i.doRebind = false;
+            } catch (RemoteException e) {
+                if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private final boolean scheduleServiceRestartLocked(ServiceRecord r,
+            boolean allowCancel) {
+        boolean canceled = false;
+
+        ServiceMap smap = getServiceMap(r.userId);
+        if (smap.mServicesByName.get(r.name) != r) {
+            ServiceRecord cur = smap.mServicesByName.get(r.name);
+            Slog.wtf(TAG, "Attempting to schedule restart of " + r
+                    + " when found in map: " + cur);
+            return false;
+        }
+
+        final long now = SystemClock.uptimeMillis();
+
+        if ((r.serviceInfo.applicationInfo.flags
+                &ApplicationInfo.FLAG_PERSISTENT) == 0) {
+            long minDuration = SERVICE_RESTART_DURATION;
+            long resetTime = SERVICE_RESET_RUN_DURATION;
+
+            // Any delivered but not yet finished starts should be put back
+            // on the pending list.
+            final int N = r.deliveredStarts.size();
+            if (N > 0) {
+                for (int i=N-1; i>=0; i--) {
+                    ServiceRecord.StartItem si = r.deliveredStarts.get(i);
+                    si.removeUriPermissionsLocked();
+                    if (si.intent == null) {
+                        // We'll generate this again if needed.
+                    } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT
+                            && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) {
+                        r.pendingStarts.add(0, si);
+                        long dur = SystemClock.uptimeMillis() - si.deliveredTime;
+                        dur *= 2;
+                        if (minDuration < dur) minDuration = dur;
+                        if (resetTime < dur) resetTime = dur;
+                    } else {
+                        Slog.w(TAG, "Canceling start item " + si.intent + " in service "
+                                + r.name);
+                        canceled = true;
+                    }
+                }
+                r.deliveredStarts.clear();
+            }
+
+            r.totalRestartCount++;
+            if (r.restartDelay == 0) {
+                r.restartCount++;
+                r.restartDelay = minDuration;
+            } else {
+                // If it has been a "reasonably long time" since the service
+                // was started, then reset our restart duration back to
+                // the beginning, so we don't infinitely increase the duration
+                // on a service that just occasionally gets killed (which is
+                // a normal case, due to process being killed to reclaim memory).
+                if (now > (r.restartTime+resetTime)) {
+                    r.restartCount = 1;
+                    r.restartDelay = minDuration;
+                } else {
+                    r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR;
+                    if (r.restartDelay < minDuration) {
+                        r.restartDelay = minDuration;
+                    }
+                }
+            }
+
+            r.nextRestartTime = now + r.restartDelay;
+
+            // Make sure that we don't end up restarting a bunch of services
+            // all at the same time.
+            boolean repeat;
+            do {
+                repeat = false;
+                for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                    ServiceRecord r2 = mRestartingServices.get(i);
+                    if (r2 != r && r.nextRestartTime
+                            >= (r2.nextRestartTime-SERVICE_MIN_RESTART_TIME_BETWEEN)
+                            && r.nextRestartTime
+                            < (r2.nextRestartTime+SERVICE_MIN_RESTART_TIME_BETWEEN)) {
+                        r.nextRestartTime = r2.nextRestartTime + SERVICE_MIN_RESTART_TIME_BETWEEN;
+                        r.restartDelay = r.nextRestartTime - now;
+                        repeat = true;
+                        break;
+                    }
+                }
+            } while (repeat);
+
+        } else {
+            // Persistent processes are immediately restarted, so there is no
+            // reason to hold of on restarting their services.
+            r.totalRestartCount++;
+            r.restartCount = 0;
+            r.restartDelay = 0;
+            r.nextRestartTime = now;
+        }
+
+        if (!mRestartingServices.contains(r)) {
+            r.createdFromFg = false;
+            mRestartingServices.add(r);
+            r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now);
+        }
+
+        r.cancelNotification();
+
+        mAm.mHandler.removeCallbacks(r.restarter);
+        mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
+        r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
+        Slog.w(TAG, "Scheduling restart of crashed service "
+                + r.shortName + " in " + r.restartDelay + "ms");
+        EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
+                r.userId, r.shortName, r.restartDelay);
+
+        return canceled;
+    }
+
+    final void performServiceRestartLocked(ServiceRecord r) {
+        if (!mRestartingServices.contains(r)) {
+            return;
+        }
+        bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+    }
+
+    private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid,
+            boolean force) {
+        if (!force && r.restartDelay == 0) {
+            return false;
+        }
+        // Remove from the restarting list; if the service is currently on the
+        // restarting list, or the call is coming from another app, then this
+        // service has become of much more interest so we reset the restart interval.
+        boolean removed = mRestartingServices.remove(r);
+        if (removed || callingUid != r.appInfo.uid) {
+            r.resetRestartCounter();
+        }
+        if (removed) {
+            clearRestartingIfNeededLocked(r);
+        }
+        mAm.mHandler.removeCallbacks(r.restarter);
+        return true;
+    }
+
+    private void clearRestartingIfNeededLocked(ServiceRecord r) {
+        if (r.restartTracker != null) {
+            // If this is the last restarting record with this tracker, then clear
+            // the tracker's restarting state.
+            boolean stillTracking = false;
+            for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                if (mRestartingServices.get(i).restartTracker == r.restartTracker) {
+                    stillTracking = true;
+                    break;
+                }
+            }
+            if (!stillTracking) {
+                r.restartTracker.setRestarting(false, mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+                r.restartTracker = null;
+            }
+        }
+    }
+
+    private final String bringUpServiceLocked(ServiceRecord r,
+            int intentFlags, boolean execInFg, boolean whileRestarting) {
+        //Slog.i(TAG, "Bring up service:");
+        //r.dump("  ");
+
+        if (r.app != null && r.app.thread != null) {
+            sendServiceArgsLocked(r, execInFg, false);
+            return null;
+        }
+
+        if (!whileRestarting && r.restartDelay > 0) {
+            // If waiting for a restart, then do nothing.
+            return null;
+        }
+
+        if (DEBUG_SERVICE) Slog.v(TAG, "Bringing up " + r + " " + r.intent);
+
+        // We are now bringing the service up, so no longer in the
+        // restarting state.
+        if (mRestartingServices.remove(r)) {
+            clearRestartingIfNeededLocked(r);
+        }
+
+        // Make sure this service is no longer considered delayed, we are starting it now.
+        if (r.delayed) {
+            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r);
+            getServiceMap(r.userId).mDelayedStartList.remove(r);
+            r.delayed = false;
+        }
+
+        // Make sure that the user who owns this service is started.  If not,
+        // we don't want to allow it to run.
+        if (mAm.mStartedUsers.get(r.userId) == null) {
+            String msg = "Unable to launch app "
+                    + r.appInfo.packageName + "/"
+                    + r.appInfo.uid + " for service "
+                    + r.intent.getIntent() + ": user " + r.userId + " is stopped";
+            Slog.w(TAG, msg);
+            bringDownServiceLocked(r);
+            return msg;
+        }
+
+        // Service is now being launched, its package can't be stopped.
+        try {
+            AppGlobals.getPackageManager().setPackageStoppedState(
+                    r.packageName, false, r.userId);
+        } catch (RemoteException e) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + r.packageName + ": " + e);
+        }
+
+        final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
+        final String procName = r.processName;
+        ProcessRecord app;
+
+        if (!isolated) {
+            app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
+            if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+                        + " app=" + app);
+            if (app != null && app.thread != null) {
+                try {
+                    app.addPackage(r.appInfo.packageName, mAm.mProcessStats);
+                    realStartServiceLocked(r, app, execInFg);
+                    return null;
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Exception when starting service " + r.shortName, e);
+                }
+
+                // If a dead object exception was thrown -- fall through to
+                // restart the application.
+            }
+        } else {
+            // If this service runs in an isolated process, then each time
+            // we call startProcessLocked() we will get a new isolated
+            // process, starting another process if we are currently waiting
+            // for a previous process to come up.  To deal with this, we store
+            // in the service any current isolated process it is running in or
+            // waiting to have come up.
+            app = r.isolatedProc;
+        }
+
+        // Not running -- get it started, and enqueue this service record
+        // to be executed when the app comes up.
+        if (app == null) {
+            if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
+                    "service", r.name, false, isolated, false)) == null) {
+                String msg = "Unable to launch app "
+                        + r.appInfo.packageName + "/"
+                        + r.appInfo.uid + " for service "
+                        + r.intent.getIntent() + ": process is bad";
+                Slog.w(TAG, msg);
+                bringDownServiceLocked(r);
+                return msg;
+            }
+            if (isolated) {
+                r.isolatedProc = app;
+            }
+        }
+
+        if (!mPendingServices.contains(r)) {
+            mPendingServices.add(r);
+        }
+
+        if (r.delayedStop) {
+            // Oh and hey we've already been asked to stop!
+            r.delayedStop = false;
+            if (r.startRequested) {
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r);
+                stopServiceLocked(r);
+            }
+        }
+
+        return null;
+    }
+
+    private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {
+        for (int i=r.bindings.size()-1; i>=0; i--) {
+            IntentBindRecord ibr = r.bindings.valueAt(i);
+            if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
+                break;
+            }
+        }
+    }
+
+    private final void realStartServiceLocked(ServiceRecord r,
+            ProcessRecord app, boolean execInFg) throws RemoteException {
+        if (app.thread == null) {
+            throw new RemoteException();
+        }
+        if (DEBUG_MU)
+            Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
+                    + ", ProcessRecord.uid = " + app.uid);
+        r.app = app;
+        r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
+
+        app.services.add(r);
+        bumpServiceExecutingLocked(r, execInFg, "create");
+        mAm.updateLruProcessLocked(app, false, null);
+        mAm.updateOomAdjLocked();
+
+        boolean created = false;
+        try {
+            String nameTerm;
+            int lastPeriod = r.shortName.lastIndexOf('.');
+            nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
+            EventLogTags.writeAmCreateService(
+                    r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
+            synchronized (r.stats.getBatteryStats()) {
+                r.stats.startLaunchedLocked();
+            }
+            mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
+            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
+            app.thread.scheduleCreateService(r, r.serviceInfo,
+                    mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
+                    app.repProcState);
+            r.postNotification();
+            created = true;
+        } finally {
+            if (!created) {
+                app.services.remove(r);
+                r.app = null;
+                scheduleServiceRestartLocked(r, false);
+            }
+        }
+
+        requestServiceBindingsLocked(r, execInFg);
+
+        // If the service is in the started state, and there are no
+        // pending arguments, then fake up one so its onStartCommand() will
+        // be called.
+        if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
+            r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
+                    null, null));
+        }
+
+        sendServiceArgsLocked(r, execInFg, true);
+
+        if (r.delayed) {
+            if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
+            getServiceMap(r.userId).mDelayedStartList.remove(r);
+            r.delayed = false;
+        }
+
+        if (r.delayedStop) {
+            // Oh and hey we've already been asked to stop!
+            r.delayedStop = false;
+            if (r.startRequested) {
+                if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
+                stopServiceLocked(r);
+            }
+        }
+    }
+
+    private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
+            boolean oomAdjusted) {
+        final int N = r.pendingStarts.size();
+        if (N == 0) {
+            return;
+        }
+
+        while (r.pendingStarts.size() > 0) {
+            try {
+                ServiceRecord.StartItem si = r.pendingStarts.remove(0);
+                if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: "
+                        + r + " " + r.intent + " args=" + si.intent);
+                if (si.intent == null && N > 1) {
+                    // If somehow we got a dummy null intent in the middle,
+                    // then skip it.  DO NOT skip a null intent when it is
+                    // the only one in the list -- this is to support the
+                    // onStartCommand(null) case.
+                    continue;
+                }
+                si.deliveredTime = SystemClock.uptimeMillis();
+                r.deliveredStarts.add(si);
+                si.deliveryCount++;
+                if (si.neededGrants != null) {
+                    mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
+                            si.getUriPermissionsLocked());
+                }
+                bumpServiceExecutingLocked(r, execInFg, "start");
+                if (!oomAdjusted) {
+                    oomAdjusted = true;
+                    mAm.updateOomAdjLocked(r.app);
+                }
+                int flags = 0;
+                if (si.deliveryCount > 1) {
+                    flags |= Service.START_FLAG_RETRY;
+                }
+                if (si.doneExecutingCount > 0) {
+                    flags |= Service.START_FLAG_REDELIVERY;
+                }
+                r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
+            } catch (RemoteException e) {
+                // Remote process gone...  we'll let the normal cleanup take
+                // care of this.
+                if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
+                break;
+            } catch (Exception e) {
+                Slog.w(TAG, "Unexpected exception", e);
+                break;
+            }
+        }
+    }
+
+    private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
+        // Are we still explicitly being asked to run?
+        if (r.startRequested) {
+            return true;
+        }
+
+        // Is someone still bound to us keepign us running?
+        if (!knowConn) {
+            hasConn = r.hasAutoCreateConnections();
+        }
+        if (hasConn) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn,
+            boolean hasConn) {
+        //Slog.i(TAG, "Bring down service:");
+        //r.dump("  ");
+
+        if (isServiceNeeded(r, knowConn, hasConn)) {
+            return;
+        }
+
+        // Are we in the process of launching?
+        if (mPendingServices.contains(r)) {
+            return;
+        }
+
+        bringDownServiceLocked(r);
+    }
+
+    private final void bringDownServiceLocked(ServiceRecord r) {
+        //Slog.i(TAG, "Bring down service:");
+        //r.dump("  ");
+
+        // Report to all of the connections that the service is no longer
+        // available.
+        for (int conni=r.connections.size()-1; conni>=0; conni--) {
+            ArrayList<ConnectionRecord> c = r.connections.valueAt(conni);
+            for (int i=0; i<c.size(); i++) {
+                ConnectionRecord cr = c.get(i);
+                // There is still a connection to the service that is
+                // being brought down.  Mark it as dead.
+                cr.serviceDead = true;
+                try {
+                    cr.conn.connected(r.name, null);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failure disconnecting service " + r.name +
+                          " to connection " + c.get(i).conn.asBinder() +
+                          " (in " + c.get(i).binding.client.processName + ")", e);
+                }
+            }
+        }
+
+        // Tell the service that it has been unbound.
+        if (r.app != null && r.app.thread != null) {
+            for (int i=r.bindings.size()-1; i>=0; i--) {
+                IntentBindRecord ibr = r.bindings.valueAt(i);
+                if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr
+                        + ": hasBound=" + ibr.hasBound);
+                if (ibr.hasBound) {
+                    try {
+                        bumpServiceExecutingLocked(r, false, "bring down unbind");
+                        mAm.updateOomAdjLocked(r.app);
+                        ibr.hasBound = false;
+                        r.app.thread.scheduleUnbindService(r,
+                                ibr.intent.getIntent());
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Exception when unbinding service "
+                                + r.shortName, e);
+                        serviceProcessGoneLocked(r);
+                    }
+                }
+            }
+        }
+
+        if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down " + r + " " + r.intent);
+        EventLogTags.writeAmDestroyService(
+                r.userId, System.identityHashCode(r), (r.app != null) ? r.app.pid : -1);
+
+        final ServiceMap smap = getServiceMap(r.userId);
+        smap.mServicesByName.remove(r.name);
+        smap.mServicesByIntent.remove(r.intent);
+        r.totalRestartCount = 0;
+        unscheduleServiceRestartLocked(r, 0, true);
+
+        // Also make sure it is not on the pending list.
+        for (int i=mPendingServices.size()-1; i>=0; i--) {
+            if (mPendingServices.get(i) == r) {
+                mPendingServices.remove(i);
+                if (DEBUG_SERVICE) Slog.v(TAG, "Removed pending: " + r);
+            }
+        }
+
+        r.cancelNotification();
+        r.isForeground = false;
+        r.foregroundId = 0;
+        r.foregroundNoti = null;
+
+        // Clear start entries.
+        r.clearDeliveredStartsLocked();
+        r.pendingStarts.clear();
+
+        if (r.app != null) {
+            synchronized (r.stats.getBatteryStats()) {
+                r.stats.stopLaunchedLocked();
+            }
+            r.app.services.remove(r);
+            if (r.app.thread != null) {
+                updateServiceForegroundLocked(r.app, false);
+                try {
+                    bumpServiceExecutingLocked(r, false, "destroy");
+                    mDestroyingServices.add(r);
+                    mAm.updateOomAdjLocked(r.app);
+                    r.app.thread.scheduleStopService(r);
+                } catch (Exception e) {
+                    Slog.w(TAG, "Exception when destroying service "
+                            + r.shortName, e);
+                    serviceProcessGoneLocked(r);
+                }
+            } else {
+                if (DEBUG_SERVICE) Slog.v(
+                    TAG, "Removed service that has no process: " + r);
+            }
+        } else {
+            if (DEBUG_SERVICE) Slog.v(
+                TAG, "Removed service that is not running: " + r);
+        }
+
+        if (r.bindings.size() > 0) {
+            r.bindings.clear();
+        }
+
+        if (r.restarter instanceof ServiceRestarter) {
+           ((ServiceRestarter)r.restarter).setService(null);
+        }
+
+        int memFactor = mAm.mProcessStats.getMemFactorLocked();
+        long now = SystemClock.uptimeMillis();
+        if (r.tracker != null) {
+            r.tracker.setStarted(false, memFactor, now);
+            r.tracker.setBound(false, memFactor, now);
+            if (r.executeNesting == 0) {
+                r.tracker.clearCurrentOwner(r, false);
+                r.tracker = null;
+            }
+        }
+
+        smap.ensureNotStartingBackground(r);
+    }
+
+    void removeConnectionLocked(
+        ConnectionRecord c, ProcessRecord skipApp, ActivityRecord skipAct) {
+        IBinder binder = c.conn.asBinder();
+        AppBindRecord b = c.binding;
+        ServiceRecord s = b.service;
+        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
+        if (clist != null) {
+            clist.remove(c);
+            if (clist.size() == 0) {
+                s.connections.remove(binder);
+            }
+        }
+        b.connections.remove(c);
+        if (c.activity != null && c.activity != skipAct) {
+            if (c.activity.connections != null) {
+                c.activity.connections.remove(c);
+            }
+        }
+        if (b.client != skipApp) {
+            b.client.connections.remove(c);
+            if ((c.flags&Context.BIND_ABOVE_CLIENT) != 0) {
+                b.client.updateHasAboveClientLocked();
+            }
+            if (s.app != null) {
+                updateServiceClientActivitiesLocked(s.app, c);
+            }
+        }
+        clist = mServiceConnections.get(binder);
+        if (clist != null) {
+            clist.remove(c);
+            if (clist.size() == 0) {
+                mServiceConnections.remove(binder);
+            }
+        }
+
+        if (b.connections.size() == 0) {
+            b.intent.apps.remove(b.client);
+        }
+
+        if (!c.serviceDead) {
+            if (DEBUG_SERVICE) Slog.v(TAG, "Disconnecting binding " + b.intent
+                    + ": shouldUnbind=" + b.intent.hasBound);
+            if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
+                    && b.intent.hasBound) {
+                try {
+                    bumpServiceExecutingLocked(s, false, "unbind");
+                    if (b.client != s.app && (c.flags&Context.BIND_WAIVE_PRIORITY) == 0
+                            && s.app.setProcState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+                        // If this service's process is not already in the cached list,
+                        // then update it in the LRU list here because this may be causing
+                        // it to go down there and we want it to start out near the top.
+                        mAm.updateLruProcessLocked(s.app, false, null);
+                    }
+                    mAm.updateOomAdjLocked(s.app);
+                    b.intent.hasBound = false;
+                    // Assume the client doesn't want to know about a rebind;
+                    // we will deal with that later if it asks for one.
+                    b.intent.doRebind = false;
+                    s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent());
+                } catch (Exception e) {
+                    Slog.w(TAG, "Exception when unbinding service " + s.shortName, e);
+                    serviceProcessGoneLocked(s);
+                }
+            }
+
+            if ((c.flags&Context.BIND_AUTO_CREATE) != 0) {
+                boolean hasAutoCreate = s.hasAutoCreateConnections();
+                if (!hasAutoCreate) {
+                    if (s.tracker != null) {
+                        s.tracker.setBound(false, mAm.mProcessStats.getMemFactorLocked(),
+                                SystemClock.uptimeMillis());
+                    }
+                }
+                bringDownServiceIfNeededLocked(s, true, hasAutoCreate);
+            }
+        }
+    }
+
+    void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
+        boolean inDestroying = mDestroyingServices.contains(r);
+        if (r != null) {
+            if (type == 1) {
+                // This is a call from a service start...  take care of
+                // book-keeping.
+                r.callStart = true;
+                switch (res) {
+                    case Service.START_STICKY_COMPATIBILITY:
+                    case Service.START_STICKY: {
+                        // We are done with the associated start arguments.
+                        r.findDeliveredStart(startId, true);
+                        // Don't stop if killed.
+                        r.stopIfKilled = false;
+                        break;
+                    }
+                    case Service.START_NOT_STICKY: {
+                        // We are done with the associated start arguments.
+                        r.findDeliveredStart(startId, true);
+                        if (r.getLastStartId() == startId) {
+                            // There is no more work, and this service
+                            // doesn't want to hang around if killed.
+                            r.stopIfKilled = true;
+                        }
+                        break;
+                    }
+                    case Service.START_REDELIVER_INTENT: {
+                        // We'll keep this item until they explicitly
+                        // call stop for it, but keep track of the fact
+                        // that it was delivered.
+                        ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);
+                        if (si != null) {
+                            si.deliveryCount = 0;
+                            si.doneExecutingCount++;
+                            // Don't stop if killed.
+                            r.stopIfKilled = true;
+                        }
+                        break;
+                    }
+                    case Service.START_TASK_REMOVED_COMPLETE: {
+                        // Special processing for onTaskRemoved().  Don't
+                        // impact normal onStartCommand() processing.
+                        r.findDeliveredStart(startId, true);
+                        break;
+                    }
+                    default:
+                        throw new IllegalArgumentException(
+                                "Unknown service start result: " + res);
+                }
+                if (res == Service.START_STICKY_COMPATIBILITY) {
+                    r.callStart = false;
+                }
+            }
+            final long origId = Binder.clearCallingIdentity();
+            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+            Binder.restoreCallingIdentity(origId);
+        } else {
+            Slog.w(TAG, "Done executing unknown service from pid "
+                    + Binder.getCallingPid());
+        }
+    }
+
+    private void serviceProcessGoneLocked(ServiceRecord r) {
+        if (r.tracker != null) {
+            int memFactor = mAm.mProcessStats.getMemFactorLocked();
+            long now = SystemClock.uptimeMillis();
+            r.tracker.setExecuting(false, memFactor, now);
+            r.tracker.setBound(false, memFactor, now);
+            r.tracker.setStarted(false, memFactor, now);
+        }
+        serviceDoneExecutingLocked(r, true, true);
+    }
+
+    private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
+            boolean finishing) {
+        if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r
+                + ": nesting=" + r.executeNesting
+                + ", inDestroying=" + inDestroying + ", app=" + r.app);
+        else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName);
+        r.executeNesting--;
+        if (r.executeNesting <= 0) {
+            if (r.app != null) {
+                if (DEBUG_SERVICE) Slog.v(TAG,
+                        "Nesting at 0 of " + r.shortName);
+                r.app.execServicesFg = false;
+                r.app.executingServices.remove(r);
+                if (r.app.executingServices.size() == 0) {
+                    if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG,
+                            "No more executingServices of " + r.shortName);
+                    mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
+                } else if (r.executeFg) {
+                    // Need to re-evaluate whether the app still needs to be in the foreground.
+                    for (int i=r.app.executingServices.size()-1; i>=0; i--) {
+                        if (r.app.executingServices.valueAt(i).executeFg) {
+                            r.app.execServicesFg = true;
+                            break;
+                        }
+                    }
+                }
+                if (inDestroying) {
+                    if (DEBUG_SERVICE) Slog.v(TAG,
+                            "doneExecuting remove destroying " + r);
+                    mDestroyingServices.remove(r);
+                    r.bindings.clear();
+                }
+                mAm.updateOomAdjLocked(r.app);
+            }
+            r.executeFg = false;
+            if (r.tracker != null) {
+                r.tracker.setExecuting(false, mAm.mProcessStats.getMemFactorLocked(),
+                        SystemClock.uptimeMillis());
+                if (finishing) {
+                    r.tracker.clearCurrentOwner(r, false);
+                    r.tracker = null;
+                }
+            }
+            if (finishing) {
+                if (r.app != null && !r.app.persistent) {
+                    r.app.services.remove(r);
+                }
+                r.app = null;
+            }
+        }
+    }
+
+    boolean attachApplicationLocked(ProcessRecord proc, String processName) throws Exception {
+        boolean didSomething = false;
+        // Collect any services that are waiting for this process to come up.
+        if (mPendingServices.size() > 0) {
+            ServiceRecord sr = null;
+            try {
+                for (int i=0; i<mPendingServices.size(); i++) {
+                    sr = mPendingServices.get(i);
+                    if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
+                            || !processName.equals(sr.processName))) {
+                        continue;
+                    }
+
+                    mPendingServices.remove(i);
+                    i--;
+                    proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats);
+                    realStartServiceLocked(sr, proc, sr.createdFromFg);
+                    didSomething = true;
+                }
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in new application when starting service "
+                        + sr.shortName, e);
+                throw e;
+            }
+        }
+        // Also, if there are any services that are waiting to restart and
+        // would run in this process, now is a good time to start them.  It would
+        // be weird to bring up the process but arbitrarily not let the services
+        // run at this point just because their restart time hasn't come up.
+        if (mRestartingServices.size() > 0) {
+            ServiceRecord sr = null;
+            for (int i=0; i<mRestartingServices.size(); i++) {
+                sr = mRestartingServices.get(i);
+                if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid
+                        || !processName.equals(sr.processName))) {
+                    continue;
+                }
+                mAm.mHandler.removeCallbacks(sr.restarter);
+                mAm.mHandler.post(sr.restarter);
+            }
+        }
+        return didSomething;
+    }
+
+    void processStartTimedOutLocked(ProcessRecord proc) {
+        for (int i=0; i<mPendingServices.size(); i++) {
+            ServiceRecord sr = mPendingServices.get(i);
+            if ((proc.uid == sr.appInfo.uid
+                    && proc.processName.equals(sr.processName))
+                    || sr.isolatedProc == proc) {
+                Slog.w(TAG, "Forcing bringing down service: " + sr);
+                sr.isolatedProc = null;
+                mPendingServices.remove(i);
+                i--;
+                bringDownServiceLocked(sr);
+            }
+        }
+    }
+
+    private boolean collectForceStopServicesLocked(String name, int userId,
+            boolean evenPersistent, boolean doit,
+            ArrayMap<ComponentName, ServiceRecord> services,
+            ArrayList<ServiceRecord> result) {
+        boolean didSomething = false;
+        for (int i=0; i<services.size(); i++) {
+            ServiceRecord service = services.valueAt(i);
+            if ((name == null || service.packageName.equals(name))
+                    && (service.app == null || evenPersistent || !service.app.persistent)) {
+                if (!doit) {
+                    return true;
+                }
+                didSomething = true;
+                Slog.i(TAG, "  Force stopping service " + service);
+                if (service.app != null) {
+                    service.app.removed = true;
+                    if (!service.app.persistent) {
+                        service.app.services.remove(service);
+                    }
+                }
+                service.app = null;
+                service.isolatedProc = null;
+                result.add(service);
+            }
+        }
+        return didSomething;
+    }
+
+    boolean forceStopLocked(String name, int userId, boolean evenPersistent, boolean doit) {
+        boolean didSomething = false;
+        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+        if (userId == UserHandle.USER_ALL) {
+            for (int i=0; i<mServiceMap.size(); i++) {
+                didSomething |= collectForceStopServicesLocked(name, userId, evenPersistent,
+                        doit, mServiceMap.valueAt(i).mServicesByName, services);
+                if (!doit && didSomething) {
+                    return true;
+                }
+            }
+        } else {
+            ServiceMap smap = mServiceMap.get(userId);
+            if (smap != null) {
+                ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByName;
+                didSomething = collectForceStopServicesLocked(name, userId, evenPersistent,
+                        doit, items, services);
+            }
+        }
+
+        int N = services.size();
+        for (int i=0; i<N; i++) {
+            bringDownServiceLocked(services.get(i));
+        }
+        return didSomething;
+    }
+
+    void cleanUpRemovedTaskLocked(TaskRecord tr, ComponentName component, Intent baseIntent) {
+        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+        ArrayMap<ComponentName, ServiceRecord> alls = getServices(tr.userId);
+        for (int i=0; i<alls.size(); i++) {
+            ServiceRecord sr = alls.valueAt(i);
+            if (sr.packageName.equals(component.getPackageName())) {
+                services.add(sr);
+            }
+        }
+
+        // Take care of any running services associated with the app.
+        for (int i=0; i<services.size(); i++) {
+            ServiceRecord sr = services.get(i);
+            if (sr.startRequested) {
+                if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
+                    Slog.i(TAG, "Stopping service " + sr.shortName + ": remove task");
+                    stopServiceLocked(sr);
+                } else {
+                    sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
+                            sr.makeNextStartId(), baseIntent, null));
+                    if (sr.app != null && sr.app.thread != null) {
+                        // We always run in the foreground, since this is called as
+                        // part of the "remove task" UI operation.
+                        sendServiceArgsLocked(sr, true, false);
+                    }
+                }
+            }
+        }
+    }
+
+    final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
+        // Report disconnected services.
+        if (false) {
+            // XXX we are letting the client link to the service for
+            // death notifications.
+            if (app.services.size() > 0) {
+                Iterator<ServiceRecord> it = app.services.iterator();
+                while (it.hasNext()) {
+                    ServiceRecord r = it.next();
+                    for (int conni=r.connections.size()-1; conni>=0; conni--) {
+                        ArrayList<ConnectionRecord> cl = r.connections.valueAt(conni);
+                        for (int i=0; i<cl.size(); i++) {
+                            ConnectionRecord c = cl.get(i);
+                            if (c.binding.client != app) {
+                                try {
+                                    //c.conn.connected(r.className, null);
+                                } catch (Exception e) {
+                                    // todo: this should be asynchronous!
+                                    Slog.w(TAG, "Exception thrown disconnected servce "
+                                          + r.shortName
+                                          + " from app " + app.processName, e);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // First clear app state from services.
+        for (int i=app.services.size()-1; i>=0; i--) {
+            ServiceRecord sr = app.services.valueAt(i);
+            synchronized (sr.stats.getBatteryStats()) {
+                sr.stats.stopLaunchedLocked();
+            }
+            if (sr.app != null && !sr.app.persistent) {
+                sr.app.services.remove(sr);
+            }
+            sr.app = null;
+            sr.isolatedProc = null;
+            sr.executeNesting = 0;
+            sr.forceClearTracker();
+            if (mDestroyingServices.remove(sr)) {
+                if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr);
+            }
+
+            final int numClients = sr.bindings.size();
+            for (int bindingi=numClients-1; bindingi>=0; bindingi--) {
+                IntentBindRecord b = sr.bindings.valueAt(bindingi);
+                if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b
+                        + ": shouldUnbind=" + b.hasBound);
+                b.binder = null;
+                b.requested = b.received = b.hasBound = false;
+            }
+        }
+
+        // Clean up any connections this application has to other services.
+        for (int i=app.connections.size()-1; i>=0; i--) {
+            ConnectionRecord r = app.connections.valueAt(i);
+            removeConnectionLocked(r, app, null);
+        }
+        app.connections.clear();
+
+        ServiceMap smap = getServiceMap(app.userId);
+
+        // Now do remaining service cleanup.
+        for (int i=app.services.size()-1; i>=0; i--) {
+            ServiceRecord sr = app.services.valueAt(i);
+            // Sanity check: if the service listed for the app is not one
+            // we actually are maintaining, drop it.
+            if (smap.mServicesByName.get(sr.name) != sr) {
+                ServiceRecord cur = smap.mServicesByName.get(sr.name);
+                Slog.wtf(TAG, "Service " + sr + " in process " + app
+                        + " not same as in map: " + cur);
+                app.services.removeAt(i);
+                continue;
+            }
+
+            // Any services running in the application may need to be placed
+            // back in the pending list.
+            if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
+                    &ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                Slog.w(TAG, "Service crashed " + sr.crashCount
+                        + " times, stopping: " + sr);
+                EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
+                        sr.userId, sr.crashCount, sr.shortName, app.pid);
+                bringDownServiceLocked(sr);
+            } else if (!allowRestart) {
+                bringDownServiceLocked(sr);
+            } else {
+                boolean canceled = scheduleServiceRestartLocked(sr, true);
+
+                // Should the service remain running?  Note that in the
+                // extreme case of so many attempts to deliver a command
+                // that it failed we also will stop it here.
+                if (sr.startRequested && (sr.stopIfKilled || canceled)) {
+                    if (sr.pendingStarts.size() == 0) {
+                        sr.startRequested = false;
+                        if (sr.tracker != null) {
+                            sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
+                                    SystemClock.uptimeMillis());
+                        }
+                        if (!sr.hasAutoCreateConnections()) {
+                            // Whoops, no reason to restart!
+                            bringDownServiceLocked(sr);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!allowRestart) {
+            app.services.clear();
+
+            // Make sure there are no more restarting services for this process.
+            for (int i=mRestartingServices.size()-1; i>=0; i--) {
+                ServiceRecord r = mRestartingServices.get(i);
+                if (r.processName.equals(app.processName) &&
+                        r.serviceInfo.applicationInfo.uid == app.info.uid) {
+                    mRestartingServices.remove(i);
+                    clearRestartingIfNeededLocked(r);
+                }
+            }
+            for (int i=mPendingServices.size()-1; i>=0; i--) {
+                ServiceRecord r = mPendingServices.get(i);
+                if (r.processName.equals(app.processName) &&
+                        r.serviceInfo.applicationInfo.uid == app.info.uid) {
+                    mPendingServices.remove(i);
+                }
+            }
+        }
+
+        // Make sure we have no more records on the stopping list.
+        int i = mDestroyingServices.size();
+        while (i > 0) {
+            i--;
+            ServiceRecord sr = mDestroyingServices.get(i);
+            if (sr.app == app) {
+                sr.forceClearTracker();
+                mDestroyingServices.remove(i);
+                if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr);
+            }
+        }
+
+        app.executingServices.clear();
+    }
+
+    ActivityManager.RunningServiceInfo makeRunningServiceInfoLocked(ServiceRecord r) {
+        ActivityManager.RunningServiceInfo info =
+            new ActivityManager.RunningServiceInfo();
+        info.service = r.name;
+        if (r.app != null) {
+            info.pid = r.app.pid;
+        }
+        info.uid = r.appInfo.uid;
+        info.process = r.processName;
+        info.foreground = r.isForeground;
+        info.activeSince = r.createTime;
+        info.started = r.startRequested;
+        info.clientCount = r.connections.size();
+        info.crashCount = r.crashCount;
+        info.lastActivityTime = r.lastActivity;
+        if (r.isForeground) {
+            info.flags |= ActivityManager.RunningServiceInfo.FLAG_FOREGROUND;
+        }
+        if (r.startRequested) {
+            info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED;
+        }
+        if (r.app != null && r.app.pid == ActivityManagerService.MY_PID) {
+            info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS;
+        }
+        if (r.app != null && r.app.persistent) {
+            info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS;
+        }
+
+        for (int conni=r.connections.size()-1; conni>=0; conni--) {
+            ArrayList<ConnectionRecord> connl = r.connections.valueAt(conni);
+            for (int i=0; i<connl.size(); i++) {
+                ConnectionRecord conn = connl.get(i);
+                if (conn.clientLabel != 0) {
+                    info.clientPackage = conn.binding.client.info.packageName;
+                    info.clientLabel = conn.clientLabel;
+                    return info;
+                }
+            }
+        }
+        return info;
+    }
+
+    List<ActivityManager.RunningServiceInfo> getRunningServiceInfoLocked(int maxNum,
+            int flags) {
+        ArrayList<ActivityManager.RunningServiceInfo> res
+                = new ArrayList<ActivityManager.RunningServiceInfo>();
+
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (ActivityManager.checkUidPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    uid) == PackageManager.PERMISSION_GRANTED) {
+                int[] users = mAm.getUsersLocked();
+                for (int ui=0; ui<users.length && res.size() < maxNum; ui++) {
+                    ArrayMap<ComponentName, ServiceRecord> alls = getServices(users[ui]);
+                    for (int i=0; i<alls.size() && res.size() < maxNum; i++) {
+                        ServiceRecord sr = alls.valueAt(i);
+                        res.add(makeRunningServiceInfoLocked(sr));
+                    }
+                }
+
+                for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) {
+                    ServiceRecord r = mRestartingServices.get(i);
+                    ActivityManager.RunningServiceInfo info =
+                            makeRunningServiceInfoLocked(r);
+                    info.restarting = r.nextRestartTime;
+                    res.add(info);
+                }
+            } else {
+                int userId = UserHandle.getUserId(uid);
+                ArrayMap<ComponentName, ServiceRecord> alls = getServices(userId);
+                for (int i=0; i<alls.size() && res.size() < maxNum; i++) {
+                    ServiceRecord sr = alls.valueAt(i);
+                    res.add(makeRunningServiceInfoLocked(sr));
+                }
+
+                for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) {
+                    ServiceRecord r = mRestartingServices.get(i);
+                    if (r.userId == userId) {
+                        ActivityManager.RunningServiceInfo info =
+                                makeRunningServiceInfoLocked(r);
+                        info.restarting = r.nextRestartTime;
+                        res.add(info);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return res;
+    }
+
+    public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) {
+        int userId = UserHandle.getUserId(Binder.getCallingUid());
+        ServiceRecord r = getServiceByName(name, userId);
+        if (r != null) {
+            for (int conni=r.connections.size()-1; conni>=0; conni--) {
+                ArrayList<ConnectionRecord> conn = r.connections.valueAt(conni);
+                for (int i=0; i<conn.size(); i++) {
+                    if (conn.get(i).clientIntent != null) {
+                        return conn.get(i).clientIntent;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    void serviceTimeout(ProcessRecord proc) {
+        String anrMessage = null;
+
+        synchronized(this) {
+            if (proc.executingServices.size() == 0 || proc.thread == null) {
+                return;
+            }
+            long maxTime = SystemClock.uptimeMillis() -
+                    (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
+            ServiceRecord timeout = null;
+            long nextTime = 0;
+            for (int i=proc.executingServices.size()-1; i>=0; i--) {
+                ServiceRecord sr = proc.executingServices.valueAt(i);
+                if (sr.executingStart < maxTime) {
+                    timeout = sr;
+                    break;
+                }
+                if (sr.executingStart > nextTime) {
+                    nextTime = sr.executingStart;
+                }
+            }
+            if (timeout != null && mAm.mLruProcesses.contains(proc)) {
+                Slog.w(TAG, "Timeout executing service: " + timeout);
+                anrMessage = "Executing service " + timeout.shortName;
+            } else {
+                Message msg = mAm.mHandler.obtainMessage(
+                        ActivityManagerService.SERVICE_TIMEOUT_MSG);
+                msg.obj = proc;
+                mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg
+                        ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT));
+            }
+        }
+
+        if (anrMessage != null) {
+            mAm.appNotResponding(proc, null, null, false, anrMessage);
+        }
+    }
+
+    void scheduleServiceTimeoutLocked(ProcessRecord proc) {
+        if (proc.executingServices.size() == 0 || proc.thread == null) {
+            return;
+        }
+        long now = SystemClock.uptimeMillis();
+        Message msg = mAm.mHandler.obtainMessage(
+                ActivityManagerService.SERVICE_TIMEOUT_MSG);
+        msg.obj = proc;
+        mAm.mHandler.sendMessageAtTime(msg,
+                proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
+    }
+
+    /**
+     * Prints a list of ServiceRecords (dumpsys activity services)
+     */
+    void dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
+        boolean needSep = false;
+        boolean printedAnything = false;
+
+        ItemMatcher matcher = new ItemMatcher();
+        matcher.build(args, opti);
+
+        pw.println("ACTIVITY MANAGER SERVICES (dumpsys activity services)");
+        try {
+            int[] users = mAm.getUsersLocked();
+            for (int user : users) {
+                ServiceMap smap = getServiceMap(user);
+                boolean printed = false;
+                if (smap.mServicesByName.size() > 0) {
+                    long nowReal = SystemClock.elapsedRealtime();
+                    needSep = false;
+                    for (int si=0; si<smap.mServicesByName.size(); si++) {
+                        ServiceRecord r = smap.mServicesByName.valueAt(si);
+                        if (!matcher.match(r, r.name)) {
+                            continue;
+                        }
+                        if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                            continue;
+                        }
+                        if (!printed) {
+                            if (printedAnything) {
+                                pw.println();
+                            }
+                            pw.println("  User " + user + " active services:");
+                            printed = true;
+                        }
+                        printedAnything = true;
+                        if (needSep) {
+                            pw.println();
+                        }
+                        pw.print("  * ");
+                        pw.println(r);
+                        if (dumpAll) {
+                            r.dump(pw, "    ");
+                            needSep = true;
+                        } else {
+                            pw.print("    app=");
+                            pw.println(r.app);
+                            pw.print("    created=");
+                            TimeUtils.formatDuration(r.createTime, nowReal, pw);
+                            pw.print(" started=");
+                            pw.print(r.startRequested);
+                            pw.print(" connections=");
+                            pw.println(r.connections.size());
+                            if (r.connections.size() > 0) {
+                                pw.println("    Connections:");
+                                for (int conni=0; conni<r.connections.size(); conni++) {
+                                    ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
+                                    for (int i = 0; i < clist.size(); i++) {
+                                        ConnectionRecord conn = clist.get(i);
+                                        pw.print("      ");
+                                        pw.print(conn.binding.intent.intent.getIntent()
+                                                .toShortString(false, false, false, false));
+                                        pw.print(" -> ");
+                                        ProcessRecord proc = conn.binding.client;
+                                        pw.println(proc != null ? proc.toShortString() : "null");
+                                    }
+                                }
+                            }
+                        }
+                        if (dumpClient && r.app != null && r.app.thread != null) {
+                            pw.println("    Client:");
+                            pw.flush();
+                            try {
+                                TransferPipe tp = new TransferPipe();
+                                try {
+                                    r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(),
+                                            r, args);
+                                    tp.setBufferPrefix("      ");
+                                    // Short timeout, since blocking here can
+                                    // deadlock with the application.
+                                    tp.go(fd, 2000);
+                                } finally {
+                                    tp.kill();
+                                }
+                            } catch (IOException e) {
+                                pw.println("      Failure while dumping the service: " + e);
+                            } catch (RemoteException e) {
+                                pw.println("      Got a RemoteException while dumping the service");
+                            }
+                            needSep = true;
+                        }
+                    }
+                    needSep |= printed;
+                }
+                printed = false;
+                for (int si=0, SN=smap.mDelayedStartList.size(); si<SN; si++) {
+                    ServiceRecord r = smap.mDelayedStartList.get(si);
+                    if (!matcher.match(r, r.name)) {
+                        continue;
+                    }
+                    if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (printedAnything) {
+                            pw.println();
+                        }
+                        pw.println("  User " + user + " delayed start services:");
+                        printed = true;
+                    }
+                    printedAnything = true;
+                    pw.print("  * Delayed start "); pw.println(r);
+                }
+                printed = false;
+                for (int si=0, SN=smap.mStartingBackground.size(); si<SN; si++) {
+                    ServiceRecord r = smap.mStartingBackground.get(si);
+                    if (!matcher.match(r, r.name)) {
+                        continue;
+                    }
+                    if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (printedAnything) {
+                            pw.println();
+                        }
+                        pw.println("  User " + user + " starting in background:");
+                        printed = true;
+                    }
+                    printedAnything = true;
+                    pw.print("  * Starting bg "); pw.println(r);
+                }
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Exception in dumpServicesLocked", e);
+        }
+
+        if (mPendingServices.size() > 0) {
+            boolean printed = false;
+            for (int i=0; i<mPendingServices.size(); i++) {
+                ServiceRecord r = mPendingServices.get(i);
+                if (!matcher.match(r, r.name)) {
+                    continue;
+                }
+                if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                    continue;
+                }
+                printedAnything = true;
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Pending services:");
+                    printed = true;
+                }
+                pw.print("  * Pending "); pw.println(r);
+                r.dump(pw, "    ");
+            }
+            needSep = true;
+        }
+
+        if (mRestartingServices.size() > 0) {
+            boolean printed = false;
+            for (int i=0; i<mRestartingServices.size(); i++) {
+                ServiceRecord r = mRestartingServices.get(i);
+                if (!matcher.match(r, r.name)) {
+                    continue;
+                }
+                if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                    continue;
+                }
+                printedAnything = true;
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Restarting services:");
+                    printed = true;
+                }
+                pw.print("  * Restarting "); pw.println(r);
+                r.dump(pw, "    ");
+            }
+            needSep = true;
+        }
+
+        if (mDestroyingServices.size() > 0) {
+            boolean printed = false;
+            for (int i=0; i< mDestroyingServices.size(); i++) {
+                ServiceRecord r = mDestroyingServices.get(i);
+                if (!matcher.match(r, r.name)) {
+                    continue;
+                }
+                if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                    continue;
+                }
+                printedAnything = true;
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Destroying services:");
+                    printed = true;
+                }
+                pw.print("  * Destroy "); pw.println(r);
+                r.dump(pw, "    ");
+            }
+            needSep = true;
+        }
+
+        if (dumpAll) {
+            boolean printed = false;
+            for (int ic=0; ic<mServiceConnections.size(); ic++) {
+                ArrayList<ConnectionRecord> r = mServiceConnections.valueAt(ic);
+                for (int i=0; i<r.size(); i++) {
+                    ConnectionRecord cr = r.get(i);
+                    if (!matcher.match(cr.binding.service, cr.binding.service.name)) {
+                        continue;
+                    }
+                    if (dumpPackage != null && (cr.binding.client == null
+                            || !dumpPackage.equals(cr.binding.client.info.packageName))) {
+                        continue;
+                    }
+                    printedAnything = true;
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  Connection bindings to services:");
+                        printed = true;
+                    }
+                    pw.print("  * "); pw.println(cr);
+                    cr.dump(pw, "    ");
+                }
+            }
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    /**
+     * There are three ways to call this:
+     *  - no service specified: dump all the services
+     *  - a flattened component name that matched an existing service was specified as the
+     *    first arg: dump that one service
+     *  - the first arg isn't the flattened component name of an existing service:
+     *    dump all services whose component contains the first arg as a substring
+     */
+    protected boolean dumpService(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+
+        synchronized (this) {
+            int[] users = mAm.getUsersLocked();
+            if ("all".equals(name)) {
+                for (int user : users) {
+                    ServiceMap smap = mServiceMap.get(user);
+                    if (smap == null) {
+                        continue;
+                    }
+                    ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+                    for (int i=0; i<alls.size(); i++) {
+                        ServiceRecord r1 = alls.valueAt(i);
+                        services.add(r1);
+                    }
+                }
+            } else {
+                ComponentName componentName = name != null
+                        ? ComponentName.unflattenFromString(name) : null;
+                int objectId = 0;
+                if (componentName == null) {
+                    // Not a '/' separated full component name; maybe an object ID?
+                    try {
+                        objectId = Integer.parseInt(name, 16);
+                        name = null;
+                        componentName = null;
+                    } catch (RuntimeException e) {
+                    }
+                }
+
+                for (int user : users) {
+                    ServiceMap smap = mServiceMap.get(user);
+                    if (smap == null) {
+                        continue;
+                    }
+                    ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+                    for (int i=0; i<alls.size(); i++) {
+                        ServiceRecord r1 = alls.valueAt(i);
+                        if (componentName != null) {
+                            if (r1.name.equals(componentName)) {
+                                services.add(r1);
+                            }
+                        } else if (name != null) {
+                            if (r1.name.flattenToString().contains(name)) {
+                                services.add(r1);
+                            }
+                        } else if (System.identityHashCode(r1) == objectId) {
+                            services.add(r1);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (services.size() <= 0) {
+            return false;
+        }
+
+        boolean needSep = false;
+        for (int i=0; i<services.size(); i++) {
+            if (needSep) {
+                pw.println();
+            }
+            needSep = true;
+            dumpService("", fd, pw, services.get(i), args, dumpAll);
+        }
+        return true;
+    }
+
+    /**
+     * Invokes IApplicationThread.dumpService() on the thread of the specified service if
+     * there is a thread associated with the service.
+     */
+    private void dumpService(String prefix, FileDescriptor fd, PrintWriter pw,
+            final ServiceRecord r, String[] args, boolean dumpAll) {
+        String innerPrefix = prefix + "  ";
+        synchronized (this) {
+            pw.print(prefix); pw.print("SERVICE ");
+                    pw.print(r.shortName); pw.print(" ");
+                    pw.print(Integer.toHexString(System.identityHashCode(r)));
+                    pw.print(" pid=");
+                    if (r.app != null) pw.println(r.app.pid);
+                    else pw.println("(not running)");
+            if (dumpAll) {
+                r.dump(pw, innerPrefix);
+            }
+        }
+        if (r.app != null && r.app.thread != null) {
+            pw.print(prefix); pw.println("  Client:");
+            pw.flush();
+            try {
+                TransferPipe tp = new TransferPipe();
+                try {
+                    r.app.thread.dumpService(tp.getWriteFd().getFileDescriptor(), r, args);
+                    tp.setBufferPrefix(prefix + "    ");
+                    tp.go(fd);
+                } finally {
+                    tp.kill();
+                }
+            } catch (IOException e) {
+                pw.println(prefix + "    Failure while dumping the service: " + e);
+            } catch (RemoteException e) {
+                pw.println(prefix + "    Got a RemoteException while dumping the service");
+            }
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
new file mode 100644
index 0000000..4540c1c
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -0,0 +1,16548 @@
+/*
+ * Copyright (C) 2006-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+
+import android.app.AppOpsManager;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
+import android.appwidget.AppWidgetManager;
+import android.graphics.Rect;
+import android.util.ArrayMap;
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.ProcessMap;
+import com.android.internal.app.ProcessStats;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.TransferPipe;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.MemInfoReader;
+import com.android.internal.util.Preconditions;
+import com.android.server.AppOpsService;
+import com.android.server.AttributeCache;
+import com.android.server.IntentResolver;
+import com.android.server.SystemServer;
+import com.android.server.Watchdog;
+import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.firewall.IntentFirewall;
+import com.android.server.pm.UserManagerService;
+import com.android.server.wm.AppTransition;
+import com.android.server.wm.WindowManagerService;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import dalvik.system.Zygote;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
+import android.app.ActivityThread;
+import android.app.AlertDialog;
+import android.app.AppGlobals;
+import android.app.ApplicationErrorReport;
+import android.app.Dialog;
+import android.app.IActivityController;
+import android.app.IApplicationThread;
+import android.app.IInstrumentationWatcher;
+import android.app.INotificationManager;
+import android.app.IProcessObserver;
+import android.app.IServiceConnection;
+import android.app.IStopUserCallback;
+import android.app.IThumbnailReceiver;
+import android.app.IUiAutomationConnection;
+import android.app.IUserSwitchObserver;
+import android.app.Instrumentation;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.backup.IBackupManager;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.IContentProvider;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PathPermission;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.net.Proxy;
+import android.net.ProxyProperties;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.DropBoxManager;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPermissionController;
+import android.os.IRemoteCallback;
+import android.os.IUserManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UpdateLock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.AtomicFile;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Pair;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+public final class ActivityManagerService extends ActivityManagerNative
+        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
+    private static final String USER_DATA_DIR = "/data/user/";
+    static final String TAG = "ActivityManager";
+    static final String TAG_MU = "ActivityManagerServiceMU";
+    static final boolean DEBUG = false;
+    static final boolean localLOGV = DEBUG;
+    static final boolean DEBUG_BACKUP = localLOGV || false;
+    static final boolean DEBUG_BROADCAST = localLOGV || false;
+    static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
+    static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false;
+    static final boolean DEBUG_CLEANUP = localLOGV || false;
+    static final boolean DEBUG_CONFIGURATION = localLOGV || false;
+    static final boolean DEBUG_FOCUS = false;
+    static final boolean DEBUG_IMMERSIVE = localLOGV || false;
+    static final boolean DEBUG_MU = localLOGV || false;
+    static final boolean DEBUG_OOM_ADJ = localLOGV || false;
+    static final boolean DEBUG_LRU = localLOGV || false;
+    static final boolean DEBUG_PAUSE = localLOGV || false;
+    static final boolean DEBUG_POWER = localLOGV || false;
+    static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
+    static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false;
+    static final boolean DEBUG_PROCESSES = localLOGV || false;
+    static final boolean DEBUG_PROVIDER = localLOGV || false;
+    static final boolean DEBUG_RESULTS = localLOGV || false;
+    static final boolean DEBUG_SERVICE = localLOGV || false;
+    static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
+    static final boolean DEBUG_STACK = localLOGV || false;
+    static final boolean DEBUG_SWITCH = localLOGV || false;
+    static final boolean DEBUG_TASKS = localLOGV || false;
+    static final boolean DEBUG_THUMBNAILS = localLOGV || false;
+    static final boolean DEBUG_TRANSITION = localLOGV || false;
+    static final boolean DEBUG_URI_PERMISSION = localLOGV || false;
+    static final boolean DEBUG_USER_LEAVING = localLOGV || false;
+    static final boolean DEBUG_VISBILITY = localLOGV || false;
+    static final boolean DEBUG_PSS = localLOGV || false;
+    static final boolean DEBUG_LOCKSCREEN = localLOGV || false;
+    static final boolean VALIDATE_TOKENS = false;
+    static final boolean SHOW_ACTIVITY_START_TIME = true;
+
+    // Control over CPU and battery monitoring.
+    static final long BATTERY_STATS_TIME = 30*60*1000;      // write battery stats every 30 minutes.
+    static final boolean MONITOR_CPU_USAGE = true;
+    static final long MONITOR_CPU_MIN_TIME = 5*1000;        // don't sample cpu less than every 5 seconds.
+    static final long MONITOR_CPU_MAX_TIME = 0x0fffffff;    // wait possibly forever for next cpu sample.
+    static final boolean MONITOR_THREAD_CPU_USAGE = false;
+
+    // The flags that are set for all calls we make to the package manager.
+    static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES;
+
+    private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+
+    static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
+
+    // Maximum number of recent tasks that we can remember.
+    static final int MAX_RECENT_TASKS = ActivityManager.isLowRamDeviceStatic() ? 10 : 20;
+
+    // Amount of time after a call to stopAppSwitches() during which we will
+    // prevent further untrusted switches from happening.
+    static final long APP_SWITCH_DELAY_TIME = 5*1000;
+
+    // How long we wait for a launched process to attach to the activity manager
+    // before we decide it's never going to come up for real.
+    static final int PROC_START_TIMEOUT = 10*1000;
+
+    // How long we wait for a launched process to attach to the activity manager
+    // before we decide it's never going to come up for real, when the process was
+    // started with a wrapper for instrumentation (such as Valgrind) because it
+    // could take much longer than usual.
+    static final int PROC_START_TIMEOUT_WITH_WRAPPER = 300*1000;
+
+    // How long to wait after going idle before forcing apps to GC.
+    static final int GC_TIMEOUT = 5*1000;
+
+    // The minimum amount of time between successive GC requests for a process.
+    static final int GC_MIN_INTERVAL = 60*1000;
+
+    // The minimum amount of time between successive PSS requests for a process.
+    static final int FULL_PSS_MIN_INTERVAL = 10*60*1000;
+
+    // The minimum amount of time between successive PSS requests for a process
+    // when the request is due to the memory state being lowered.
+    static final int FULL_PSS_LOWERED_INTERVAL = 2*60*1000;
+
+    // The rate at which we check for apps using excessive power -- 15 mins.
+    static final int POWER_CHECK_DELAY = (DEBUG_POWER_QUICK ? 2 : 15) * 60*1000;
+
+    // The minimum sample duration we will allow before deciding we have
+    // enough data on wake locks to start killing things.
+    static final int WAKE_LOCK_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
+
+    // The minimum sample duration we will allow before deciding we have
+    // enough data on CPU usage to start killing things.
+    static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
+
+    // How long we allow a receiver to run before giving up on it.
+    static final int BROADCAST_FG_TIMEOUT = 10*1000;
+    static final int BROADCAST_BG_TIMEOUT = 60*1000;
+
+    // How long we wait until we timeout on key dispatching.
+    static final int KEY_DISPATCHING_TIMEOUT = 5*1000;
+
+    // How long we wait until we timeout on key dispatching during instrumentation.
+    static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60*1000;
+
+    // Amount of time we wait for observers to handle a user switch before
+    // giving up on them and unfreezing the screen.
+    static final int USER_SWITCH_TIMEOUT = 2*1000;
+
+    // Maximum number of users we allow to be running at a time.
+    static final int MAX_RUNNING_USERS = 3;
+
+    // How long to wait in getAssistContextExtras for the activity and foreground services
+    // to respond with the result.
+    static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500;
+
+    // Maximum number of persisted Uri grants a package is allowed
+    static final int MAX_PERSISTED_URI_GRANTS = 128;
+
+    static final int MY_PID = Process.myPid();
+
+    static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    // How many bytes to write into the dropbox log before truncating
+    static final int DROPBOX_MAX_SIZE = 256 * 1024;
+
+    /** Run all ActivityStacks through this */
+    ActivityStackSupervisor mStackSupervisor;
+
+    public IntentFirewall mIntentFirewall;
+
+    // Whether we should show our dialogs (ANR, crash, etc) or just perform their
+    // default actuion automatically.  Important for devices without direct input
+    // devices.
+    private boolean mShowDialogs = true;
+
+    /**
+     * Description of a request to start a new activity, which has been held
+     * due to app switches being disabled.
+     */
+    static class PendingActivityLaunch {
+        final ActivityRecord r;
+        final ActivityRecord sourceRecord;
+        final int startFlags;
+        final ActivityStack stack;
+
+        PendingActivityLaunch(ActivityRecord _r, ActivityRecord _sourceRecord,
+                int _startFlags, ActivityStack _stack) {
+            r = _r;
+            sourceRecord = _sourceRecord;
+            startFlags = _startFlags;
+            stack = _stack;
+        }
+    }
+
+    final ArrayList<PendingActivityLaunch> mPendingActivityLaunches
+            = new ArrayList<PendingActivityLaunch>();
+
+    BroadcastQueue mFgBroadcastQueue;
+    BroadcastQueue mBgBroadcastQueue;
+    // Convenient for easy iteration over the queues. Foreground is first
+    // so that dispatch of foreground broadcasts gets precedence.
+    final BroadcastQueue[] mBroadcastQueues = new BroadcastQueue[2];
+
+    BroadcastQueue broadcastQueueForIntent(Intent intent) {
+        final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
+        if (DEBUG_BACKGROUND_BROADCAST) {
+            Slog.i(TAG, "Broadcast intent " + intent + " on "
+                    + (isFg ? "foreground" : "background")
+                    + " queue");
+        }
+        return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
+    }
+
+    BroadcastRecord broadcastRecordForReceiverLocked(IBinder receiver) {
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            BroadcastRecord r = queue.getMatchingOrderedReceiver(receiver);
+            if (r != null) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Activity we have told the window manager to have key focus.
+     */
+    ActivityRecord mFocusedActivity = null;
+
+    /**
+     * List of intents that were used to start the most recent tasks.
+     */
+    private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+
+    public class PendingAssistExtras extends Binder implements Runnable {
+        public final ActivityRecord activity;
+        public boolean haveResult = false;
+        public Bundle result = null;
+        public PendingAssistExtras(ActivityRecord _activity) {
+            activity = _activity;
+        }
+        @Override
+        public void run() {
+            Slog.w(TAG, "getAssistContextExtras failed: timeout retrieving from " + activity);
+            synchronized (this) {
+                haveResult = true;
+                notifyAll();
+            }
+        }
+    }
+
+    final ArrayList<PendingAssistExtras> mPendingAssistExtras
+            = new ArrayList<PendingAssistExtras>();
+
+    /**
+     * Process management.
+     */
+    final ProcessList mProcessList = new ProcessList();
+
+    /**
+     * All of the applications we currently have running organized by name.
+     * The keys are strings of the application package name (as
+     * returned by the package manager), and the keys are ApplicationRecord
+     * objects.
+     */
+    final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
+
+    /**
+     * Tracking long-term execution of processes to look for abuse and other
+     * bad app behavior.
+     */
+    final ProcessStatsService mProcessStats;
+
+    /**
+     * The currently running isolated processes.
+     */
+    final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<ProcessRecord>();
+
+    /**
+     * Counter for assigning isolated process uids, to avoid frequently reusing the
+     * same ones.
+     */
+    int mNextIsolatedProcessUid = 0;
+
+    /**
+     * The currently running heavy-weight process, if any.
+     */
+    ProcessRecord mHeavyWeightProcess = null;
+
+    /**
+     * The last time that various processes have crashed.
+     */
+    final ProcessMap<Long> mProcessCrashTimes = new ProcessMap<Long>();
+
+    /**
+     * Information about a process that is currently marked as bad.
+     */
+    static final class BadProcessInfo {
+        BadProcessInfo(long time, String shortMsg, String longMsg, String stack) {
+            this.time = time;
+            this.shortMsg = shortMsg;
+            this.longMsg = longMsg;
+            this.stack = stack;
+        }
+
+        final long time;
+        final String shortMsg;
+        final String longMsg;
+        final String stack;
+    }
+
+    /**
+     * Set of applications that we consider to be bad, and will reject
+     * incoming broadcasts from (which the user has no control over).
+     * Processes are added to this set when they have crashed twice within
+     * a minimum amount of time; they are removed from it when they are
+     * later restarted (hopefully due to some user action).  The value is the
+     * time it was added to the list.
+     */
+    final ProcessMap<BadProcessInfo> mBadProcesses = new ProcessMap<BadProcessInfo>();
+
+    /**
+     * All of the processes we currently have running organized by pid.
+     * The keys are the pid running the application.
+     *
+     * <p>NOTE: This object is protected by its own lock, NOT the global
+     * activity manager lock!
+     */
+    final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
+
+    /**
+     * All of the processes that have been forced to be foreground.  The key
+     * is the pid of the caller who requested it (we hold a death
+     * link on it).
+     */
+    abstract class ForegroundToken implements IBinder.DeathRecipient {
+        int pid;
+        IBinder token;
+    }
+    final SparseArray<ForegroundToken> mForegroundProcesses = new SparseArray<ForegroundToken>();
+
+    /**
+     * List of records for processes that someone had tried to start before the
+     * system was ready.  We don't start them at that point, but ensure they
+     * are started by the time booting is complete.
+     */
+    final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>();
+
+    /**
+     * List of persistent applications that are in the process
+     * of being started.
+     */
+    final ArrayList<ProcessRecord> mPersistentStartingProcesses = new ArrayList<ProcessRecord>();
+
+    /**
+     * Processes that are being forcibly torn down.
+     */
+    final ArrayList<ProcessRecord> mRemovedProcesses = new ArrayList<ProcessRecord>();
+
+    /**
+     * List of running applications, sorted by recent usage.
+     * The first entry in the list is the least recently used.
+     */
+    final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>();
+
+    /**
+     * Where in mLruProcesses that the processes hosting activities start.
+     */
+    int mLruProcessActivityStart = 0;
+
+    /**
+     * Where in mLruProcesses that the processes hosting services start.
+     * This is after (lower index) than mLruProcessesActivityStart.
+     */
+    int mLruProcessServiceStart = 0;
+
+    /**
+     * List of processes that should gc as soon as things are idle.
+     */
+    final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<ProcessRecord>();
+
+    /**
+     * Processes we want to collect PSS data from.
+     */
+    final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>();
+
+    /**
+     * Last time we requested PSS data of all processes.
+     */
+    long mLastFullPssTime = SystemClock.uptimeMillis();
+
+    /**
+     * This is the process holding what we currently consider to be
+     * the "home" activity.
+     */
+    ProcessRecord mHomeProcess;
+
+    /**
+     * This is the process holding the activity the user last visited that
+     * is in a different process from the one they are currently in.
+     */
+    ProcessRecord mPreviousProcess;
+
+    /**
+     * The time at which the previous process was last visible.
+     */
+    long mPreviousProcessVisibleTime;
+
+    /**
+     * Which uses have been started, so are allowed to run code.
+     */
+    final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>();
+
+    /**
+     * LRU list of history of current users.  Most recently current is at the end.
+     */
+    final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
+
+    /**
+     * Constant array of the users that are currently started.
+     */
+    int[] mStartedUserArray = new int[] { 0 };
+
+    /**
+     * Registered observers of the user switching mechanics.
+     */
+    final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
+            = new RemoteCallbackList<IUserSwitchObserver>();
+
+    /**
+     * Currently active user switch.
+     */
+    Object mCurUserSwitchCallback;
+
+    /**
+     * Packages that the user has asked to have run in screen size
+     * compatibility mode instead of filling the screen.
+     */
+    final CompatModePackages mCompatModePackages;
+
+    /**
+     * Set of IntentSenderRecord objects that are currently active.
+     */
+    final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords
+            = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>();
+
+    /**
+     * Fingerprints (hashCode()) of stack traces that we've
+     * already logged DropBox entries for.  Guarded by itself.  If
+     * something (rogue user app) forces this over
+     * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared.
+     */
+    private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
+    private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
+
+    /**
+     * Strict Mode background batched logging state.
+     *
+     * The string buffer is guarded by itself, and its lock is also
+     * used to determine if another batched write is already
+     * in-flight.
+     */
+    private final StringBuilder mStrictModeBuffer = new StringBuilder();
+
+    /**
+     * Keeps track of all IIntentReceivers that have been registered for
+     * broadcasts.  Hash keys are the receiver IBinder, hash value is
+     * a ReceiverList.
+     */
+    final HashMap<IBinder, ReceiverList> mRegisteredReceivers =
+            new HashMap<IBinder, ReceiverList>();
+
+    /**
+     * Resolver for broadcast intents to registered receivers.
+     * Holds BroadcastFilter (subclass of IntentFilter).
+     */
+    final IntentResolver<BroadcastFilter, BroadcastFilter> mReceiverResolver
+            = new IntentResolver<BroadcastFilter, BroadcastFilter>() {
+        @Override
+        protected boolean allowFilterResult(
+                BroadcastFilter filter, List<BroadcastFilter> dest) {
+            IBinder target = filter.receiverList.receiver.asBinder();
+            for (int i=dest.size()-1; i>=0; i--) {
+                if (dest.get(i).receiverList.receiver.asBinder() == target) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected BroadcastFilter newResult(BroadcastFilter filter, int match, int userId) {
+            if (userId == UserHandle.USER_ALL || filter.owningUserId == UserHandle.USER_ALL
+                    || userId == filter.owningUserId) {
+                return super.newResult(filter, match, userId);
+            }
+            return null;
+        }
+
+        @Override
+        protected BroadcastFilter[] newArray(int size) {
+            return new BroadcastFilter[size];
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, BroadcastFilter filter) {
+            return packageName.equals(filter.packageName);
+        }
+    };
+
+    /**
+     * State of all active sticky broadcasts per user.  Keys are the action of the
+     * sticky Intent, values are an ArrayList of all broadcasted intents with
+     * that action (which should usually be one).  The SparseArray is keyed
+     * by the user ID the sticky is for, and can include UserHandle.USER_ALL
+     * for stickies that are sent to all users.
+     */
+    final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts =
+            new SparseArray<ArrayMap<String, ArrayList<Intent>>>();
+
+    final ActiveServices mServices;
+
+    /**
+     * Backup/restore process management
+     */
+    String mBackupAppName = null;
+    BackupRecord mBackupTarget = null;
+
+    /**
+     * List of PendingThumbnailsRecord objects of clients who are still
+     * waiting to receive all of the thumbnails for a task.
+     */
+    final ArrayList<PendingThumbnailsRecord> mPendingThumbnails =
+            new ArrayList<PendingThumbnailsRecord>();
+
+    final ProviderMap mProviderMap;
+
+    /**
+     * List of content providers who have clients waiting for them.  The
+     * application is currently being launched and the provider will be
+     * removed from this list once it is published.
+     */
+    final ArrayList<ContentProviderRecord> mLaunchingProviders
+            = new ArrayList<ContentProviderRecord>();
+
+    /**
+     * File storing persisted {@link #mGrantedUriPermissions}.
+     */
+    private final AtomicFile mGrantFile;
+
+    /** XML constants used in {@link #mGrantFile} */
+    private static final String TAG_URI_GRANTS = "uri-grants";
+    private static final String TAG_URI_GRANT = "uri-grant";
+    private static final String ATTR_USER_HANDLE = "userHandle";
+    private static final String ATTR_SOURCE_PKG = "sourcePkg";
+    private static final String ATTR_TARGET_PKG = "targetPkg";
+    private static final String ATTR_URI = "uri";
+    private static final String ATTR_MODE_FLAGS = "modeFlags";
+    private static final String ATTR_CREATED_TIME = "createdTime";
+
+    /**
+     * Global set of specific {@link Uri} permissions that have been granted.
+     * This optimized lookup structure maps from {@link UriPermission#targetUid}
+     * to {@link UriPermission#uri} to {@link UriPermission}.
+     */
+    @GuardedBy("this")
+    private final SparseArray<ArrayMap<Uri, UriPermission>>
+            mGrantedUriPermissions = new SparseArray<ArrayMap<Uri, UriPermission>>();
+
+    CoreSettingsObserver mCoreSettingsObserver;
+
+    /**
+     * Thread-local storage used to carry caller permissions over through
+     * indirect content-provider access.
+     */
+    private class Identity {
+        public int pid;
+        public int uid;
+
+        Identity(int _pid, int _uid) {
+            pid = _pid;
+            uid = _uid;
+        }
+    }
+
+    private static ThreadLocal<Identity> sCallerIdentity = new ThreadLocal<Identity>();
+
+    /**
+     * All information we have collected about the runtime performance of
+     * any user id that can impact battery performance.
+     */
+    final BatteryStatsService mBatteryStatsService;
+
+    /**
+     * Information about component usage
+     */
+    final UsageStatsService mUsageStatsService;
+
+    /**
+     * Information about and control over application operations
+     */
+    final AppOpsService mAppOpsService;
+
+    /**
+     * Current configuration information.  HistoryRecord objects are given
+     * a reference to this object to indicate which configuration they are
+     * currently running in, so this object must be kept immutable.
+     */
+    Configuration mConfiguration = new Configuration();
+
+    /**
+     * Current sequencing integer of the configuration, for skipping old
+     * configurations.
+     */
+    int mConfigurationSeq = 0;
+
+    /**
+     * Hardware-reported OpenGLES version.
+     */
+    final int GL_ES_VERSION;
+
+    /**
+     * List of initialization arguments to pass to all processes when binding applications to them.
+     * For example, references to the commonly used services.
+     */
+    HashMap<String, IBinder> mAppBindArgs;
+
+    /**
+     * Temporary to avoid allocations.  Protected by main lock.
+     */
+    final StringBuilder mStringBuilder = new StringBuilder(256);
+
+    /**
+     * Used to control how we initialize the service.
+     */
+    boolean mStartRunning = false;
+    ComponentName mTopComponent;
+    String mTopAction;
+    String mTopData;
+    boolean mProcessesReady = false;
+    boolean mSystemReady = false;
+    boolean mBooting = false;
+    boolean mWaitingUpdate = false;
+    boolean mDidUpdate = false;
+    boolean mOnBattery = false;
+    boolean mLaunchWarningShown = false;
+
+    Context mContext;
+
+    int mFactoryTest;
+
+    boolean mCheckedForSetup;
+
+    /**
+     * The time at which we will allow normal application switches again,
+     * after a call to {@link #stopAppSwitches()}.
+     */
+    long mAppSwitchesAllowedTime;
+
+    /**
+     * This is set to true after the first switch after mAppSwitchesAllowedTime
+     * is set; any switches after that will clear the time.
+     */
+    boolean mDidAppSwitch;
+
+    /**
+     * Last time (in realtime) at which we checked for power usage.
+     */
+    long mLastPowerCheckRealtime;
+
+    /**
+     * Last time (in uptime) at which we checked for power usage.
+     */
+    long mLastPowerCheckUptime;
+
+    /**
+     * Set while we are wanting to sleep, to prevent any
+     * activities from being started/resumed.
+     */
+    boolean mSleeping = false;
+
+    /**
+     * State of external calls telling us if the device is asleep.
+     */
+    boolean mWentToSleep = false;
+
+    /**
+     * State of external call telling us if the lock screen is shown.
+     */
+    boolean mLockScreenShown = false;
+
+    /**
+     * Set if we are shutting down the system, similar to sleeping.
+     */
+    boolean mShuttingDown = false;
+
+    /**
+     * Current sequence id for oom_adj computation traversal.
+     */
+    int mAdjSeq = 0;
+
+    /**
+     * Current sequence id for process LRU updating.
+     */
+    int mLruSeq = 0;
+
+    /**
+     * Keep track of the non-cached/empty process we last found, to help
+     * determine how to distribute cached/empty processes next time.
+     */
+    int mNumNonCachedProcs = 0;
+
+    /**
+     * Keep track of the number of cached hidden procs, to balance oom adj
+     * distribution between those and empty procs.
+     */
+    int mNumCachedHiddenProcs = 0;
+
+    /**
+     * Keep track of the number of service processes we last found, to
+     * determine on the next iteration which should be B services.
+     */
+    int mNumServiceProcs = 0;
+    int mNewNumAServiceProcs = 0;
+    int mNewNumServiceProcs = 0;
+
+    /**
+     * Allow the current computed overall memory level of the system to go down?
+     * This is set to false when we are killing processes for reasons other than
+     * memory management, so that the now smaller process list will not be taken as
+     * an indication that memory is tighter.
+     */
+    boolean mAllowLowerMemLevel = false;
+
+    /**
+     * The last computed memory level, for holding when we are in a state that
+     * processes are going away for other reasons.
+     */
+    int mLastMemoryLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+
+    /**
+     * The last total number of process we have, to determine if changes actually look
+     * like a shrinking number of process due to lower RAM.
+     */
+    int mLastNumProcesses;
+
+    /**
+     * The uptime of the last time we performed idle maintenance.
+     */
+    long mLastIdleTime = SystemClock.uptimeMillis();
+
+    /**
+     * Total time spent with RAM that has been added in the past since the last idle time.
+     */
+    long mLowRamTimeSinceLastIdle = 0;
+
+    /**
+     * If RAM is currently low, when that horrible situatin started.
+     */
+    long mLowRamStartTime = 0;
+
+    /**
+     * This is set if we had to do a delayed dexopt of an app before launching
+     * it, to increasing the ANR timeouts in that case.
+     */
+    boolean mDidDexOpt;
+
+    String mDebugApp = null;
+    boolean mWaitForDebugger = false;
+    boolean mDebugTransient = false;
+    String mOrigDebugApp = null;
+    boolean mOrigWaitForDebugger = false;
+    boolean mAlwaysFinishActivities = false;
+    IActivityController mController = null;
+    String mProfileApp = null;
+    ProcessRecord mProfileProc = null;
+    String mProfileFile;
+    ParcelFileDescriptor mProfileFd;
+    int mProfileType = 0;
+    boolean mAutoStopProfiler = false;
+    String mOpenGlTraceApp = null;
+
+    static class ProcessChangeItem {
+        static final int CHANGE_ACTIVITIES = 1<<0;
+        static final int CHANGE_IMPORTANCE= 1<<1;
+        int changes;
+        int uid;
+        int pid;
+        int importance;
+        boolean foregroundActivities;
+    }
+
+    final RemoteCallbackList<IProcessObserver> mProcessObservers
+            = new RemoteCallbackList<IProcessObserver>();
+    ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
+
+    final ArrayList<ProcessChangeItem> mPendingProcessChanges
+            = new ArrayList<ProcessChangeItem>();
+    final ArrayList<ProcessChangeItem> mAvailProcessChanges
+            = new ArrayList<ProcessChangeItem>();
+
+    /**
+     * Runtime CPU use collection thread.  This object's lock is used to
+     * protect all related state.
+     */
+    final Thread mProcessCpuThread;
+
+    /**
+     * Used to collect process stats when showing not responding dialog.
+     * Protected by mProcessCpuThread.
+     */
+    final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(
+            MONITOR_THREAD_CPU_USAGE);
+    final AtomicLong mLastCpuTime = new AtomicLong(0);
+    final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true);
+
+    long mLastWriteTime = 0;
+
+    /**
+     * Used to retain an update lock when the foreground activity is in
+     * immersive mode.
+     */
+    final UpdateLock mUpdateLock = new UpdateLock("immersive");
+
+    /**
+     * Set to true after the system has finished booting.
+     */
+    boolean mBooted = false;
+
+    int mProcessLimit = ProcessList.MAX_CACHED_APPS;
+    int mProcessLimitOverride = -1;
+
+    WindowManagerService mWindowManager;
+
+    static ActivityManagerService mSelf;
+    static ActivityThread mSystemThread;
+
+    int mCurrentUserId = 0;
+    private UserManagerService mUserManager;
+
+    private final class AppDeathRecipient implements IBinder.DeathRecipient {
+        final ProcessRecord mApp;
+        final int mPid;
+        final IApplicationThread mAppThread;
+
+        AppDeathRecipient(ProcessRecord app, int pid,
+                IApplicationThread thread) {
+            if (localLOGV) Slog.v(
+                TAG, "New death recipient " + this
+                + " for thread " + thread.asBinder());
+            mApp = app;
+            mPid = pid;
+            mAppThread = thread;
+        }
+
+        @Override
+        public void binderDied() {
+            if (localLOGV) Slog.v(
+                TAG, "Death received in " + this
+                + " for thread " + mAppThread.asBinder());
+            synchronized(ActivityManagerService.this) {
+                appDiedLocked(mApp, mPid, mAppThread);
+            }
+        }
+    }
+
+    static final int SHOW_ERROR_MSG = 1;
+    static final int SHOW_NOT_RESPONDING_MSG = 2;
+    static final int SHOW_FACTORY_ERROR_MSG = 3;
+    static final int UPDATE_CONFIGURATION_MSG = 4;
+    static final int GC_BACKGROUND_PROCESSES_MSG = 5;
+    static final int WAIT_FOR_DEBUGGER_MSG = 6;
+    static final int SERVICE_TIMEOUT_MSG = 12;
+    static final int UPDATE_TIME_ZONE = 13;
+    static final int SHOW_UID_ERROR_MSG = 14;
+    static final int IM_FEELING_LUCKY_MSG = 15;
+    static final int PROC_START_TIMEOUT_MSG = 20;
+    static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
+    static final int KILL_APPLICATION_MSG = 22;
+    static final int FINALIZE_PENDING_INTENT_MSG = 23;
+    static final int POST_HEAVY_NOTIFICATION_MSG = 24;
+    static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25;
+    static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26;
+    static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27;
+    static final int CLEAR_DNS_CACHE_MSG = 28;
+    static final int UPDATE_HTTP_PROXY_MSG = 29;
+    static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30;
+    static final int DISPATCH_PROCESSES_CHANGED = 31;
+    static final int DISPATCH_PROCESS_DIED = 32;
+    static final int REPORT_MEM_USAGE_MSG = 33;
+    static final int REPORT_USER_SWITCH_MSG = 34;
+    static final int CONTINUE_USER_SWITCH_MSG = 35;
+    static final int USER_SWITCH_TIMEOUT_MSG = 36;
+    static final int IMMERSIVE_MODE_LOCK_MSG = 37;
+    static final int PERSIST_URI_GRANTS_MSG = 38;
+    static final int REQUEST_ALL_PSS_MSG = 39;
+
+    static final int FIRST_ACTIVITY_STACK_MSG = 100;
+    static final int FIRST_BROADCAST_QUEUE_MSG = 200;
+    static final int FIRST_COMPAT_MODE_MSG = 300;
+    static final int FIRST_SUPERVISOR_STACK_MSG = 100;
+
+    AlertDialog mUidAlert;
+    CompatModeDialog mCompatModeDialog;
+    long mLastMemUsageReportTime = 0;
+
+    /**
+     * Flag whether the current user is a "monkey", i.e. whether
+     * the UI is driven by a UI automation tool.
+     */
+    private boolean mUserIsMonkey;
+
+    final Handler mHandler = new Handler() {
+        //public Handler() {
+        //    if (localLOGV) Slog.v(TAG, "Handler started!");
+        //}
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case SHOW_ERROR_MSG: {
+                HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
+                boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
+                        Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+                synchronized (ActivityManagerService.this) {
+                    ProcessRecord proc = (ProcessRecord)data.get("app");
+                    AppErrorResult res = (AppErrorResult) data.get("result");
+                    if (proc != null && proc.crashDialog != null) {
+                        Slog.e(TAG, "App already has crash dialog: " + proc);
+                        if (res != null) {
+                            res.set(0);
+                        }
+                        return;
+                    }
+                    if (!showBackground && UserHandle.getAppId(proc.uid)
+                            >= Process.FIRST_APPLICATION_UID && proc.userId != mCurrentUserId
+                            && proc.pid != MY_PID) {
+                        Slog.w(TAG, "Skipping crash dialog of " + proc + ": background");
+                        if (res != null) {
+                            res.set(0);
+                        }
+                        return;
+                    }
+                    if (mShowDialogs && !mSleeping && !mShuttingDown) {
+                        Dialog d = new AppErrorDialog(mContext,
+                                ActivityManagerService.this, res, proc);
+                        d.show();
+                        proc.crashDialog = d;
+                    } else {
+                        // The device is asleep, so just pretend that the user
+                        // saw a crash dialog and hit "force quit".
+                        if (res != null) {
+                            res.set(0);
+                        }
+                    }
+                }
+
+                ensureBootCompleted();
+            } break;
+            case SHOW_NOT_RESPONDING_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
+                    ProcessRecord proc = (ProcessRecord)data.get("app");
+                    if (proc != null && proc.anrDialog != null) {
+                        Slog.e(TAG, "App already has anr dialog: " + proc);
+                        return;
+                    }
+
+                    Intent intent = new Intent("android.intent.action.ANR");
+                    if (!mProcessesReady) {
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
+                    }
+                    broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            false, false, MY_PID, Process.SYSTEM_UID, 0 /* TODO: Verify */);
+
+                    if (mShowDialogs) {
+                        Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
+                                mContext, proc, (ActivityRecord)data.get("activity"),
+                                msg.arg1 != 0);
+                        d.show();
+                        proc.anrDialog = d;
+                    } else {
+                        // Just kill the app if there is no dialog to be shown.
+                        killAppAtUsersRequest(proc, null);
+                    }
+                }
+
+                ensureBootCompleted();
+            } break;
+            case SHOW_STRICT_MODE_VIOLATION_MSG: {
+                HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
+                synchronized (ActivityManagerService.this) {
+                    ProcessRecord proc = (ProcessRecord) data.get("app");
+                    if (proc == null) {
+                        Slog.e(TAG, "App not found when showing strict mode dialog.");
+                        break;
+                    }
+                    if (proc.crashDialog != null) {
+                        Slog.e(TAG, "App already has strict mode dialog: " + proc);
+                        return;
+                    }
+                    AppErrorResult res = (AppErrorResult) data.get("result");
+                    if (mShowDialogs && !mSleeping && !mShuttingDown) {
+                        Dialog d = new StrictModeViolationDialog(mContext,
+                                ActivityManagerService.this, res, proc);
+                        d.show();
+                        proc.crashDialog = d;
+                    } else {
+                        // The device is asleep, so just pretend that the user
+                        // saw a crash dialog and hit "force quit".
+                        res.set(0);
+                    }
+                }
+                ensureBootCompleted();
+            } break;
+            case SHOW_FACTORY_ERROR_MSG: {
+                Dialog d = new FactoryErrorDialog(
+                    mContext, msg.getData().getCharSequence("msg"));
+                d.show();
+                ensureBootCompleted();
+            } break;
+            case UPDATE_CONFIGURATION_MSG: {
+                final ContentResolver resolver = mContext.getContentResolver();
+                Settings.System.putConfiguration(resolver, (Configuration)msg.obj);
+            } break;
+            case GC_BACKGROUND_PROCESSES_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    performAppGcsIfAppropriateLocked();
+                }
+            } break;
+            case WAIT_FOR_DEBUGGER_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    ProcessRecord app = (ProcessRecord)msg.obj;
+                    if (msg.arg1 != 0) {
+                        if (!app.waitedForDebugger) {
+                            Dialog d = new AppWaitingForDebuggerDialog(
+                                    ActivityManagerService.this,
+                                    mContext, app);
+                            app.waitDialog = d;
+                            app.waitedForDebugger = true;
+                            d.show();
+                        }
+                    } else {
+                        if (app.waitDialog != null) {
+                            app.waitDialog.dismiss();
+                            app.waitDialog = null;
+                        }
+                    }
+                }
+            } break;
+            case SERVICE_TIMEOUT_MSG: {
+                if (mDidDexOpt) {
+                    mDidDexOpt = false;
+                    Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);
+                    nmsg.obj = msg.obj;
+                    mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);
+                    return;
+                }
+                mServices.serviceTimeout((ProcessRecord)msg.obj);
+            } break;
+            case UPDATE_TIME_ZONE: {
+                synchronized (ActivityManagerService.this) {
+                    for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                        ProcessRecord r = mLruProcesses.get(i);
+                        if (r.thread != null) {
+                            try {
+                                r.thread.updateTimeZone();
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG, "Failed to update time zone for: " + r.info.processName);
+                            }
+                        }
+                    }
+                }
+            } break;
+            case CLEAR_DNS_CACHE_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                        ProcessRecord r = mLruProcesses.get(i);
+                        if (r.thread != null) {
+                            try {
+                                r.thread.clearDnsCache();
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG, "Failed to clear dns cache for: " + r.info.processName);
+                            }
+                        }
+                    }
+                }
+            } break;
+            case UPDATE_HTTP_PROXY_MSG: {
+                ProxyProperties proxy = (ProxyProperties)msg.obj;
+                String host = "";
+                String port = "";
+                String exclList = "";
+                String pacFileUrl = null;
+                if (proxy != null) {
+                    host = proxy.getHost();
+                    port = Integer.toString(proxy.getPort());
+                    exclList = proxy.getExclusionList();
+                    pacFileUrl = proxy.getPacFileUrl();
+                }
+                synchronized (ActivityManagerService.this) {
+                    for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                        ProcessRecord r = mLruProcesses.get(i);
+                        if (r.thread != null) {
+                            try {
+                                r.thread.setHttpProxy(host, port, exclList, pacFileUrl);
+                            } catch (RemoteException ex) {
+                                Slog.w(TAG, "Failed to update http proxy for: " +
+                                        r.info.processName);
+                            }
+                        }
+                    }
+                }
+            } break;
+            case SHOW_UID_ERROR_MSG: {
+                String title = "System UIDs Inconsistent";
+                String text = "UIDs on the system are inconsistent, you need to wipe your"
+                        + " data partition or your device will be unstable.";
+                Log.e(TAG, title + ": " + text);
+                if (mShowDialogs) {
+                    // XXX This is a temporary dialog, no need to localize.
+                    AlertDialog d = new BaseErrorDialog(mContext);
+                    d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+                    d.setCancelable(false);
+                    d.setTitle(title);
+                    d.setMessage(text);
+                    d.setButton(DialogInterface.BUTTON_POSITIVE, "I'm Feeling Lucky",
+                            mHandler.obtainMessage(IM_FEELING_LUCKY_MSG));
+                    mUidAlert = d;
+                    d.show();
+                }
+            } break;
+            case IM_FEELING_LUCKY_MSG: {
+                if (mUidAlert != null) {
+                    mUidAlert.dismiss();
+                    mUidAlert = null;
+                }
+            } break;
+            case PROC_START_TIMEOUT_MSG: {
+                if (mDidDexOpt) {
+                    mDidDexOpt = false;
+                    Message nmsg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
+                    nmsg.obj = msg.obj;
+                    mHandler.sendMessageDelayed(nmsg, PROC_START_TIMEOUT);
+                    return;
+                }
+                ProcessRecord app = (ProcessRecord)msg.obj;
+                synchronized (ActivityManagerService.this) {
+                    processStartTimedOutLocked(app);
+                }
+            } break;
+            case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    doPendingActivityLaunchesLocked(true);
+                }
+            } break;
+            case KILL_APPLICATION_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    int appid = msg.arg1;
+                    boolean restart = (msg.arg2 == 1);
+                    Bundle bundle = (Bundle)msg.obj;
+                    String pkg = bundle.getString("pkg");
+                    String reason = bundle.getString("reason");
+                    forceStopPackageLocked(pkg, appid, restart, false, true, false,
+                            UserHandle.USER_ALL, reason);
+                }
+            } break;
+            case FINALIZE_PENDING_INTENT_MSG: {
+                ((PendingIntentRecord)msg.obj).completeFinalize();
+            } break;
+            case POST_HEAVY_NOTIFICATION_MSG: {
+                INotificationManager inm = NotificationManager.getService();
+                if (inm == null) {
+                    return;
+                }
+
+                ActivityRecord root = (ActivityRecord)msg.obj;
+                ProcessRecord process = root.app;
+                if (process == null) {
+                    return;
+                }
+
+                try {
+                    Context context = mContext.createPackageContext(process.info.packageName, 0);
+                    String text = mContext.getString(R.string.heavy_weight_notification,
+                            context.getApplicationInfo().loadLabel(context.getPackageManager()));
+                    Notification notification = new Notification();
+                    notification.icon = com.android.internal.R.drawable.stat_sys_adb; //context.getApplicationInfo().icon;
+                    notification.when = 0;
+                    notification.flags = Notification.FLAG_ONGOING_EVENT;
+                    notification.tickerText = text;
+                    notification.defaults = 0; // please be quiet
+                    notification.sound = null;
+                    notification.vibrate = null;
+                    notification.setLatestEventInfo(context, text,
+                            mContext.getText(R.string.heavy_weight_notification_detail),
+                            PendingIntent.getActivityAsUser(mContext, 0, root.intent,
+                                    PendingIntent.FLAG_CANCEL_CURRENT, null,
+                                    new UserHandle(root.userId)));
+
+                    try {
+                        int[] outId = new int[1];
+                        inm.enqueueNotificationWithTag("android", "android", null,
+                                R.string.heavy_weight_notification,
+                                notification, outId, root.userId);
+                    } catch (RuntimeException e) {
+                        Slog.w(ActivityManagerService.TAG,
+                                "Error showing notification for heavy-weight app", e);
+                    } catch (RemoteException e) {
+                    }
+                } catch (NameNotFoundException e) {
+                    Slog.w(TAG, "Unable to create context for heavy notification", e);
+                }
+            } break;
+            case CANCEL_HEAVY_NOTIFICATION_MSG: {
+                INotificationManager inm = NotificationManager.getService();
+                if (inm == null) {
+                    return;
+                }
+                try {
+                    inm.cancelNotificationWithTag("android", null,
+                            R.string.heavy_weight_notification,  msg.arg1);
+                } catch (RuntimeException e) {
+                    Slog.w(ActivityManagerService.TAG,
+                            "Error canceling notification for service", e);
+                } catch (RemoteException e) {
+                }
+            } break;
+            case CHECK_EXCESSIVE_WAKE_LOCKS_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    checkExcessivePowerUsageLocked(true);
+                    removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                    Message nmsg = obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                    sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+                }
+            } break;
+            case SHOW_COMPAT_MODE_DIALOG_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    ActivityRecord ar = (ActivityRecord)msg.obj;
+                    if (mCompatModeDialog != null) {
+                        if (mCompatModeDialog.mAppInfo.packageName.equals(
+                                ar.info.applicationInfo.packageName)) {
+                            return;
+                        }
+                        mCompatModeDialog.dismiss();
+                        mCompatModeDialog = null;
+                    }
+                    if (ar != null && false) {
+                        if (mCompatModePackages.getPackageAskCompatModeLocked(
+                                ar.packageName)) {
+                            int mode = mCompatModePackages.computeCompatModeLocked(
+                                    ar.info.applicationInfo);
+                            if (mode == ActivityManager.COMPAT_MODE_DISABLED
+                                    || mode == ActivityManager.COMPAT_MODE_ENABLED) {
+                                mCompatModeDialog = new CompatModeDialog(
+                                        ActivityManagerService.this, mContext,
+                                        ar.info.applicationInfo);
+                                mCompatModeDialog.show();
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+            case DISPATCH_PROCESSES_CHANGED: {
+                dispatchProcessesChanged();
+                break;
+            }
+            case DISPATCH_PROCESS_DIED: {
+                final int pid = msg.arg1;
+                final int uid = msg.arg2;
+                dispatchProcessDied(pid, uid);
+                break;
+            }
+            case REPORT_MEM_USAGE_MSG: {
+                final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj;
+                Thread thread = new Thread() {
+                    @Override public void run() {
+                        final SparseArray<ProcessMemInfo> infoMap
+                                = new SparseArray<ProcessMemInfo>(memInfos.size());
+                        for (int i=0, N=memInfos.size(); i<N; i++) {
+                            ProcessMemInfo mi = memInfos.get(i);
+                            infoMap.put(mi.pid, mi);
+                        }
+                        updateCpuStatsNow();
+                        synchronized (mProcessCpuThread) {
+                            final int N = mProcessCpuTracker.countStats();
+                            for (int i=0; i<N; i++) {
+                                ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                                if (st.vsize > 0) {
+                                    long pss = Debug.getPss(st.pid, null);
+                                    if (pss > 0) {
+                                        if (infoMap.indexOfKey(st.pid) < 0) {
+                                            ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid,
+                                                    ProcessList.NATIVE_ADJ, -1, "native", null);
+                                            mi.pss = pss;
+                                            memInfos.add(mi);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+
+                        long totalPss = 0;
+                        for (int i=0, N=memInfos.size(); i<N; i++) {
+                            ProcessMemInfo mi = memInfos.get(i);
+                            if (mi.pss == 0) {
+                                mi.pss = Debug.getPss(mi.pid, null);
+                            }
+                            totalPss += mi.pss;
+                        }
+                        Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
+                            @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) {
+                                if (lhs.oomAdj != rhs.oomAdj) {
+                                    return lhs.oomAdj < rhs.oomAdj ? -1 : 1;
+                                }
+                                if (lhs.pss != rhs.pss) {
+                                    return lhs.pss < rhs.pss ? 1 : -1;
+                                }
+                                return 0;
+                            }
+                        });
+
+                        StringBuilder tag = new StringBuilder(128);
+                        StringBuilder stack = new StringBuilder(128);
+                        tag.append("Low on memory -- ");
+                        appendMemBucket(tag, totalPss, "total", false);
+                        appendMemBucket(stack, totalPss, "total", true);
+
+                        StringBuilder logBuilder = new StringBuilder(1024);
+                        logBuilder.append("Low on memory:\n");
+
+                        boolean firstLine = true;
+                        int lastOomAdj = Integer.MIN_VALUE;
+                        for (int i=0, N=memInfos.size(); i<N; i++) {
+                            ProcessMemInfo mi = memInfos.get(i);
+
+                            if (mi.oomAdj != ProcessList.NATIVE_ADJ
+                                    && (mi.oomAdj < ProcessList.SERVICE_ADJ
+                                            || mi.oomAdj == ProcessList.HOME_APP_ADJ
+                                            || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) {
+                                if (lastOomAdj != mi.oomAdj) {
+                                    lastOomAdj = mi.oomAdj;
+                                    if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+                                        tag.append(" / ");
+                                    }
+                                    if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) {
+                                        if (firstLine) {
+                                            stack.append(":");
+                                            firstLine = false;
+                                        }
+                                        stack.append("\n\t at ");
+                                    } else {
+                                        stack.append("$");
+                                    }
+                                } else {
+                                    tag.append(" ");
+                                    stack.append("$");
+                                }
+                                if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+                                    appendMemBucket(tag, mi.pss, mi.name, false);
+                                }
+                                appendMemBucket(stack, mi.pss, mi.name, true);
+                                if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ
+                                        && ((i+1) >= N || memInfos.get(i+1).oomAdj != lastOomAdj)) {
+                                    stack.append("(");
+                                    for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) {
+                                        if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) {
+                                            stack.append(DUMP_MEM_OOM_LABEL[k]);
+                                            stack.append(":");
+                                            stack.append(DUMP_MEM_OOM_ADJ[k]);
+                                        }
+                                    }
+                                    stack.append(")");
+                                }
+                            }
+
+                            logBuilder.append("  ");
+                            logBuilder.append(ProcessList.makeOomAdjString(mi.oomAdj));
+                            logBuilder.append(' ');
+                            logBuilder.append(ProcessList.makeProcStateString(mi.procState));
+                            logBuilder.append(' ');
+                            ProcessList.appendRamKb(logBuilder, mi.pss);
+                            logBuilder.append(" kB: ");
+                            logBuilder.append(mi.name);
+                            logBuilder.append(" (");
+                            logBuilder.append(mi.pid);
+                            logBuilder.append(") ");
+                            logBuilder.append(mi.adjType);
+                            logBuilder.append('\n');
+                            if (mi.adjReason != null) {
+                                logBuilder.append("                      ");
+                                logBuilder.append(mi.adjReason);
+                                logBuilder.append('\n');
+                            }
+                        }
+
+                        logBuilder.append("           ");
+                        ProcessList.appendRamKb(logBuilder, totalPss);
+                        logBuilder.append(" kB: TOTAL\n");
+
+                        long[] infos = new long[Debug.MEMINFO_COUNT];
+                        Debug.getMemInfo(infos);
+                        logBuilder.append("  MemInfo: ");
+                        logBuilder.append(infos[Debug.MEMINFO_SLAB]).append(" kB slab, ");
+                        logBuilder.append(infos[Debug.MEMINFO_SHMEM]).append(" kB shmem, ");
+                        logBuilder.append(infos[Debug.MEMINFO_BUFFERS]).append(" kB buffers, ");
+                        logBuilder.append(infos[Debug.MEMINFO_CACHED]).append(" kB cached, ");
+                        logBuilder.append(infos[Debug.MEMINFO_FREE]).append(" kB free\n");
+                        if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) {
+                            logBuilder.append("  ZRAM: ");
+                            logBuilder.append(infos[Debug.MEMINFO_ZRAM_TOTAL]);
+                            logBuilder.append(" kB RAM, ");
+                            logBuilder.append(infos[Debug.MEMINFO_SWAP_TOTAL]);
+                            logBuilder.append(" kB swap total, ");
+                            logBuilder.append(infos[Debug.MEMINFO_SWAP_FREE]);
+                            logBuilder.append(" kB swap free\n");
+                        }
+                        Slog.i(TAG, logBuilder.toString());
+
+                        StringBuilder dropBuilder = new StringBuilder(1024);
+                        /*
+                        StringWriter oomSw = new StringWriter();
+                        PrintWriter oomPw = new FastPrintWriter(oomSw, false, 256);
+                        StringWriter catSw = new StringWriter();
+                        PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
+                        String[] emptyArgs = new String[] { };
+                        dumpApplicationMemoryUsage(null, oomPw, "  ", emptyArgs, true, catPw);
+                        oomPw.flush();
+                        String oomString = oomSw.toString();
+                        */
+                        dropBuilder.append(stack);
+                        dropBuilder.append('\n');
+                        dropBuilder.append('\n');
+                        dropBuilder.append(logBuilder);
+                        dropBuilder.append('\n');
+                        /*
+                        dropBuilder.append(oomString);
+                        dropBuilder.append('\n');
+                        */
+                        StringWriter catSw = new StringWriter();
+                        synchronized (ActivityManagerService.this) {
+                            PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
+                            String[] emptyArgs = new String[] { };
+                            catPw.println();
+                            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null);
+                            catPw.println();
+                            mServices.dumpServicesLocked(null, catPw, emptyArgs, 0,
+                                    false, false, null);
+                            catPw.println();
+                            dumpActivitiesLocked(null, catPw, emptyArgs, 0, false, false, null);
+                            catPw.flush();
+                        }
+                        dropBuilder.append(catSw.toString());
+                        addErrorToDropBox("lowmem", null, "system_server", null,
+                                null, tag.toString(), dropBuilder.toString(), null, null);
+                        //Slog.i(TAG, "Sent to dropbox:");
+                        //Slog.i(TAG, dropBuilder.toString());
+                        synchronized (ActivityManagerService.this) {
+                            long now = SystemClock.uptimeMillis();
+                            if (mLastMemUsageReportTime < now) {
+                                mLastMemUsageReportTime = now;
+                            }
+                        }
+                    }
+                };
+                thread.start();
+                break;
+            }
+            case REPORT_USER_SWITCH_MSG: {
+                dispatchUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case CONTINUE_USER_SWITCH_MSG: {
+                continueUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case USER_SWITCH_TIMEOUT_MSG: {
+                timeoutUserSwitch((UserStartedState)msg.obj, msg.arg1, msg.arg2);
+                break;
+            }
+            case IMMERSIVE_MODE_LOCK_MSG: {
+                final boolean nextState = (msg.arg1 != 0);
+                if (mUpdateLock.isHeld() != nextState) {
+                    if (DEBUG_IMMERSIVE) {
+                        final ActivityRecord r = (ActivityRecord) msg.obj;
+                        Slog.d(TAG, "Applying new update lock state '" + nextState + "' for " + r);
+                    }
+                    if (nextState) {
+                        mUpdateLock.acquire();
+                    } else {
+                        mUpdateLock.release();
+                    }
+                }
+                break;
+            }
+            case PERSIST_URI_GRANTS_MSG: {
+                writeGrantedUriPermissions();
+                break;
+            }
+            case REQUEST_ALL_PSS_MSG: {
+                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                break;
+            }
+            }
+        }
+    };
+
+    static final int COLLECT_PSS_BG_MSG = 1;
+
+    final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case COLLECT_PSS_BG_MSG: {
+                int i=0, num=0;
+                long start = SystemClock.uptimeMillis();
+                long[] tmp = new long[1];
+                do {
+                    ProcessRecord proc;
+                    int procState;
+                    int pid;
+                    synchronized (ActivityManagerService.this) {
+                        if (i >= mPendingPssProcesses.size()) {
+                            if (DEBUG_PSS) Slog.d(TAG, "Collected PSS of " + num + " of " + i
+                                    + " processes in " + (SystemClock.uptimeMillis()-start) + "ms");
+                            mPendingPssProcesses.clear();
+                            return;
+                        }
+                        proc = mPendingPssProcesses.get(i);
+                        procState = proc.pssProcState;
+                        if (proc.thread != null && procState == proc.setProcState) {
+                            pid = proc.pid;
+                        } else {
+                            proc = null;
+                            pid = 0;
+                        }
+                        i++;
+                    }
+                    if (proc != null) {
+                        long pss = Debug.getPss(pid, tmp);
+                        synchronized (ActivityManagerService.this) {
+                            if (proc.thread != null && proc.setProcState == procState
+                                    && proc.pid == pid) {
+                                num++;
+                                proc.lastPssTime = SystemClock.uptimeMillis();
+                                proc.baseProcessTracker.addPss(pss, tmp[0], true, proc.pkgList);
+                                if (DEBUG_PSS) Slog.d(TAG, "PSS of " + proc.toShortString()
+                                        + ": " + pss + " lastPss=" + proc.lastPss
+                                        + " state=" + ProcessList.makeProcStateString(procState));
+                                if (proc.initialIdlePss == 0) {
+                                    proc.initialIdlePss = pss;
+                                }
+                                proc.lastPss = pss;
+                                if (procState >= ActivityManager.PROCESS_STATE_HOME) {
+                                    proc.lastCachedPss = pss;
+                                }
+                            }
+                        }
+                    }
+                } while (true);
+            }
+            }
+        }
+    };
+
+    public static void setSystemProcess() {
+        try {
+            ActivityManagerService m = mSelf;
+
+            ServiceManager.addService(Context.ACTIVITY_SERVICE, m, true);
+            ServiceManager.addService(ProcessStats.SERVICE_NAME, m.mProcessStats);
+            ServiceManager.addService("meminfo", new MemBinder(m));
+            ServiceManager.addService("gfxinfo", new GraphicsBinder(m));
+            ServiceManager.addService("dbinfo", new DbBinder(m));
+            if (MONITOR_CPU_USAGE) {
+                ServiceManager.addService("cpuinfo", new CpuBinder(m));
+            }
+            ServiceManager.addService("permission", new PermissionController(m));
+
+            ApplicationInfo info =
+                mSelf.mContext.getPackageManager().getApplicationInfo(
+                            "android", STOCK_PM_FLAGS);
+            mSystemThread.installSystemApplicationInfo(info);
+
+            synchronized (mSelf) {
+                ProcessRecord app = mSelf.newProcessRecordLocked(info,
+                        info.processName, false);
+                app.persistent = true;
+                app.pid = MY_PID;
+                app.maxAdj = ProcessList.SYSTEM_ADJ;
+                app.makeActive(mSystemThread.getApplicationThread(), mSelf.mProcessStats);
+                mSelf.mProcessNames.put(app.processName, app.uid, app);
+                synchronized (mSelf.mPidsSelfLocked) {
+                    mSelf.mPidsSelfLocked.put(app.pid, app);
+                }
+                mSelf.updateLruProcessLocked(app, false, null);
+                mSelf.updateOomAdjLocked();
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException(
+                    "Unable to find android system package", e);
+        }
+    }
+
+    public void setWindowManager(WindowManagerService wm) {
+        mWindowManager = wm;
+        mStackSupervisor.setWindowManager(wm);
+    }
+
+    public void startObservingNativeCrashes() {
+        final NativeCrashListener ncl = new NativeCrashListener();
+        ncl.start();
+    }
+
+    public static final Context main(int factoryTest) {
+        AThread thr = new AThread();
+        thr.start();
+
+        synchronized (thr) {
+            while (thr.mService == null) {
+                try {
+                    thr.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        ActivityManagerService m = thr.mService;
+        mSelf = m;
+        ActivityThread at = ActivityThread.systemMain();
+        mSystemThread = at;
+        Context context = at.getSystemContext();
+        context.setTheme(android.R.style.Theme_Holo);
+        m.mContext = context;
+        m.mFactoryTest = factoryTest;
+        m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface());
+
+        m.mStackSupervisor = new ActivityStackSupervisor(m);
+
+        m.mBatteryStatsService.publish(context);
+        m.mUsageStatsService.publish(context);
+        m.mAppOpsService.publish(context);
+
+        synchronized (thr) {
+            thr.mReady = true;
+            thr.notifyAll();
+        }
+
+        m.startRunning(null, null, null, null);
+
+        return context;
+    }
+
+    public static ActivityManagerService self() {
+        return mSelf;
+    }
+
+    public IAppOpsService getAppOpsService() {
+        return mAppOpsService;
+    }
+
+    static class AThread extends Thread {
+        ActivityManagerService mService;
+        Looper mLooper;
+        boolean mReady = false;
+
+        public AThread() {
+            super("ActivityManager");
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+
+            android.os.Process.setThreadPriority(
+                    android.os.Process.THREAD_PRIORITY_FOREGROUND);
+            android.os.Process.setCanSelfBackground(false);
+
+            ActivityManagerService m = new ActivityManagerService();
+
+            synchronized (this) {
+                mService = m;
+                mLooper = Looper.myLooper();
+                Watchdog.getInstance().addThread(new Handler(mLooper), getName());
+                notifyAll();
+            }
+
+            synchronized (this) {
+                while (!mReady) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+
+            // For debug builds, log event loop stalls to dropbox for analysis.
+            if (StrictMode.conditionallyEnableDebugLogging()) {
+                Slog.i(TAG, "Enabled StrictMode logging for AThread's Looper");
+            }
+
+            Looper.loop();
+        }
+    }
+
+    static class MemBinder extends Binder {
+        ActivityManagerService mActivityManagerService;
+        MemBinder(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump meminfo from from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                        + " without permission " + android.Manifest.permission.DUMP);
+                return;
+            }
+
+            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
+        }
+    }
+
+    static class GraphicsBinder extends Binder {
+        ActivityManagerService mActivityManagerService;
+        GraphicsBinder(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump gfxinfo from from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                        + " without permission " + android.Manifest.permission.DUMP);
+                return;
+            }
+
+            mActivityManagerService.dumpGraphicsHardwareUsage(fd, pw, args);
+        }
+    }
+
+    static class DbBinder extends Binder {
+        ActivityManagerService mActivityManagerService;
+        DbBinder(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump dbinfo from from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                        + " without permission " + android.Manifest.permission.DUMP);
+                return;
+            }
+
+            mActivityManagerService.dumpDbInfo(fd, pw, args);
+        }
+    }
+
+    static class CpuBinder extends Binder {
+        ActivityManagerService mActivityManagerService;
+        CpuBinder(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (mActivityManagerService.checkCallingPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+                pw.println("Permission Denial: can't dump cpuinfo from from pid="
+                        + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                        + " without permission " + android.Manifest.permission.DUMP);
+                return;
+            }
+
+            synchronized (mActivityManagerService.mProcessCpuThread) {
+                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
+                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
+                        SystemClock.uptimeMillis()));
+            }
+        }
+    }
+
+    private ActivityManagerService() {
+        Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass());
+
+        mFgBroadcastQueue = new BroadcastQueue(this, "foreground", BROADCAST_FG_TIMEOUT, false);
+        mBgBroadcastQueue = new BroadcastQueue(this, "background", BROADCAST_BG_TIMEOUT, true);
+        mBroadcastQueues[0] = mFgBroadcastQueue;
+        mBroadcastQueues[1] = mBgBroadcastQueue;
+
+        mServices = new ActiveServices(this);
+        mProviderMap = new ProviderMap(this);
+
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        systemDir.mkdirs();
+        mBatteryStatsService = new BatteryStatsService(new File(
+                systemDir, "batterystats.bin").toString());
+        mBatteryStatsService.getActiveStatistics().readLocked();
+        mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+        mOnBattery = DEBUG_POWER ? true
+                : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
+        mBatteryStatsService.getActiveStatistics().setCallback(this);
+
+        mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
+
+        mUsageStatsService = new UsageStatsService(new File(systemDir, "usagestats").toString());
+        mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"));
+
+        mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"));
+
+        // User 0 is the first and only user that runs at boot.
+        mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true));
+        mUserLru.add(Integer.valueOf(0));
+        updateStartedUserArrayLocked();
+
+        GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
+            ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+
+        mConfiguration.setToDefaults();
+        mConfiguration.setLocale(Locale.getDefault());
+
+        mConfigurationSeq = mConfiguration.seq = 1;
+        mProcessCpuTracker.init();
+
+        mCompatModePackages = new CompatModePackages(this, systemDir);
+
+        // Add ourself to the Watchdog monitors.
+        Watchdog.getInstance().addMonitor(this);
+
+        mProcessCpuThread = new Thread("CpuTracker") {
+            @Override
+            public void run() {
+                while (true) {
+                    try {
+                        try {
+                            synchronized(this) {
+                                final long now = SystemClock.uptimeMillis();
+                                long nextCpuDelay = (mLastCpuTime.get()+MONITOR_CPU_MAX_TIME)-now;
+                                long nextWriteDelay = (mLastWriteTime+BATTERY_STATS_TIME)-now;
+                                //Slog.i(TAG, "Cpu delay=" + nextCpuDelay
+                                //        + ", write delay=" + nextWriteDelay);
+                                if (nextWriteDelay < nextCpuDelay) {
+                                    nextCpuDelay = nextWriteDelay;
+                                }
+                                if (nextCpuDelay > 0) {
+                                    mProcessCpuMutexFree.set(true);
+                                    this.wait(nextCpuDelay);
+                                }
+                            }
+                        } catch (InterruptedException e) {
+                        }
+                        updateCpuStatsNow();
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Unexpected exception collecting process stats", e);
+                    }
+                }
+            }
+        };
+        mProcessCpuThread.start();
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        if (code == SYSPROPS_TRANSACTION) {
+            // We need to tell all apps about the system property change.
+            ArrayList<IBinder> procs = new ArrayList<IBinder>();
+            synchronized(this) {
+                final int NP = mProcessNames.getMap().size();
+                for (int ip=0; ip<NP; ip++) {
+                    SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+                    final int NA = apps.size();
+                    for (int ia=0; ia<NA; ia++) {
+                        ProcessRecord app = apps.valueAt(ia);
+                        if (app.thread != null) {
+                            procs.add(app.thread.asBinder());
+                        }
+                    }
+                }
+            }
+
+            int N = procs.size();
+            for (int i=0; i<N; i++) {
+                Parcel data2 = Parcel.obtain();
+                try {
+                    procs.get(i).transact(IBinder.SYSPROPS_TRANSACTION, data2, null, 0);
+                } catch (RemoteException e) {
+                }
+                data2.recycle();
+            }
+        }
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The activity manager only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Activity Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    void updateCpuStats() {
+        final long now = SystemClock.uptimeMillis();
+        if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) {
+            return;
+        }
+        if (mProcessCpuMutexFree.compareAndSet(true, false)) {
+            synchronized (mProcessCpuThread) {
+                mProcessCpuThread.notify();
+            }
+        }
+    }
+
+    void updateCpuStatsNow() {
+        synchronized (mProcessCpuThread) {
+            mProcessCpuMutexFree.set(false);
+            final long now = SystemClock.uptimeMillis();
+            boolean haveNewCpuStats = false;
+
+            if (MONITOR_CPU_USAGE &&
+                    mLastCpuTime.get() < (now-MONITOR_CPU_MIN_TIME)) {
+                mLastCpuTime.set(now);
+                haveNewCpuStats = true;
+                mProcessCpuTracker.update();
+                //Slog.i(TAG, mProcessCpu.printCurrentState());
+                //Slog.i(TAG, "Total CPU usage: "
+                //        + mProcessCpu.getTotalCpuPercent() + "%");
+
+                // Slog the cpu usage if the property is set.
+                if ("true".equals(SystemProperties.get("events.cpu"))) {
+                    int user = mProcessCpuTracker.getLastUserTime();
+                    int system = mProcessCpuTracker.getLastSystemTime();
+                    int iowait = mProcessCpuTracker.getLastIoWaitTime();
+                    int irq = mProcessCpuTracker.getLastIrqTime();
+                    int softIrq = mProcessCpuTracker.getLastSoftIrqTime();
+                    int idle = mProcessCpuTracker.getLastIdleTime();
+
+                    int total = user + system + iowait + irq + softIrq + idle;
+                    if (total == 0) total = 1;
+
+                    EventLog.writeEvent(EventLogTags.CPU,
+                            ((user+system+iowait+irq+softIrq) * 100) / total,
+                            (user * 100) / total,
+                            (system * 100) / total,
+                            (iowait * 100) / total,
+                            (irq * 100) / total,
+                            (softIrq * 100) / total);
+                }
+            }
+
+            long[] cpuSpeedTimes = mProcessCpuTracker.getLastCpuSpeedTimes();
+            final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics();
+            synchronized(bstats) {
+                synchronized(mPidsSelfLocked) {
+                    if (haveNewCpuStats) {
+                        if (mOnBattery) {
+                            int perc = bstats.startAddingCpuLocked();
+                            int totalUTime = 0;
+                            int totalSTime = 0;
+                            final int N = mProcessCpuTracker.countStats();
+                            for (int i=0; i<N; i++) {
+                                ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                                if (!st.working) {
+                                    continue;
+                                }
+                                ProcessRecord pr = mPidsSelfLocked.get(st.pid);
+                                int otherUTime = (st.rel_utime*perc)/100;
+                                int otherSTime = (st.rel_stime*perc)/100;
+                                totalUTime += otherUTime;
+                                totalSTime += otherSTime;
+                                if (pr != null) {
+                                    BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(
+                                            st.name, st.pid);
+                                    ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+                                            st.rel_stime-otherSTime);
+                                    ps.addSpeedStepTimes(cpuSpeedTimes);
+                                    pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10;
+                                } else if (st.uid >= Process.FIRST_APPLICATION_UID) {
+                                    BatteryStatsImpl.Uid.Proc ps = st.batteryStats;
+                                    if (ps == null) {
+                                        st.batteryStats = ps = bstats.getProcessStatsLocked(st.uid,
+                                                "(Unknown)");
+                                    }
+                                    ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+                                            st.rel_stime-otherSTime);
+                                    ps.addSpeedStepTimes(cpuSpeedTimes);
+                                } else {
+                                    BatteryStatsImpl.Uid.Proc ps =
+                                            bstats.getProcessStatsLocked(st.name, st.pid);
+                                    if (ps != null) {
+                                        ps.addCpuTimeLocked(st.rel_utime-otherUTime,
+                                                st.rel_stime-otherSTime);
+                                        ps.addSpeedStepTimes(cpuSpeedTimes);
+                                    }
+                                }
+                            }
+                            bstats.finishAddingCpuLocked(perc, totalUTime,
+                                    totalSTime, cpuSpeedTimes);
+                        }
+                    }
+                }
+
+                if (mLastWriteTime < (now-BATTERY_STATS_TIME)) {
+                    mLastWriteTime = now;
+                    mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
+                }
+            }
+        }
+    }
+
+    @Override
+    public void batteryNeedsCpuUpdate() {
+        updateCpuStatsNow();
+    }
+
+    @Override
+    public void batteryPowerChanged(boolean onBattery) {
+        // When plugging in, update the CPU stats first before changing
+        // the plug state.
+        updateCpuStatsNow();
+        synchronized (this) {
+            synchronized(mPidsSelfLocked) {
+                mOnBattery = DEBUG_POWER ? true : onBattery;
+            }
+        }
+    }
+
+    /**
+     * Initialize the application bind args. These are passed to each
+     * process when the bindApplication() IPC is sent to the process. They're
+     * lazily setup to make sure the services are running when they're asked for.
+     */
+    private HashMap<String, IBinder> getCommonServicesLocked() {
+        if (mAppBindArgs == null) {
+            mAppBindArgs = new HashMap<String, IBinder>();
+
+            // Setup the application init args
+            mAppBindArgs.put("package", ServiceManager.getService("package"));
+            mAppBindArgs.put("window", ServiceManager.getService("window"));
+            mAppBindArgs.put(Context.ALARM_SERVICE,
+                    ServiceManager.getService(Context.ALARM_SERVICE));
+        }
+        return mAppBindArgs;
+    }
+
+    final void setFocusedActivityLocked(ActivityRecord r) {
+        if (mFocusedActivity != r) {
+            if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r);
+            mFocusedActivity = r;
+            mStackSupervisor.setFocusedStack(r);
+            if (r != null) {
+                mWindowManager.setFocusedApp(r.appToken, true);
+            }
+            applyUpdateLockStateLocked(r);
+        }
+    }
+
+    @Override
+    public void setFocusedStack(int stackId) {
+        if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId);
+        synchronized (ActivityManagerService.this) {
+            ActivityStack stack = mStackSupervisor.getStack(stackId);
+            if (stack != null) {
+                ActivityRecord r = stack.topRunningActivityLocked(null);
+                if (r != null) {
+                    setFocusedActivityLocked(r);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void notifyActivityDrawn(IBinder token) {
+        if (DEBUG_VISBILITY) Slog.d(TAG, "notifyActivityDrawn: token=" + token);
+        synchronized (this) {
+            ActivityRecord r= mStackSupervisor.isInAnyStackLocked(token);
+            if (r != null) {
+                r.task.stack.notifyActivityDrawnLocked(r);
+            }
+        }
+    }
+
+    final void applyUpdateLockStateLocked(ActivityRecord r) {
+        // Modifications to the UpdateLock state are done on our handler, outside
+        // the activity manager's locks.  The new state is determined based on the
+        // state *now* of the relevant activity record.  The object is passed to
+        // the handler solely for logging detail, not to be consulted/modified.
+        final boolean nextState = r != null && r.immersive;
+        mHandler.sendMessage(
+                mHandler.obtainMessage(IMMERSIVE_MODE_LOCK_MSG, (nextState) ? 1 : 0, 0, r));
+    }
+
+    final void showAskCompatModeDialogLocked(ActivityRecord r) {
+        Message msg = Message.obtain();
+        msg.what = SHOW_COMPAT_MODE_DIALOG_MSG;
+        msg.obj = r.task.askedCompatMode ? null : r;
+        mHandler.sendMessage(msg);
+    }
+
+    private final int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
+            String what, Object obj, ProcessRecord srcApp) {
+        app.lastActivityTime = now;
+
+        if (app.activities.size() > 0) {
+            // Don't want to touch dependent processes that are hosting activities.
+            return index;
+        }
+
+        int lrui = mLruProcesses.lastIndexOf(app);
+        if (lrui < 0) {
+            Slog.wtf(TAG, "Adding dependent process " + app + " not on LRU list: "
+                    + what + " " + obj + " from " + srcApp);
+            return index;
+        }
+
+        if (lrui >= index) {
+            // Don't want to cause this to move dependent processes *back* in the
+            // list as if they were less frequently used.
+            return index;
+        }
+
+        if (lrui >= mLruProcessActivityStart) {
+            // Don't want to touch dependent processes that are hosting activities.
+            return index;
+        }
+
+        mLruProcesses.remove(lrui);
+        if (index > 0) {
+            index--;
+        }
+        if (DEBUG_LRU) Slog.d(TAG, "Moving dep from " + lrui + " to " + index
+                + " in LRU list: " + app);
+        mLruProcesses.add(index, app);
+        return index;
+    }
+
+    final void removeLruProcessLocked(ProcessRecord app) {
+        int lrui = mLruProcesses.lastIndexOf(app);
+        if (lrui >= 0) {
+            if (lrui <= mLruProcessActivityStart) {
+                mLruProcessActivityStart--;
+            }
+            if (lrui <= mLruProcessServiceStart) {
+                mLruProcessServiceStart--;
+            }
+            mLruProcesses.remove(lrui);
+        }
+    }
+
+    final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
+            ProcessRecord client) {
+        final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities;
+        final boolean hasService = false; // not impl yet. app.services.size() > 0;
+        if (!activityChange && hasActivity) {
+            // The process has activties, so we are only going to allow activity-based
+            // adjustments move it.  It should be kept in the front of the list with other
+            // processes that have activities, and we don't want those to change their
+            // order except due to activity operations.
+            return;
+        }
+
+        mLruSeq++;
+        final long now = SystemClock.uptimeMillis();
+        app.lastActivityTime = now;
+
+        // First a quick reject: if the app is already at the position we will
+        // put it, then there is nothing to do.
+        if (hasActivity) {
+            final int N = mLruProcesses.size();
+            if (N > 0 && mLruProcesses.get(N-1) == app) {
+                if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top activity: " + app);
+                return;
+            }
+        } else {
+            if (mLruProcessServiceStart > 0
+                    && mLruProcesses.get(mLruProcessServiceStart-1) == app) {
+                if (DEBUG_LRU) Slog.d(TAG, "Not moving, already top other: " + app);
+                return;
+            }
+        }
+
+        int lrui = mLruProcesses.lastIndexOf(app);
+
+        if (app.persistent && lrui >= 0) {
+            // We don't care about the position of persistent processes, as long as
+            // they are in the list.
+            if (DEBUG_LRU) Slog.d(TAG, "Not moving, persistent: " + app);
+            return;
+        }
+
+        /* In progress: compute new position first, so we can avoid doing work
+           if the process is not actually going to move.  Not yet working.
+        int addIndex;
+        int nextIndex;
+        boolean inActivity = false, inService = false;
+        if (hasActivity) {
+            // Process has activities, put it at the very tipsy-top.
+            addIndex = mLruProcesses.size();
+            nextIndex = mLruProcessServiceStart;
+            inActivity = true;
+        } else if (hasService) {
+            // Process has services, put it at the top of the service list.
+            addIndex = mLruProcessActivityStart;
+            nextIndex = mLruProcessServiceStart;
+            inActivity = true;
+            inService = true;
+        } else  {
+            // Process not otherwise of interest, it goes to the top of the non-service area.
+            addIndex = mLruProcessServiceStart;
+            if (client != null) {
+                int clientIndex = mLruProcesses.lastIndexOf(client);
+                if (clientIndex < 0) Slog.d(TAG, "Unknown client " + client + " when updating "
+                        + app);
+                if (clientIndex >= 0 && addIndex > clientIndex) {
+                    addIndex = clientIndex;
+                }
+            }
+            nextIndex = addIndex > 0 ? addIndex-1 : addIndex;
+        }
+
+        Slog.d(TAG, "Update LRU at " + lrui + " to " + addIndex + " (act="
+                + mLruProcessActivityStart + "): " + app);
+        */
+
+        if (lrui >= 0) {
+            if (lrui < mLruProcessActivityStart) {
+                mLruProcessActivityStart--;
+            }
+            if (lrui < mLruProcessServiceStart) {
+                mLruProcessServiceStart--;
+            }
+            /*
+            if (addIndex > lrui) {
+                addIndex--;
+            }
+            if (nextIndex > lrui) {
+                nextIndex--;
+            }
+            */
+            mLruProcesses.remove(lrui);
+        }
+
+        /*
+        mLruProcesses.add(addIndex, app);
+        if (inActivity) {
+            mLruProcessActivityStart++;
+        }
+        if (inService) {
+            mLruProcessActivityStart++;
+        }
+        */
+
+        int nextIndex;
+        if (hasActivity) {
+            final int N = mLruProcesses.size();
+            if (app.activities.size() == 0 && mLruProcessActivityStart < (N-1)) {
+                // Process doesn't have activities, but has clients with
+                // activities...  move it up, but one below the top (the top
+                // should always have a real activity).
+                if (DEBUG_LRU) Slog.d(TAG, "Adding to second-top of LRU activity list: " + app);
+                mLruProcesses.add(N-1, app);
+                // To keep it from spamming the LRU list (by making a bunch of clients),
+                // we will push down any other entries owned by the app.
+                final int uid = app.info.uid;
+                for (int i=N-2; i>mLruProcessActivityStart; i--) {
+                    ProcessRecord subProc = mLruProcesses.get(i);
+                    if (subProc.info.uid == uid) {
+                        // We want to push this one down the list.  If the process after
+                        // it is for the same uid, however, don't do so, because we don't
+                        // want them internally to be re-ordered.
+                        if (mLruProcesses.get(i-1).info.uid != uid) {
+                            if (DEBUG_LRU) Slog.d(TAG, "Pushing uid " + uid + " swapping at " + i
+                                    + ": " + mLruProcesses.get(i) + " : " + mLruProcesses.get(i-1));
+                            ProcessRecord tmp = mLruProcesses.get(i);
+                            mLruProcesses.set(i, mLruProcesses.get(i-1));
+                            mLruProcesses.set(i-1, tmp);
+                            i--;
+                        }
+                    } else {
+                        // A gap, we can stop here.
+                        break;
+                    }
+                }
+            } else {
+                // Process has activities, put it at the very tipsy-top.
+                if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU activity list: " + app);
+                mLruProcesses.add(app);
+            }
+            nextIndex = mLruProcessServiceStart;
+        } else if (hasService) {
+            // Process has services, put it at the top of the service list.
+            if (DEBUG_LRU) Slog.d(TAG, "Adding to top of LRU service list: " + app);
+            mLruProcesses.add(mLruProcessActivityStart, app);
+            nextIndex = mLruProcessServiceStart;
+            mLruProcessActivityStart++;
+        } else  {
+            // Process not otherwise of interest, it goes to the top of the non-service area.
+            int index = mLruProcessServiceStart;
+            if (client != null) {
+                // If there is a client, don't allow the process to be moved up higher
+                // in the list than that client.
+                int clientIndex = mLruProcesses.lastIndexOf(client);
+                if (DEBUG_LRU && clientIndex < 0) Slog.d(TAG, "Unknown client " + client
+                        + " when updating " + app);
+                if (clientIndex <= lrui) {
+                    // Don't allow the client index restriction to push it down farther in the
+                    // list than it already is.
+                    clientIndex = lrui;
+                }
+                if (clientIndex >= 0 && index > clientIndex) {
+                    index = clientIndex;
+                }
+            }
+            if (DEBUG_LRU) Slog.d(TAG, "Adding at " + index + " of LRU list: " + app);
+            mLruProcesses.add(index, app);
+            nextIndex = index-1;
+            mLruProcessActivityStart++;
+            mLruProcessServiceStart++;
+        }
+
+        // If the app is currently using a content provider or service,
+        // bump those processes as well.
+        for (int j=app.connections.size()-1; j>=0; j--) {
+            ConnectionRecord cr = app.connections.valueAt(j);
+            if (cr.binding != null && !cr.serviceDead && cr.binding.service != null
+                    && cr.binding.service.app != null
+                    && cr.binding.service.app.lruSeq != mLruSeq
+                    && !cr.binding.service.app.persistent) {
+                nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,
+                        "service connection", cr, app);
+            }
+        }
+        for (int j=app.conProviders.size()-1; j>=0; j--) {
+            ContentProviderRecord cpr = app.conProviders.get(j).provider;
+            if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {
+                nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,
+                        "provider reference", cpr, app);
+            }
+        }
+    }
+
+    final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) {
+        if (uid == Process.SYSTEM_UID) {
+            // The system gets to run in any process.  If there are multiple
+            // processes with the same uid, just pick the first (this
+            // should never happen).
+            SparseArray<ProcessRecord> procs = mProcessNames.getMap().get(processName);
+            if (procs == null) return null;
+            final int N = procs.size();
+            for (int i = 0; i < N; i++) {
+                if (UserHandle.isSameUser(procs.keyAt(i), uid)) return procs.valueAt(i);
+            }
+        }
+        ProcessRecord proc = mProcessNames.get(processName, uid);
+        if (false && proc != null && !keepIfLarge
+                && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY
+                && proc.lastCachedPss >= 4000) {
+            // Turn this condition on to cause killing to happen regularly, for testing.
+            if (proc.baseProcessTracker != null) {
+                proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
+            }
+            killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss)
+                    + "k from cached");
+        } else if (proc != null && !keepIfLarge
+                && mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
+                && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+            if (DEBUG_PSS) Slog.d(TAG, "May not keep " + proc + ": pss=" + proc.lastCachedPss);
+            if (proc.lastCachedPss >= mProcessList.getCachedRestoreThresholdKb()) {
+                if (proc.baseProcessTracker != null) {
+                    proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
+                }
+                killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss)
+                        + "k from cached");
+            }
+        }
+        return proc;
+    }
+
+    void ensurePackageDexOpt(String packageName) {
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            if (pm.performDexOpt(packageName)) {
+                mDidDexOpt = true;
+            }
+        } catch (RemoteException e) {
+        }
+    }
+
+    boolean isNextTransitionForward() {
+        int transit = mWindowManager.getPendingAppTransition();
+        return transit == AppTransition.TRANSIT_ACTIVITY_OPEN
+                || transit == AppTransition.TRANSIT_TASK_OPEN
+                || transit == AppTransition.TRANSIT_TASK_TO_FRONT;
+    }
+
+    final ProcessRecord startProcessLocked(String processName,
+            ApplicationInfo info, boolean knownToBeDead, int intentFlags,
+            String hostingType, ComponentName hostingName, boolean allowWhileBooting,
+            boolean isolated, boolean keepIfLarge) {
+        ProcessRecord app;
+        if (!isolated) {
+            app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
+        } else {
+            // If this is an isolated process, it can't re-use an existing process.
+            app = null;
+        }
+        // We don't have to do anything more if:
+        // (1) There is an existing application record; and
+        // (2) The caller doesn't think it is dead, OR there is no thread
+        //     object attached to it so we know it couldn't have crashed; and
+        // (3) There is a pid assigned to it, so it is either starting or
+        //     already running.
+        if (DEBUG_PROCESSES) Slog.v(TAG, "startProcess: name=" + processName
+                + " app=" + app + " knownToBeDead=" + knownToBeDead
+                + " thread=" + (app != null ? app.thread : null)
+                + " pid=" + (app != null ? app.pid : -1));
+        if (app != null && app.pid > 0) {
+            if (!knownToBeDead || app.thread == null) {
+                // We already have the app running, or are waiting for it to
+                // come up (we have a pid but not yet its thread), so keep it.
+                if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app);
+                // If this is a new package in the process, add the package to the list
+                app.addPackage(info.packageName, mProcessStats);
+                return app;
+            }
+
+            // An application record is attached to a previous process,
+            // clean it up now.
+            if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app);
+            handleAppDiedLocked(app, true, true);
+        }
+
+        String hostingNameStr = hostingName != null
+                ? hostingName.flattenToShortString() : null;
+
+        if (!isolated) {
+            if ((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) {
+                // If we are in the background, then check to see if this process
+                // is bad.  If so, we will just silently fail.
+                if (mBadProcesses.get(info.processName, info.uid) != null) {
+                    if (DEBUG_PROCESSES) Slog.v(TAG, "Bad process: " + info.uid
+                            + "/" + info.processName);
+                    return null;
+                }
+            } else {
+                // When the user is explicitly starting a process, then clear its
+                // crash count so that we won't make it bad until they see at
+                // least one crash dialog again, and make the process good again
+                // if it had been bad.
+                if (DEBUG_PROCESSES) Slog.v(TAG, "Clearing bad process: " + info.uid
+                        + "/" + info.processName);
+                mProcessCrashTimes.remove(info.processName, info.uid);
+                if (mBadProcesses.get(info.processName, info.uid) != null) {
+                    EventLog.writeEvent(EventLogTags.AM_PROC_GOOD,
+                            UserHandle.getUserId(info.uid), info.uid,
+                            info.processName);
+                    mBadProcesses.remove(info.processName, info.uid);
+                    if (app != null) {
+                        app.bad = false;
+                    }
+                }
+            }
+        }
+
+        if (app == null) {
+            app = newProcessRecordLocked(info, processName, isolated);
+            if (app == null) {
+                Slog.w(TAG, "Failed making new process record for "
+                        + processName + "/" + info.uid + " isolated=" + isolated);
+                return null;
+            }
+            mProcessNames.put(processName, app.uid, app);
+            if (isolated) {
+                mIsolatedProcesses.put(app.uid, app);
+            }
+        } else {
+            // If this is a new package in the process, add the package to the list
+            app.addPackage(info.packageName, mProcessStats);
+        }
+
+        // If the system is not ready yet, then hold off on starting this
+        // process until it is.
+        if (!mProcessesReady
+                && !isAllowedWhileBooting(info)
+                && !allowWhileBooting) {
+            if (!mProcessesOnHold.contains(app)) {
+                mProcessesOnHold.add(app);
+            }
+            if (DEBUG_PROCESSES) Slog.v(TAG, "System not ready, putting on hold: " + app);
+            return app;
+        }
+
+        startProcessLocked(app, hostingType, hostingNameStr);
+        return (app.pid != 0) ? app : null;
+    }
+
+    boolean isAllowedWhileBooting(ApplicationInfo ai) {
+        return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0;
+    }
+
+    private final void startProcessLocked(ProcessRecord app,
+            String hostingType, String hostingNameStr) {
+        if (app.pid > 0 && app.pid != MY_PID) {
+            synchronized (mPidsSelfLocked) {
+                mPidsSelfLocked.remove(app.pid);
+                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            }
+            app.setPid(0);
+        }
+
+        if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG,
+                "startProcessLocked removing on hold: " + app);
+        mProcessesOnHold.remove(app);
+
+        updateCpuStats();
+
+        try {
+            int uid = app.uid;
+
+            int[] gids = null;
+            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+            if (!app.isolated) {
+                int[] permGids = null;
+                try {
+                    final PackageManager pm = mContext.getPackageManager();
+                    permGids = pm.getPackageGids(app.info.packageName);
+
+                    if (Environment.isExternalStorageEmulated()) {
+                        if (pm.checkPermission(
+                                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
+                                app.info.packageName) == PERMISSION_GRANTED) {
+                            mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
+                        } else {
+                            mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
+                        }
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                    Slog.w(TAG, "Unable to retrieve gids", e);
+                }
+
+                /*
+                 * Add shared application GID so applications can share some
+                 * resources like shared libraries
+                 */
+                if (permGids == null) {
+                    gids = new int[1];
+                } else {
+                    gids = new int[permGids.length + 1];
+                    System.arraycopy(permGids, 0, gids, 1, permGids.length);
+                }
+                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
+            }
+            if (mFactoryTest != SystemServer.FACTORY_TEST_OFF) {
+                if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
+                        && mTopComponent != null
+                        && app.processName.equals(mTopComponent.getPackageName())) {
+                    uid = 0;
+                }
+                if (mFactoryTest == SystemServer.FACTORY_TEST_HIGH_LEVEL
+                        && (app.info.flags&ApplicationInfo.FLAG_FACTORY_TEST) != 0) {
+                    uid = 0;
+                }
+            }
+            int debugFlags = 0;
+            if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+                debugFlags |= Zygote.DEBUG_ENABLE_DEBUGGER;
+                // Also turn on CheckJNI for debuggable apps. It's quite
+                // awkward to turn on otherwise.
+                debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
+            }
+            // Run the app in safe mode if its manifest requests so or the
+            // system is booted in safe mode.
+            if ((app.info.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0 ||
+                Zygote.systemInSafeMode == true) {
+                debugFlags |= Zygote.DEBUG_ENABLE_SAFEMODE;
+            }
+            if ("1".equals(SystemProperties.get("debug.checkjni"))) {
+                debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;
+            }
+            if ("1".equals(SystemProperties.get("debug.jni.logging"))) {
+                debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING;
+            }
+            if ("1".equals(SystemProperties.get("debug.assert"))) {
+                debugFlags |= Zygote.DEBUG_ENABLE_ASSERT;
+            }
+
+            // Start the process.  It will either succeed and return a result containing
+            // the PID of the new process, or else throw a RuntimeException.
+            Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
+                    app.processName, uid, uid, gids, debugFlags, mountExternal,
+                    app.info.targetSdkVersion, app.info.seinfo, null);
+
+            BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics();
+            synchronized (bs) {
+                if (bs.isOnBattery()) {
+                    bs.getProcessStatsLocked(app.uid, app.processName).incStartsLocked();
+                }
+            }
+
+            EventLog.writeEvent(EventLogTags.AM_PROC_START,
+                    UserHandle.getUserId(uid), startResult.pid, uid,
+                    app.processName, hostingType,
+                    hostingNameStr != null ? hostingNameStr : "");
+
+            if (app.persistent) {
+                Watchdog.getInstance().processStarted(app.processName, startResult.pid);
+            }
+
+            StringBuilder buf = mStringBuilder;
+            buf.setLength(0);
+            buf.append("Start proc ");
+            buf.append(app.processName);
+            buf.append(" for ");
+            buf.append(hostingType);
+            if (hostingNameStr != null) {
+                buf.append(" ");
+                buf.append(hostingNameStr);
+            }
+            buf.append(": pid=");
+            buf.append(startResult.pid);
+            buf.append(" uid=");
+            buf.append(uid);
+            buf.append(" gids={");
+            if (gids != null) {
+                for (int gi=0; gi<gids.length; gi++) {
+                    if (gi != 0) buf.append(", ");
+                    buf.append(gids[gi]);
+
+                }
+            }
+            buf.append("}");
+            Slog.i(TAG, buf.toString());
+            app.setPid(startResult.pid);
+            app.usingWrapper = startResult.usingWrapper;
+            app.removed = false;
+            synchronized (mPidsSelfLocked) {
+                this.mPidsSelfLocked.put(startResult.pid, app);
+                Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);
+                msg.obj = app;
+                mHandler.sendMessageDelayed(msg, startResult.usingWrapper
+                        ? PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);
+            }
+        } catch (RuntimeException e) {
+            // XXX do better error recovery.
+            app.setPid(0);
+            Slog.e(TAG, "Failure starting process " + app.processName, e);
+        }
+    }
+
+    void updateUsageStats(ActivityRecord component, boolean resumed) {
+        if (DEBUG_SWITCH) Slog.d(TAG, "updateUsageStats: comp=" + component + "res=" + resumed);
+        final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        if (resumed) {
+            mUsageStatsService.noteResumeComponent(component.realActivity);
+            synchronized (stats) {
+                stats.noteActivityResumedLocked(component.app.uid);
+            }
+        } else {
+            mUsageStatsService.notePauseComponent(component.realActivity);
+            synchronized (stats) {
+                stats.noteActivityPausedLocked(component.app.uid);
+            }
+        }
+    }
+
+    Intent getHomeIntent() {
+        Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
+        intent.setComponent(mTopComponent);
+        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+            intent.addCategory(Intent.CATEGORY_HOME);
+        }
+        return intent;
+    }
+
+    boolean startHomeActivityLocked(int userId) {
+        if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
+                && mTopAction == null) {
+            // We are running in factory test mode, but unable to find
+            // the factory test app, so just sit around displaying the
+            // error message and don't try to start anything.
+            return false;
+        }
+        Intent intent = getHomeIntent();
+        ActivityInfo aInfo =
+            resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
+        if (aInfo != null) {
+            intent.setComponent(new ComponentName(
+                    aInfo.applicationInfo.packageName, aInfo.name));
+            // Don't do this if the home app is currently being
+            // instrumented.
+            aInfo = new ActivityInfo(aInfo);
+            aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
+            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
+                    aInfo.applicationInfo.uid, true);
+            if (app == null || app.instrumentationClass == null) {
+                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
+                mStackSupervisor.startHomeActivity(intent, aInfo);
+            }
+        }
+
+        return true;
+    }
+
+    private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) {
+        ActivityInfo ai = null;
+        ComponentName comp = intent.getComponent();
+        try {
+            if (comp != null) {
+                ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
+            } else {
+                ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
+                        intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            flags, userId);
+
+                if (info != null) {
+                    ai = info.activityInfo;
+                }
+            }
+        } catch (RemoteException e) {
+            // ignore
+        }
+
+        return ai;
+    }
+
+    /**
+     * Starts the "new version setup screen" if appropriate.
+     */
+    void startSetupActivityLocked() {
+        // Only do this once per boot.
+        if (mCheckedForSetup) {
+            return;
+        }
+
+        // We will show this screen if the current one is a different
+        // version than the last one shown, and we are not running in
+        // low-level factory test mode.
+        final ContentResolver resolver = mContext.getContentResolver();
+        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL &&
+                Settings.Global.getInt(resolver,
+                        Settings.Global.DEVICE_PROVISIONED, 0) != 0) {
+            mCheckedForSetup = true;
+
+            // See if we should be showing the platform update setup UI.
+            Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP);
+            List<ResolveInfo> ris = mSelf.mContext.getPackageManager()
+                    .queryIntentActivities(intent, PackageManager.GET_META_DATA);
+
+            // We don't allow third party apps to replace this.
+            ResolveInfo ri = null;
+            for (int i=0; ris != null && i<ris.size(); i++) {
+                if ((ris.get(i).activityInfo.applicationInfo.flags
+                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                    ri = ris.get(i);
+                    break;
+                }
+            }
+
+            if (ri != null) {
+                String vers = ri.activityInfo.metaData != null
+                        ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION)
+                        : null;
+                if (vers == null && ri.activityInfo.applicationInfo.metaData != null) {
+                    vers = ri.activityInfo.applicationInfo.metaData.getString(
+                            Intent.METADATA_SETUP_VERSION);
+                }
+                String lastVers = Settings.Secure.getString(
+                        resolver, Settings.Secure.LAST_SETUP_SHOWN);
+                if (vers != null && !vers.equals(lastVers)) {
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    intent.setComponent(new ComponentName(
+                            ri.activityInfo.packageName, ri.activityInfo.name));
+                    mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo,
+                            null, null, 0, 0, 0, null, 0, null, false, null);
+                }
+            }
+        }
+    }
+
+    CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
+        return mCompatModePackages.compatibilityInfoForPackageLocked(ai);
+    }
+
+    void enforceNotIsolatedCaller(String caller) {
+        if (UserHandle.isIsolated(Binder.getCallingUid())) {
+            throw new SecurityException("Isolated process not allowed to call " + caller);
+        }
+    }
+
+    @Override
+    public int getFrontActivityScreenCompatMode() {
+        enforceNotIsolatedCaller("getFrontActivityScreenCompatMode");
+        synchronized (this) {
+            return mCompatModePackages.getFrontActivityScreenCompatModeLocked();
+        }
+    }
+
+    @Override
+    public void setFrontActivityScreenCompatMode(int mode) {
+        enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,
+                "setFrontActivityScreenCompatMode");
+        synchronized (this) {
+            mCompatModePackages.setFrontActivityScreenCompatModeLocked(mode);
+        }
+    }
+
+    @Override
+    public int getPackageScreenCompatMode(String packageName) {
+        enforceNotIsolatedCaller("getPackageScreenCompatMode");
+        synchronized (this) {
+            return mCompatModePackages.getPackageScreenCompatModeLocked(packageName);
+        }
+    }
+
+    @Override
+    public void setPackageScreenCompatMode(String packageName, int mode) {
+        enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,
+                "setPackageScreenCompatMode");
+        synchronized (this) {
+            mCompatModePackages.setPackageScreenCompatModeLocked(packageName, mode);
+        }
+    }
+
+    @Override
+    public boolean getPackageAskScreenCompat(String packageName) {
+        enforceNotIsolatedCaller("getPackageAskScreenCompat");
+        synchronized (this) {
+            return mCompatModePackages.getPackageAskCompatModeLocked(packageName);
+        }
+    }
+
+    @Override
+    public void setPackageAskScreenCompat(String packageName, boolean ask) {
+        enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY,
+                "setPackageAskScreenCompat");
+        synchronized (this) {
+            mCompatModePackages.setPackageAskCompatModeLocked(packageName, ask);
+        }
+    }
+
+    private void dispatchProcessesChanged() {
+        int N;
+        synchronized (this) {
+            N = mPendingProcessChanges.size();
+            if (mActiveProcessChanges.length < N) {
+                mActiveProcessChanges = new ProcessChangeItem[N];
+            }
+            mPendingProcessChanges.toArray(mActiveProcessChanges);
+            mAvailProcessChanges.addAll(mPendingProcessChanges);
+            mPendingProcessChanges.clear();
+            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "*** Delivering " + N + " process changes");
+        }
+
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    for (int j=0; j<N; j++) {
+                        ProcessChangeItem item = mActiveProcessChanges[j];
+                        if ((item.changes&ProcessChangeItem.CHANGE_ACTIVITIES) != 0) {
+                            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "ACTIVITIES CHANGED pid="
+                                    + item.pid + " uid=" + item.uid + ": "
+                                    + item.foregroundActivities);
+                            observer.onForegroundActivitiesChanged(item.pid, item.uid,
+                                    item.foregroundActivities);
+                        }
+                        if ((item.changes&ProcessChangeItem.CHANGE_IMPORTANCE) != 0) {
+                            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "IMPORTANCE CHANGED pid="
+                                    + item.pid + " uid=" + item.uid + ": " + item.importance);
+                            observer.onImportanceChanged(item.pid, item.uid,
+                                    item.importance);
+                        }
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+    }
+
+    private void dispatchProcessDied(int pid, int uid) {
+        int i = mProcessObservers.beginBroadcast();
+        while (i > 0) {
+            i--;
+            final IProcessObserver observer = mProcessObservers.getBroadcastItem(i);
+            if (observer != null) {
+                try {
+                    observer.onProcessDied(pid, uid);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        mProcessObservers.finishBroadcast();
+    }
+
+    final void doPendingActivityLaunchesLocked(boolean doResume) {
+        final int N = mPendingActivityLaunches.size();
+        if (N <= 0) {
+            return;
+        }
+        for (int i=0; i<N; i++) {
+            PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
+            mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags,
+                    doResume && i == (N-1), null);
+        }
+        mPendingActivityLaunches.clear();
+    }
+
+    @Override
+    public final int startActivity(IApplicationThread caller, String callingPackage,
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags,
+            String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
+        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
+                resultWho, requestCode,
+                startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags,
+            String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
+        enforceNotIsolatedCaller("startActivity");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivity", null);
+        // TODO: Switch to user app stacks here.
+        return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
+                resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+                null, null, options, userId);
+    }
+
+    @Override
+    public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage,
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options, int userId) {
+        enforceNotIsolatedCaller("startActivityAndWait");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivityAndWait", null);
+        WaitResult res = new WaitResult();
+        // TODO: Switch to user app stacks here.
+        mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
+                resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+                res, null, options, UserHandle.getCallingUserId());
+        return res;
+    }
+
+    @Override
+    public final int startActivityWithConfig(IApplicationThread caller, String callingPackage,
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, Configuration config,
+            Bundle options, int userId) {
+        enforceNotIsolatedCaller("startActivityWithConfig");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivityWithConfig", null);
+        // TODO: Switch to user app stacks here.
+        int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
+                resolvedType, resultTo, resultWho, requestCode, startFlags,
+                null, null, null, config, options, userId);
+        return ret;
+    }
+
+    @Override
+    public int startActivityIntentSender(IApplicationThread caller,
+            IntentSender intent, Intent fillInIntent, String resolvedType,
+            IBinder resultTo, String resultWho, int requestCode,
+            int flagsMask, int flagsValues, Bundle options) {
+        enforceNotIsolatedCaller("startActivityIntentSender");
+        // Refuse possible leaked file descriptors
+        if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        IIntentSender sender = intent.getTarget();
+        if (!(sender instanceof PendingIntentRecord)) {
+            throw new IllegalArgumentException("Bad PendingIntent object");
+        }
+
+        PendingIntentRecord pir = (PendingIntentRecord)sender;
+
+        synchronized (this) {
+            // If this is coming from the currently resumed activity, it is
+            // effectively saying that app switches are allowed at this point.
+            final ActivityStack stack = getFocusedStack();
+            if (stack.mResumedActivity != null &&
+                    stack.mResumedActivity.info.applicationInfo.uid == Binder.getCallingUid()) {
+                mAppSwitchesAllowedTime = 0;
+            }
+        }
+        int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null,
+                resultTo, resultWho, requestCode, flagsMask, flagsValues, options);
+        return ret;
+    }
+
+    @Override
+    public boolean startNextMatchingActivity(IBinder callingActivity,
+            Intent intent, Bundle options) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized (this) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(callingActivity);
+            if (r == null) {
+                ActivityOptions.abort(options);
+                return false;
+            }
+            if (r.app == null || r.app.thread == null) {
+                // The caller is not running...  d'oh!
+                ActivityOptions.abort(options);
+                return false;
+            }
+            intent = new Intent(intent);
+            // The caller is not allowed to change the data.
+            intent.setDataAndType(r.intent.getData(), r.intent.getType());
+            // And we are resetting to find the next component...
+            intent.setComponent(null);
+
+            final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+
+            ActivityInfo aInfo = null;
+            try {
+                List<ResolveInfo> resolves =
+                    AppGlobals.getPackageManager().queryIntentActivities(
+                            intent, r.resolvedType,
+                            PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS,
+                            UserHandle.getCallingUserId());
+
+                // Look for the original activity in the list...
+                final int N = resolves != null ? resolves.size() : 0;
+                for (int i=0; i<N; i++) {
+                    ResolveInfo rInfo = resolves.get(i);
+                    if (rInfo.activityInfo.packageName.equals(r.packageName)
+                            && rInfo.activityInfo.name.equals(r.info.name)) {
+                        // We found the current one...  the next matching is
+                        // after it.
+                        i++;
+                        if (i<N) {
+                            aInfo = resolves.get(i).activityInfo;
+                        }
+                        if (debug) {
+                            Slog.v(TAG, "Next matching activity: found current " + r.packageName
+                                    + "/" + r.info.name);
+                            Slog.v(TAG, "Next matching activity: next is " + aInfo.packageName
+                                    + "/" + aInfo.name);
+                        }
+                        break;
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+
+            if (aInfo == null) {
+                // Nobody who is next!
+                ActivityOptions.abort(options);
+                if (debug) Slog.d(TAG, "Next matching activity: nothing found");
+                return false;
+            }
+
+            intent.setComponent(new ComponentName(
+                    aInfo.applicationInfo.packageName, aInfo.name));
+            intent.setFlags(intent.getFlags()&~(
+                    Intent.FLAG_ACTIVITY_FORWARD_RESULT|
+                    Intent.FLAG_ACTIVITY_CLEAR_TOP|
+                    Intent.FLAG_ACTIVITY_MULTIPLE_TASK|
+                    Intent.FLAG_ACTIVITY_NEW_TASK));
+
+            // Okay now we need to start the new activity, replacing the
+            // currently running activity.  This is a little tricky because
+            // we want to start the new one as if the current one is finished,
+            // but not finish the current one first so that there is no flicker.
+            // And thus...
+            final boolean wasFinishing = r.finishing;
+            r.finishing = true;
+
+            // Propagate reply information over to the new activity.
+            final ActivityRecord resultTo = r.resultTo;
+            final String resultWho = r.resultWho;
+            final int requestCode = r.requestCode;
+            r.resultTo = null;
+            if (resultTo != null) {
+                resultTo.removeResultsLocked(r, resultWho, requestCode);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
+                    r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
+                    resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
+                    options, false, null);
+            Binder.restoreCallingIdentity(origId);
+
+            r.finishing = wasFinishing;
+            if (res != ActivityManager.START_SUCCESS) {
+                return false;
+            }
+            return true;
+        }
+    }
+
+    final int startActivityInPackage(int uid, String callingPackage,
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, Bundle options, int userId) {
+
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivityInPackage", null);
+
+        // TODO: Switch to user app stacks here.
+        int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
+                resultTo, resultWho, requestCode, startFlags,
+                null, null, null, null, options, userId);
+        return ret;
+    }
+
+    @Override
+    public final int startActivities(IApplicationThread caller, String callingPackage,
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options,
+            int userId) {
+        enforceNotIsolatedCaller("startActivities");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivity", null);
+        // TODO: Switch to user app stacks here.
+        int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents,
+                resolvedTypes, resultTo, options, userId);
+        return ret;
+    }
+
+    final int startActivitiesInPackage(int uid, String callingPackage,
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options, int userId) {
+
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivityInPackage", null);
+        // TODO: Switch to user app stacks here.
+        int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes,
+                resultTo, options, userId);
+        return ret;
+    }
+
+    final void addRecentTaskLocked(TaskRecord task) {
+        int N = mRecentTasks.size();
+        // Quick case: check if the top-most recent task is the same.
+        if (N > 0 && mRecentTasks.get(0) == task) {
+            return;
+        }
+        // Remove any existing entries that are the same kind of task.
+        for (int i=0; i<N; i++) {
+            TaskRecord tr = mRecentTasks.get(i);
+            if (task.userId == tr.userId
+                    && ((task.affinity != null && task.affinity.equals(tr.affinity))
+                    || (task.intent != null && task.intent.filterEquals(tr.intent)))) {
+                tr.disposeThumbnail();
+                mRecentTasks.remove(i);
+                i--;
+                N--;
+                if (task.intent == null) {
+                    // If the new recent task we are adding is not fully
+                    // specified, then replace it with the existing recent task.
+                    task = tr;
+                }
+            }
+        }
+        if (N >= MAX_RECENT_TASKS) {
+            mRecentTasks.remove(N-1).disposeThumbnail();
+        }
+        mRecentTasks.add(0, task);
+    }
+
+    @Override
+    public void reportActivityFullyDrawn(IBinder token) {
+        synchronized (this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return;
+            }
+            r.reportFullyDrawnLocked();
+        }
+    }
+
+    @Override
+    public void setRequestedOrientation(IBinder token, int requestedOrientation) {
+        synchronized (this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            mWindowManager.setAppOrientation(r.appToken, requestedOrientation);
+            Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                    mConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null);
+            if (config != null) {
+                r.frozenBeforeDestroy = true;
+                if (!updateConfigurationLocked(config, r, false, false)) {
+                    mStackSupervisor.resumeTopActivitiesLocked();
+                }
+            }
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public int getRequestedOrientation(IBinder token) {
+        synchronized (this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+            return mWindowManager.getAppOrientation(r.appToken);
+        }
+    }
+
+    /**
+     * This is the internal entry point for handling Activity.finish().
+     *
+     * @param token The Binder token referencing the Activity we want to finish.
+     * @param resultCode Result code, if any, from this Activity.
+     * @param resultData Result data (Intent), if any, from this Activity.
+     *
+     * @return Returns true if the activity successfully finished, or false if it is still running.
+     */
+    @Override
+    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
+        // Refuse possible leaked file descriptors
+        if (resultData != null && resultData.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return true;
+            }
+            if (mController != null) {
+                // Find the first activity that is not finishing.
+                ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
+                if (next != null) {
+                    // ask watcher if this is allowed
+                    boolean resumeOK = true;
+                    try {
+                        resumeOK = mController.activityResuming(next.packageName);
+                    } catch (RemoteException e) {
+                        mController = null;
+                        Watchdog.getInstance().setActivityController(null);
+                    }
+
+                    if (!resumeOK) {
+                        return false;
+                    }
+                }
+            }
+            final long origId = Binder.clearCallingIdentity();
+            boolean res = r.task.stack.requestFinishActivityLocked(token, resultCode,
+                    resultData, "app-request", true);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    @Override
+    public final void finishHeavyWeightApp() {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: finishHeavyWeightApp() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        synchronized(this) {
+            if (mHeavyWeightProcess == null) {
+                return;
+            }
+
+            ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(
+                    mHeavyWeightProcess.activities);
+            for (int i=0; i<activities.size(); i++) {
+                ActivityRecord r = activities.get(i);
+                if (!r.finishing) {
+                    r.task.stack.finishActivityLocked(r, Activity.RESULT_CANCELED,
+                            null, "finish-heavy", true);
+                }
+            }
+
+            mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
+                    mHeavyWeightProcess.userId, 0));
+            mHeavyWeightProcess = null;
+        }
+    }
+
+    @Override
+    public void crashApplication(int uid, int initialPid, String packageName,
+            String message) {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: crashApplication() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        synchronized(this) {
+            ProcessRecord proc = null;
+
+            // Figure out which process to kill.  We don't trust that initialPid
+            // still has any relation to current pids, so must scan through the
+            // list.
+            synchronized (mPidsSelfLocked) {
+                for (int i=0; i<mPidsSelfLocked.size(); i++) {
+                    ProcessRecord p = mPidsSelfLocked.valueAt(i);
+                    if (p.uid != uid) {
+                        continue;
+                    }
+                    if (p.pid == initialPid) {
+                        proc = p;
+                        break;
+                    }
+                    if (p.pkgList.containsKey(packageName)) {
+                        proc = p;
+                    }
+                }
+            }
+
+            if (proc == null) {
+                Slog.w(TAG, "crashApplication: nothing for uid=" + uid
+                        + " initialPid=" + initialPid
+                        + " packageName=" + packageName);
+                return;
+            }
+
+            if (proc.thread != null) {
+                if (proc.pid == Process.myPid()) {
+                    Log.w(TAG, "crashApplication: trying to crash self!");
+                    return;
+                }
+                long ident = Binder.clearCallingIdentity();
+                try {
+                    proc.thread.scheduleCrash(message);
+                } catch (RemoteException e) {
+                }
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public final void finishSubActivity(IBinder token, String resultWho,
+            int requestCode) {
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r != null) {
+                r.task.stack.finishSubActivityLocked(r, resultWho, requestCode);
+            }
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public boolean finishActivityAffinity(IBinder token) {
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            boolean res = false;
+            if (r != null) {
+                res = r.task.stack.finishActivityAffinityLocked(r);
+            }
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    @Override
+    public boolean willActivityBeVisible(IBinder token) {
+        synchronized(this) {
+            ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                return stack.willActivityBeVisibleLocked(token);
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public void overridePendingTransition(IBinder token, String packageName,
+            int enterAnim, int exitAnim) {
+        synchronized(this) {
+            ActivityRecord self = ActivityRecord.isInStackLocked(token);
+            if (self == null) {
+                return;
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+
+            if (self.state == ActivityState.RESUMED
+                    || self.state == ActivityState.PAUSING) {
+                mWindowManager.overridePendingAppTransition(packageName,
+                        enterAnim, exitAnim, null);
+            }
+
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * Main function for removing an existing process from the activity manager
+     * as a result of that process going away.  Clears out all connections
+     * to the process.
+     */
+    private final void handleAppDiedLocked(ProcessRecord app,
+            boolean restarting, boolean allowRestart) {
+        cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
+        if (!restarting) {
+            removeLruProcessLocked(app);
+        }
+
+        if (mProfileProc == app) {
+            clearProfilerLocked();
+        }
+
+        // Remove this application's activities from active lists.
+        boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
+
+        app.activities.clear();
+
+        if (app.instrumentationClass != null) {
+            Slog.w(TAG, "Crash of app " + app.processName
+                  + " running instrumentation " + app.instrumentationClass);
+            Bundle info = new Bundle();
+            info.putString("shortMsg", "Process crashed.");
+            finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
+        }
+
+        if (!restarting) {
+            if (!mStackSupervisor.resumeTopActivitiesLocked()) {
+                // If there was nothing to resume, and we are not already
+                // restarting this process, but there is a visible activity that
+                // is hosted by the process...  then make sure all visible
+                // activities are running, taking care of restarting this
+                // process.
+                if (hasVisibleActivities) {
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+                }
+            }
+        }
+    }
+
+    private final int getLRURecordIndexForAppLocked(IApplicationThread thread) {
+        IBinder threadBinder = thread.asBinder();
+        // Find the application record.
+        for (int i=mLruProcesses.size()-1; i>=0; i--) {
+            ProcessRecord rec = mLruProcesses.get(i);
+            if (rec.thread != null && rec.thread.asBinder() == threadBinder) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    final ProcessRecord getRecordForAppLocked(
+            IApplicationThread thread) {
+        if (thread == null) {
+            return null;
+        }
+
+        int appIndex = getLRURecordIndexForAppLocked(thread);
+        return appIndex >= 0 ? mLruProcesses.get(appIndex) : null;
+    }
+
+    final void doLowMemReportIfNeededLocked(ProcessRecord dyingProc) {
+        // If there are no longer any background processes running,
+        // and the app that died was not running instrumentation,
+        // then tell everyone we are now low on memory.
+        boolean haveBg = false;
+        for (int i=mLruProcesses.size()-1; i>=0; i--) {
+            ProcessRecord rec = mLruProcesses.get(i);
+            if (rec.thread != null
+                    && rec.setProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
+                haveBg = true;
+                break;
+            }
+        }
+
+        if (!haveBg) {
+            boolean doReport = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+            if (doReport) {
+                long now = SystemClock.uptimeMillis();
+                if (now < (mLastMemUsageReportTime+5*60*1000)) {
+                    doReport = false;
+                } else {
+                    mLastMemUsageReportTime = now;
+                }
+            }
+            final ArrayList<ProcessMemInfo> memInfos
+                    = doReport ? new ArrayList<ProcessMemInfo>(mLruProcesses.size()) : null;
+            EventLog.writeEvent(EventLogTags.AM_LOW_MEMORY, mLruProcesses.size());
+            long now = SystemClock.uptimeMillis();
+            for (int i=mLruProcesses.size()-1; i>=0; i--) {
+                ProcessRecord rec = mLruProcesses.get(i);
+                if (rec == dyingProc || rec.thread == null) {
+                    continue;
+                }
+                if (doReport) {
+                    memInfos.add(new ProcessMemInfo(rec.processName, rec.pid, rec.setAdj,
+                            rec.setProcState, rec.adjType, rec.makeAdjReason()));
+                }
+                if ((rec.lastLowMemory+GC_MIN_INTERVAL) <= now) {
+                    // The low memory report is overriding any current
+                    // state for a GC request.  Make sure to do
+                    // heavy/important/visible/foreground processes first.
+                    if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                        rec.lastRequestedGc = 0;
+                    } else {
+                        rec.lastRequestedGc = rec.lastLowMemory;
+                    }
+                    rec.reportLowMemory = true;
+                    rec.lastLowMemory = now;
+                    mProcessesToGc.remove(rec);
+                    addProcessToGcListLocked(rec);
+                }
+            }
+            if (doReport) {
+                Message msg = mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos);
+                mHandler.sendMessage(msg);
+            }
+            scheduleAppGcsLocked();
+        }
+    }
+
+    final void appDiedLocked(ProcessRecord app, int pid,
+            IApplicationThread thread) {
+
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        synchronized (stats) {
+            stats.noteProcessDiedLocked(app.info.uid, pid);
+        }
+
+        // Clean up already done if the process has been re-started.
+        if (app.pid == pid && app.thread != null &&
+                app.thread.asBinder() == thread.asBinder()) {
+            boolean doLowMem = app.instrumentationClass == null;
+            boolean doOomAdj = doLowMem;
+            if (!app.killedByAm) {
+                Slog.i(TAG, "Process " + app.processName + " (pid " + pid
+                        + ") has died.");
+                mAllowLowerMemLevel = true;
+            } else {
+                // Note that we always want to do oom adj to update our state with the
+                // new number of procs.
+                mAllowLowerMemLevel = false;
+                doLowMem = false;
+            }
+            EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
+            if (DEBUG_CLEANUP) Slog.v(
+                TAG, "Dying app: " + app + ", pid: " + pid
+                + ", thread: " + thread.asBinder());
+            handleAppDiedLocked(app, false, true);
+
+            if (doOomAdj) {
+                updateOomAdjLocked();
+            }
+            if (doLowMem) {
+                doLowMemReportIfNeededLocked(app);
+            }
+        } else if (app.pid != pid) {
+            // A new process has already been started.
+            Slog.i(TAG, "Process " + app.processName + " (pid " + pid
+                    + ") has died and restarted (pid " + app.pid + ").");
+            EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName);
+        } else if (DEBUG_PROCESSES) {
+            Slog.d(TAG, "Received spurious death notification for thread "
+                    + thread.asBinder());
+        }
+    }
+
+    /**
+     * If a stack trace dump file is configured, dump process stack traces.
+     * @param clearTraces causes the dump file to be erased prior to the new
+     *    traces being written, if true; when false, the new traces will be
+     *    appended to any existing file content.
+     * @param firstPids of dalvik VM processes to dump stack traces for first
+     * @param lastPids of dalvik VM processes to dump stack traces for last
+     * @param nativeProcs optional list of native process names to dump stack crawls
+     * @return file containing stack traces, or null if no dump file is configured
+     */
+    public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids,
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
+        if (tracesPath == null || tracesPath.length() == 0) {
+            return null;
+        }
+
+        File tracesFile = new File(tracesPath);
+        try {
+            File tracesDir = tracesFile.getParentFile();
+            if (!tracesDir.exists()) {
+                tracesFile.mkdirs();
+                if (!SELinux.restorecon(tracesDir)) {
+                    return null;
+                }
+            }
+            FileUtils.setPermissions(tracesDir.getPath(), 0775, -1, -1);  // drwxrwxr-x
+
+            if (clearTraces && tracesFile.exists()) tracesFile.delete();
+            tracesFile.createNewFile();
+            FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
+        } catch (IOException e) {
+            Slog.w(TAG, "Unable to prepare ANR traces file: " + tracesPath, e);
+            return null;
+        }
+
+        dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs);
+        return tracesFile;
+    }
+
+    private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids,
+            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) {
+        // Use a FileObserver to detect when traces finish writing.
+        // The order of traces is considered important to maintain for legibility.
+        FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) {
+            @Override
+            public synchronized void onEvent(int event, String path) { notify(); }
+        };
+
+        try {
+            observer.startWatching();
+
+            // First collect all of the stacks of the most important pids.
+            if (firstPids != null) {
+                try {
+                    int num = firstPids.size();
+                    for (int i = 0; i < num; i++) {
+                        synchronized (observer) {
+                            Process.sendSignal(firstPids.get(i), Process.SIGNAL_QUIT);
+                            observer.wait(200);  // Wait for write-close, give up after 200msec
+                        }
+                    }
+                } catch (InterruptedException e) {
+                    Log.wtf(TAG, e);
+                }
+            }
+
+            // Next collect the stacks of the native pids
+            if (nativeProcs != null) {
+                int[] pids = Process.getPidsForCommands(nativeProcs);
+                if (pids != null) {
+                    for (int pid : pids) {
+                        Debug.dumpNativeBacktraceToFile(pid, tracesPath);
+                    }
+                }
+            }
+
+            // Lastly, measure CPU usage.
+            if (processCpuTracker != null) {
+                processCpuTracker.init();
+                System.gc();
+                processCpuTracker.update();
+                try {
+                    synchronized (processCpuTracker) {
+                        processCpuTracker.wait(500); // measure over 1/2 second.
+                    }
+                } catch (InterruptedException e) {
+                }
+                processCpuTracker.update();
+
+                // We'll take the stack crawls of just the top apps using CPU.
+                final int N = processCpuTracker.countWorkingStats();
+                int numProcs = 0;
+                for (int i=0; i<N && numProcs<5; i++) {
+                    ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+                    if (lastPids.indexOfKey(stats.pid) >= 0) {
+                        numProcs++;
+                        try {
+                            synchronized (observer) {
+                                Process.sendSignal(stats.pid, Process.SIGNAL_QUIT);
+                                observer.wait(200);  // Wait for write-close, give up after 200msec
+                            }
+                        } catch (InterruptedException e) {
+                            Log.wtf(TAG, e);
+                        }
+
+                    }
+                }
+            }
+        } finally {
+            observer.stopWatching();
+        }
+    }
+
+    final void logAppTooSlow(ProcessRecord app, long startTime, String msg) {
+        if (true || IS_USER_BUILD) {
+            return;
+        }
+        String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null);
+        if (tracesPath == null || tracesPath.length() == 0) {
+            return;
+        }
+
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        StrictMode.allowThreadDiskWrites();
+        try {
+            final File tracesFile = new File(tracesPath);
+            final File tracesDir = tracesFile.getParentFile();
+            final File tracesTmp = new File(tracesDir, "__tmp__");
+            try {
+                if (!tracesDir.exists()) {
+                    tracesFile.mkdirs();
+                    if (!SELinux.restorecon(tracesDir.getPath())) {
+                        return;
+                    }
+                }
+                FileUtils.setPermissions(tracesDir.getPath(), 0775, -1, -1);  // drwxrwxr-x
+
+                if (tracesFile.exists()) {
+                    tracesTmp.delete();
+                    tracesFile.renameTo(tracesTmp);
+                }
+                StringBuilder sb = new StringBuilder();
+                Time tobj = new Time();
+                tobj.set(System.currentTimeMillis());
+                sb.append(tobj.format("%Y-%m-%d %H:%M:%S"));
+                sb.append(": ");
+                TimeUtils.formatDuration(SystemClock.uptimeMillis()-startTime, sb);
+                sb.append(" since ");
+                sb.append(msg);
+                FileOutputStream fos = new FileOutputStream(tracesFile);
+                fos.write(sb.toString().getBytes());
+                if (app == null) {
+                    fos.write("\n*** No application process!".getBytes());
+                }
+                fos.close();
+                FileUtils.setPermissions(tracesFile.getPath(), 0666, -1, -1); // -rw-rw-rw-
+            } catch (IOException e) {
+                Slog.w(TAG, "Unable to prepare slow app traces file: " + tracesPath, e);
+                return;
+            }
+
+            if (app != null) {
+                ArrayList<Integer> firstPids = new ArrayList<Integer>();
+                firstPids.add(app.pid);
+                dumpStackTraces(tracesPath, firstPids, null, null, null);
+            }
+
+            File lastTracesFile = null;
+            File curTracesFile = null;
+            for (int i=9; i>=0; i--) {
+                String name = String.format(Locale.US, "slow%02d.txt", i);
+                curTracesFile = new File(tracesDir, name);
+                if (curTracesFile.exists()) {
+                    if (lastTracesFile != null) {
+                        curTracesFile.renameTo(lastTracesFile);
+                    } else {
+                        curTracesFile.delete();
+                    }
+                }
+                lastTracesFile = curTracesFile;
+            }
+            tracesFile.renameTo(curTracesFile);
+            if (tracesTmp.exists()) {
+                tracesTmp.renameTo(tracesFile);
+            }
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    final void appNotResponding(ProcessRecord app, ActivityRecord activity,
+            ActivityRecord parent, boolean aboveSystem, final String annotation) {
+        ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
+        SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
+
+        if (mController != null) {
+            try {
+                // 0 == continue, -1 = kill process immediately
+                int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
+                if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid);
+            } catch (RemoteException e) {
+                mController = null;
+                Watchdog.getInstance().setActivityController(null);
+            }
+        }
+
+        long anrTime = SystemClock.uptimeMillis();
+        if (MONITOR_CPU_USAGE) {
+            updateCpuStatsNow();
+        }
+
+        synchronized (this) {
+            // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
+            if (mShuttingDown) {
+                Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
+                return;
+            } else if (app.notResponding) {
+                Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
+                return;
+            } else if (app.crashing) {
+                Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
+                return;
+            }
+
+            // In case we come through here for the same app before completing
+            // this one, mark as anring now so we will bail out.
+            app.notResponding = true;
+
+            // Log the ANR to the event log.
+            EventLog.writeEvent(EventLogTags.AM_ANR, app.userId, app.pid,
+                    app.processName, app.info.flags, annotation);
+
+            // Dump thread traces as quickly as we can, starting with "interesting" processes.
+            firstPids.add(app.pid);
+
+            int parentPid = app.pid;
+            if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid;
+            if (parentPid != app.pid) firstPids.add(parentPid);
+
+            if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID);
+
+            for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+                ProcessRecord r = mLruProcesses.get(i);
+                if (r != null && r.thread != null) {
+                    int pid = r.pid;
+                    if (pid > 0 && pid != app.pid && pid != parentPid && pid != MY_PID) {
+                        if (r.persistent) {
+                            firstPids.add(pid);
+                        } else {
+                            lastPids.put(pid, Boolean.TRUE);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Log the ANR to the main log.
+        StringBuilder info = new StringBuilder();
+        info.setLength(0);
+        info.append("ANR in ").append(app.processName);
+        if (activity != null && activity.shortComponentName != null) {
+            info.append(" (").append(activity.shortComponentName).append(")");
+        }
+        info.append("\n");
+        info.append("PID: ").append(app.pid).append("\n");
+        if (annotation != null) {
+            info.append("Reason: ").append(annotation).append("\n");
+        }
+        if (parent != null && parent != activity) {
+            info.append("Parent: ").append(parent.shortComponentName).append("\n");
+        }
+
+        final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
+
+        File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids,
+                NATIVE_STACKS_OF_INTEREST);
+
+        String cpuInfo = null;
+        if (MONITOR_CPU_USAGE) {
+            updateCpuStatsNow();
+            synchronized (mProcessCpuThread) {
+                cpuInfo = mProcessCpuTracker.printCurrentState(anrTime);
+            }
+            info.append(processCpuTracker.printCurrentLoad());
+            info.append(cpuInfo);
+        }
+
+        info.append(processCpuTracker.printCurrentState(anrTime));
+
+        Slog.e(TAG, info.toString());
+        if (tracesFile == null) {
+            // There is no trace file, so dump (only) the alleged culprit's threads to the log
+            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
+        }
+
+        addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
+                cpuInfo, tracesFile, null);
+
+        if (mController != null) {
+            try {
+                // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately
+                int res = mController.appNotResponding(app.processName, app.pid, info.toString());
+                if (res != 0) {
+                    if (res < 0 && app.pid != MY_PID) {
+                        Process.killProcess(app.pid);
+                    } else {
+                        synchronized (this) {
+                            mServices.scheduleServiceTimeoutLocked(app);
+                        }
+                    }
+                    return;
+                }
+            } catch (RemoteException e) {
+                mController = null;
+                Watchdog.getInstance().setActivityController(null);
+            }
+        }
+
+        // Unless configured otherwise, swallow ANRs in background processes & kill the process.
+        boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
+                Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;
+
+        synchronized (this) {
+            if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
+                killUnneededProcessLocked(app, "background ANR");
+                return;
+            }
+
+            // Set the app's notResponding state, and look up the errorReportReceiver
+            makeAppNotRespondingLocked(app,
+                    activity != null ? activity.shortComponentName : null,
+                    annotation != null ? "ANR " + annotation : "ANR",
+                    info.toString());
+
+            // Bring up the infamous App Not Responding dialog
+            Message msg = Message.obtain();
+            HashMap<String, Object> map = new HashMap<String, Object>();
+            msg.what = SHOW_NOT_RESPONDING_MSG;
+            msg.obj = map;
+            msg.arg1 = aboveSystem ? 1 : 0;
+            map.put("app", app);
+            if (activity != null) {
+                map.put("activity", activity);
+            }
+
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    final void showLaunchWarningLocked(final ActivityRecord cur, final ActivityRecord next) {
+        if (!mLaunchWarningShown) {
+            mLaunchWarningShown = true;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    synchronized (ActivityManagerService.this) {
+                        final Dialog d = new LaunchWarningWindow(mContext, cur, next);
+                        d.show();
+                        mHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                synchronized (ActivityManagerService.this) {
+                                    d.dismiss();
+                                    mLaunchWarningShown = false;
+                                }
+                            }
+                        }, 4000);
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public boolean clearApplicationUserData(final String packageName,
+            final IPackageDataObserver observer, int userId) {
+        enforceNotIsolatedCaller("clearApplicationUserData");
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        userId = handleIncomingUser(pid, uid,
+                userId, false, true, "clearApplicationUserData", null);
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            int pkgUid = -1;
+            synchronized(this) {
+                try {
+                    pkgUid = pm.getPackageUid(packageName, userId);
+                } catch (RemoteException e) {
+                }
+                if (pkgUid == -1) {
+                    Slog.w(TAG, "Invalid packageName: " + packageName);
+                    if (observer != null) {
+                        try {
+                            observer.onRemoveCompleted(packageName, false);
+                        } catch (RemoteException e) {
+                            Slog.i(TAG, "Observer no longer exists.");
+                        }
+                    }
+                    return false;
+                }
+                if (uid == pkgUid || checkComponentPermission(
+                        android.Manifest.permission.CLEAR_APP_USER_DATA,
+                        pid, uid, -1, true)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    forceStopPackageLocked(packageName, pkgUid, "clear data");
+                } else {
+                    throw new SecurityException("PID " + pid + " does not have permission "
+                            + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data"
+                                    + " of package " + packageName);
+                }
+            }
+
+            try {
+                // Clear application user data
+                pm.clearApplicationUserData(packageName, observer, userId);
+
+                // Remove all permissions granted from/to this package
+                removeUriPermissionsForPackageLocked(packageName, userId, true);
+
+                Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED,
+                        Uri.fromParts("package", packageName, null));
+                intent.putExtra(Intent.EXTRA_UID, pkgUid);
+                broadcastIntentInPackage("android", Process.SYSTEM_UID, intent,
+                        null, null, 0, null, null, null, false, false, userId);
+            } catch (RemoteException e) {
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        return true;
+    }
+
+    @Override
+    public void killBackgroundProcesses(final String packageName, int userId) {
+        if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
+                != PackageManager.PERMISSION_GRANTED &&
+                checkCallingPermission(android.Manifest.permission.RESTART_PACKAGES)
+                        != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: killBackgroundProcesses() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, "killBackgroundProcesses", null);
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            synchronized(this) {
+                int appId = -1;
+                try {
+                    appId = UserHandle.getAppId(pm.getPackageUid(packageName, 0));
+                } catch (RemoteException e) {
+                }
+                if (appId == -1) {
+                    Slog.w(TAG, "Invalid packageName: " + packageName);
+                    return;
+                }
+                killPackageProcessesLocked(packageName, appId, userId,
+                        ProcessList.SERVICE_ADJ, false, true, true, false, "kill background");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    @Override
+    public void killAllBackgroundProcesses() {
+        if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+                final int NP = mProcessNames.getMap().size();
+                for (int ip=0; ip<NP; ip++) {
+                    SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+                    final int NA = apps.size();
+                    for (int ia=0; ia<NA; ia++) {
+                        ProcessRecord app = apps.valueAt(ia);
+                        if (app.persistent) {
+                            // we don't kill persistent processes
+                            continue;
+                        }
+                        if (app.removed) {
+                            procs.add(app);
+                        } else if (app.setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                            app.removed = true;
+                            procs.add(app);
+                        }
+                    }
+                }
+
+                int N = procs.size();
+                for (int i=0; i<N; i++) {
+                    removeProcessLocked(procs.get(i), false, true, "kill all background");
+                }
+                mAllowLowerMemLevel = true;
+                updateOomAdjLocked();
+                doLowMemReportIfNeededLocked(null);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    @Override
+    public void forceStopPackage(final String packageName, int userId) {
+        if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: forceStopPackage() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.FORCE_STOP_PACKAGES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        final int callingPid = Binder.getCallingPid();
+        userId = handleIncomingUser(callingPid, Binder.getCallingUid(),
+                userId, true, true, "forceStopPackage", null);
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            synchronized(this) {
+                int[] users = userId == UserHandle.USER_ALL
+                        ? getUsersLocked() : new int[] { userId };
+                for (int user : users) {
+                    int pkgUid = -1;
+                    try {
+                        pkgUid = pm.getPackageUid(packageName, user);
+                    } catch (RemoteException e) {
+                    }
+                    if (pkgUid == -1) {
+                        Slog.w(TAG, "Invalid packageName: " + packageName);
+                        continue;
+                    }
+                    try {
+                        pm.setPackageStoppedState(packageName, true, user);
+                    } catch (RemoteException e) {
+                    } catch (IllegalArgumentException e) {
+                        Slog.w(TAG, "Failed trying to unstop package "
+                                + packageName + ": " + e);
+                    }
+                    if (isUserRunningLocked(user, false)) {
+                        forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /*
+     * The pkg name and app id have to be specified.
+     */
+    @Override
+    public void killApplicationWithAppId(String pkg, int appid, String reason) {
+        if (pkg == null) {
+            return;
+        }
+        // Make sure the uid is valid.
+        if (appid < 0) {
+            Slog.w(TAG, "Invalid appid specified for pkg : " + pkg);
+            return;
+        }
+        int callerUid = Binder.getCallingUid();
+        // Only the system server can kill an application
+        if (callerUid == Process.SYSTEM_UID) {
+            // Post an aysnc message to kill the application
+            Message msg = mHandler.obtainMessage(KILL_APPLICATION_MSG);
+            msg.arg1 = appid;
+            msg.arg2 = 0;
+            Bundle bundle = new Bundle();
+            bundle.putString("pkg", pkg);
+            bundle.putString("reason", reason);
+            msg.obj = bundle;
+            mHandler.sendMessage(msg);
+        } else {
+            throw new SecurityException(callerUid + " cannot kill pkg: " +
+                    pkg);
+        }
+    }
+
+    @Override
+    public void closeSystemDialogs(String reason) {
+        enforceNotIsolatedCaller("closeSystemDialogs");
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                // Only allow this from foreground processes, so that background
+                // applications can't abuse it to prevent system UI from being shown.
+                if (uid >= Process.FIRST_APPLICATION_UID) {
+                    ProcessRecord proc;
+                    synchronized (mPidsSelfLocked) {
+                        proc = mPidsSelfLocked.get(pid);
+                    }
+                    if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        Slog.w(TAG, "Ignoring closeSystemDialogs " + reason
+                                + " from background process " + proc);
+                        return;
+                    }
+                }
+                closeSystemDialogsLocked(reason);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void closeSystemDialogsLocked(String reason) {
+        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        if (reason != null) {
+            intent.putExtra("reason", reason);
+        }
+        mWindowManager.closeSystemDialogs(reason);
+
+        mStackSupervisor.closeSystemDialogsLocked();
+
+        broadcastIntentLocked(null, null, intent, null,
+                null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1,
+                Process.SYSTEM_UID, UserHandle.USER_ALL);
+    }
+
+    @Override
+    public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) {
+        enforceNotIsolatedCaller("getProcessMemoryInfo");
+        Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length];
+        for (int i=pids.length-1; i>=0; i--) {
+            ProcessRecord proc;
+            int oomAdj;
+            synchronized (this) {
+                synchronized (mPidsSelfLocked) {
+                    proc = mPidsSelfLocked.get(pids[i]);
+                    oomAdj = proc != null ? proc.setAdj : 0;
+                }
+            }
+            infos[i] = new Debug.MemoryInfo();
+            Debug.getMemoryInfo(pids[i], infos[i]);
+            if (proc != null) {
+                synchronized (this) {
+                    if (proc.thread != null && proc.setAdj == oomAdj) {
+                        // Record this for posterity if the process has been stable.
+                        proc.baseProcessTracker.addPss(infos[i].getTotalPss(),
+                                infos[i].getTotalUss(), false, proc.pkgList);
+                    }
+                }
+            }
+        }
+        return infos;
+    }
+
+    @Override
+    public long[] getProcessPss(int[] pids) {
+        enforceNotIsolatedCaller("getProcessPss");
+        long[] pss = new long[pids.length];
+        for (int i=pids.length-1; i>=0; i--) {
+            ProcessRecord proc;
+            int oomAdj;
+            synchronized (this) {
+                synchronized (mPidsSelfLocked) {
+                    proc = mPidsSelfLocked.get(pids[i]);
+                    oomAdj = proc != null ? proc.setAdj : 0;
+                }
+            }
+            long[] tmpUss = new long[1];
+            pss[i] = Debug.getPss(pids[i], tmpUss);
+            if (proc != null) {
+                synchronized (this) {
+                    if (proc.thread != null && proc.setAdj == oomAdj) {
+                        // Record this for posterity if the process has been stable.
+                        proc.baseProcessTracker.addPss(pss[i], tmpUss[0], false, proc.pkgList);
+                    }
+                }
+            }
+        }
+        return pss;
+    }
+
+    @Override
+    public void killApplicationProcess(String processName, int uid) {
+        if (processName == null) {
+            return;
+        }
+
+        int callerUid = Binder.getCallingUid();
+        // Only the system server can kill an application
+        if (callerUid == Process.SYSTEM_UID) {
+            synchronized (this) {
+                ProcessRecord app = getProcessRecordLocked(processName, uid, true);
+                if (app != null && app.thread != null) {
+                    try {
+                        app.thread.scheduleSuicide();
+                    } catch (RemoteException e) {
+                        // If the other end already died, then our work here is done.
+                    }
+                } else {
+                    Slog.w(TAG, "Process/uid not found attempting kill of "
+                            + processName + " / " + uid);
+                }
+            }
+        } else {
+            throw new SecurityException(callerUid + " cannot kill app process: " +
+                    processName);
+        }
+    }
+
+    private void forceStopPackageLocked(final String packageName, int uid, String reason) {
+        forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false,
+                false, true, false, UserHandle.getUserId(uid), reason);
+        Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED,
+                Uri.fromParts("package", packageName, null));
+        if (!mProcessesReady) {
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                    | Intent.FLAG_RECEIVER_FOREGROUND);
+        }
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, UserHandle.getUserId(uid));
+        broadcastIntentLocked(null, null, intent,
+                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                false, false,
+                MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid));
+    }
+
+    private void forceStopUserLocked(int userId, String reason) {
+        forceStopPackageLocked(null, -1, false, false, true, false, userId, reason);
+        Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                | Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        broadcastIntentLocked(null, null, intent,
+                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                false, false,
+                MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+    }
+
+    private final boolean killPackageProcessesLocked(String packageName, int appId,
+            int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
+            boolean doit, boolean evenPersistent, String reason) {
+        ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+
+        // Remove all processes this package may have touched: all with the
+        // same UID (except for the system or root user), and all whose name
+        // matches the package name.
+        final String procNamePrefix = packageName != null ? (packageName + ":") : null;
+        final int NP = mProcessNames.getMap().size();
+        for (int ip=0; ip<NP; ip++) {
+            SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+            final int NA = apps.size();
+            for (int ia=0; ia<NA; ia++) {
+                ProcessRecord app = apps.valueAt(ia);
+                if (app.persistent && !evenPersistent) {
+                    // we don't kill persistent processes
+                    continue;
+                }
+                if (app.removed) {
+                    if (doit) {
+                        procs.add(app);
+                    }
+                    continue;
+                }
+
+                // Skip process if it doesn't meet our oom adj requirement.
+                if (app.setAdj < minOomAdj) {
+                    continue;
+                }
+
+                // If no package is specified, we call all processes under the
+                // give user id.
+                if (packageName == null) {
+                    if (app.userId != userId) {
+                        continue;
+                    }
+                    if (appId >= 0 && UserHandle.getAppId(app.uid) != appId) {
+                        continue;
+                    }
+                // Package has been specified, we want to hit all processes
+                // that match it.  We need to qualify this by the processes
+                // that are running under the specified app and user ID.
+                } else {
+                    if (UserHandle.getAppId(app.uid) != appId) {
+                        continue;
+                    }
+                    if (userId != UserHandle.USER_ALL && app.userId != userId) {
+                        continue;
+                    }
+                    if (!app.pkgList.containsKey(packageName)) {
+                        continue;
+                    }
+                }
+
+                // Process has passed all conditions, kill it!
+                if (!doit) {
+                    return true;
+                }
+                app.removed = true;
+                procs.add(app);
+            }
+        }
+
+        int N = procs.size();
+        for (int i=0; i<N; i++) {
+            removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason);
+        }
+        updateOomAdjLocked();
+        return N > 0;
+    }
+
+    private final boolean forceStopPackageLocked(String name, int appId,
+            boolean callerWillRestart, boolean purgeCache, boolean doit,
+            boolean evenPersistent, int userId, String reason) {
+        int i;
+        int N;
+
+        if (userId == UserHandle.USER_ALL && name == null) {
+            Slog.w(TAG, "Can't force stop all processes of all users, that is insane!");
+        }
+
+        if (appId < 0 && name != null) {
+            try {
+                appId = UserHandle.getAppId(
+                        AppGlobals.getPackageManager().getPackageUid(name, 0));
+            } catch (RemoteException e) {
+            }
+        }
+
+        if (doit) {
+            if (name != null) {
+                Slog.i(TAG, "Force stopping " + name + " appid=" + appId
+                        + " user=" + userId + ": " + reason);
+            } else {
+                Slog.i(TAG, "Force stopping u" + userId + ": " + reason);
+            }
+
+            final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+            for (int ip=pmap.size()-1; ip>=0; ip--) {
+                SparseArray<Long> ba = pmap.valueAt(ip);
+                for (i=ba.size()-1; i>=0; i--) {
+                    boolean remove = false;
+                    final int entUid = ba.keyAt(i);
+                    if (name != null) {
+                        if (userId == UserHandle.USER_ALL) {
+                            if (UserHandle.getAppId(entUid) == appId) {
+                                remove = true;
+                            }
+                        } else {
+                            if (entUid == UserHandle.getUid(userId, appId)) {
+                                remove = true;
+                            }
+                        }
+                    } else if (UserHandle.getUserId(entUid) == userId) {
+                        remove = true;
+                    }
+                    if (remove) {
+                        ba.removeAt(i);
+                    }
+                }
+                if (ba.size() == 0) {
+                    pmap.removeAt(ip);
+                }
+            }
+        }
+
+        boolean didSomething = killPackageProcessesLocked(name, appId, userId,
+                -100, callerWillRestart, true, doit, evenPersistent,
+                name == null ? ("stop user " + userId) : ("stop " + name));
+
+        if (mStackSupervisor.forceStopPackageLocked(name, doit, evenPersistent, userId)) {
+            if (!doit) {
+                return true;
+            }
+            didSomething = true;
+        }
+
+        if (mServices.forceStopLocked(name, userId, evenPersistent, doit)) {
+            if (!doit) {
+                return true;
+            }
+            didSomething = true;
+        }
+
+        if (name == null) {
+            // Remove all sticky broadcasts from this user.
+            mStickyBroadcasts.remove(userId);
+        }
+
+        ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
+        if (mProviderMap.collectForceStopProviders(name, appId, doit, evenPersistent,
+                userId, providers)) {
+            if (!doit) {
+                return true;
+            }
+            didSomething = true;
+        }
+        N = providers.size();
+        for (i=0; i<N; i++) {
+            removeDyingProviderLocked(null, providers.get(i), true);
+        }
+
+        // Remove transient permissions granted from/to this package/user
+        removeUriPermissionsForPackageLocked(name, userId, false);
+
+        if (name == null) {
+            // Remove pending intents.  For now we only do this when force
+            // stopping users, because we have some problems when doing this
+            // for packages -- app widgets are not currently cleaned up for
+            // such packages, so they can be left with bad pending intents.
+            if (mIntentSenderRecords.size() > 0) {
+                Iterator<WeakReference<PendingIntentRecord>> it
+                        = mIntentSenderRecords.values().iterator();
+                while (it.hasNext()) {
+                    WeakReference<PendingIntentRecord> wpir = it.next();
+                    if (wpir == null) {
+                        it.remove();
+                        continue;
+                    }
+                    PendingIntentRecord pir = wpir.get();
+                    if (pir == null) {
+                        it.remove();
+                        continue;
+                    }
+                    if (name == null) {
+                        // Stopping user, remove all objects for the user.
+                        if (pir.key.userId != userId) {
+                            // Not the same user, skip it.
+                            continue;
+                        }
+                    } else {
+                        if (UserHandle.getAppId(pir.uid) != appId) {
+                            // Different app id, skip it.
+                            continue;
+                        }
+                        if (userId != UserHandle.USER_ALL && pir.key.userId != userId) {
+                            // Different user, skip it.
+                            continue;
+                        }
+                        if (!pir.key.packageName.equals(name)) {
+                            // Different package, skip it.
+                            continue;
+                        }
+                    }
+                    if (!doit) {
+                        return true;
+                    }
+                    didSomething = true;
+                    it.remove();
+                    pir.canceled = true;
+                    if (pir.key.activity != null) {
+                        pir.key.activity.pendingResults.remove(pir.ref);
+                    }
+                }
+            }
+        }
+
+        if (doit) {
+            if (purgeCache && name != null) {
+                AttributeCache ac = AttributeCache.instance();
+                if (ac != null) {
+                    ac.removePackage(name);
+                }
+            }
+            if (mBooted) {
+                mStackSupervisor.resumeTopActivitiesLocked();
+                mStackSupervisor.scheduleIdleLocked();
+            }
+        }
+
+        return didSomething;
+    }
+
+    private final boolean removeProcessLocked(ProcessRecord app,
+            boolean callerWillRestart, boolean allowRestart, String reason) {
+        final String name = app.processName;
+        final int uid = app.uid;
+        if (DEBUG_PROCESSES) Slog.d(
+            TAG, "Force removing proc " + app.toShortString() + " (" + name
+            + "/" + uid + ")");
+
+        mProcessNames.remove(name, uid);
+        mIsolatedProcesses.remove(app.uid);
+        if (mHeavyWeightProcess == app) {
+            mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
+                    mHeavyWeightProcess.userId, 0));
+            mHeavyWeightProcess = null;
+        }
+        boolean needRestart = false;
+        if (app.pid > 0 && app.pid != MY_PID) {
+            int pid = app.pid;
+            synchronized (mPidsSelfLocked) {
+                mPidsSelfLocked.remove(pid);
+                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            }
+            killUnneededProcessLocked(app, reason);
+            handleAppDiedLocked(app, true, allowRestart);
+            removeLruProcessLocked(app);
+
+            if (app.persistent && !app.isolated) {
+                if (!callerWillRestart) {
+                    addAppLocked(app.info, false);
+                } else {
+                    needRestart = true;
+                }
+            }
+        } else {
+            mRemovedProcesses.add(app);
+        }
+
+        return needRestart;
+    }
+
+    private final void processStartTimedOutLocked(ProcessRecord app) {
+        final int pid = app.pid;
+        boolean gone = false;
+        synchronized (mPidsSelfLocked) {
+            ProcessRecord knownApp = mPidsSelfLocked.get(pid);
+            if (knownApp != null && knownApp.thread == null) {
+                mPidsSelfLocked.remove(pid);
+                gone = true;
+            }
+        }
+
+        if (gone) {
+            Slog.w(TAG, "Process " + app + " failed to attach");
+            EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, app.userId,
+                    pid, app.uid, app.processName);
+            mProcessNames.remove(app.processName, app.uid);
+            mIsolatedProcesses.remove(app.uid);
+            if (mHeavyWeightProcess == app) {
+                mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
+                        mHeavyWeightProcess.userId, 0));
+                mHeavyWeightProcess = null;
+            }
+            // Take care of any launching providers waiting for this process.
+            checkAppInLaunchingProvidersLocked(app, true);
+            // Take care of any services that are waiting for the process.
+            mServices.processStartTimedOutLocked(app);
+            killUnneededProcessLocked(app, "start timeout");
+            if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
+                Slog.w(TAG, "Unattached app died before backup, skipping");
+                try {
+                    IBackupManager bm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    bm.agentDisconnected(app.info.packageName);
+                } catch (RemoteException e) {
+                    // Can't happen; the backup manager is local
+                }
+            }
+            if (isPendingBroadcastProcessLocked(pid)) {
+                Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping");
+                skipPendingBroadcastLocked(pid);
+            }
+        } else {
+            Slog.w(TAG, "Spurious process start timeout - pid not known for " + app);
+        }
+    }
+
+    private final boolean attachApplicationLocked(IApplicationThread thread,
+            int pid) {
+
+        // Find the application record that is being attached...  either via
+        // the pid if we are running in multiple processes, or just pull the
+        // next app record if we are emulating process with anonymous threads.
+        ProcessRecord app;
+        if (pid != MY_PID && pid >= 0) {
+            synchronized (mPidsSelfLocked) {
+                app = mPidsSelfLocked.get(pid);
+            }
+        } else {
+            app = null;
+        }
+
+        if (app == null) {
+            Slog.w(TAG, "No pending application record for pid " + pid
+                    + " (IApplicationThread " + thread + "); dropping process");
+            EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
+            if (pid > 0 && pid != MY_PID) {
+                Process.killProcessQuiet(pid);
+            } else {
+                try {
+                    thread.scheduleExit();
+                } catch (Exception e) {
+                    // Ignore exceptions.
+                }
+            }
+            return false;
+        }
+
+        // If this application record is still attached to a previous
+        // process, clean it up now.
+        if (app.thread != null) {
+            handleAppDiedLocked(app, true, true);
+        }
+
+        // Tell the process all about itself.
+
+        if (localLOGV) Slog.v(
+                TAG, "Binding process pid " + pid + " to record " + app);
+
+        final String processName = app.processName;
+        try {
+            AppDeathRecipient adr = new AppDeathRecipient(
+                    app, pid, thread);
+            thread.asBinder().linkToDeath(adr, 0);
+            app.deathRecipient = adr;
+        } catch (RemoteException e) {
+            app.resetPackageList(mProcessStats);
+            startProcessLocked(app, "link fail", processName);
+            return false;
+        }
+
+        EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName);
+
+        app.makeActive(thread, mProcessStats);
+        app.curAdj = app.setAdj = -100;
+        app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
+        app.forcingToForeground = null;
+        app.foregroundServices = false;
+        app.hasShownUi = false;
+        app.debugging = false;
+        app.cached = false;
+
+        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
+        boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
+        List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
+
+        if (!normalMode) {
+            Slog.i(TAG, "Launching preboot mode app: " + app);
+        }
+
+        if (localLOGV) Slog.v(
+            TAG, "New app record " + app
+            + " thread=" + thread.asBinder() + " pid=" + pid);
+        try {
+            int testMode = IApplicationThread.DEBUG_OFF;
+            if (mDebugApp != null && mDebugApp.equals(processName)) {
+                testMode = mWaitForDebugger
+                    ? IApplicationThread.DEBUG_WAIT
+                    : IApplicationThread.DEBUG_ON;
+                app.debugging = true;
+                if (mDebugTransient) {
+                    mDebugApp = mOrigDebugApp;
+                    mWaitForDebugger = mOrigWaitForDebugger;
+                }
+            }
+            String profileFile = app.instrumentationProfileFile;
+            ParcelFileDescriptor profileFd = null;
+            boolean profileAutoStop = false;
+            if (mProfileApp != null && mProfileApp.equals(processName)) {
+                mProfileProc = app;
+                profileFile = mProfileFile;
+                profileFd = mProfileFd;
+                profileAutoStop = mAutoStopProfiler;
+            }
+            boolean enableOpenGlTrace = false;
+            if (mOpenGlTraceApp != null && mOpenGlTraceApp.equals(processName)) {
+                enableOpenGlTrace = true;
+                mOpenGlTraceApp = null;
+            }
+
+            // If the app is being launched for restore or full backup, set it up specially
+            boolean isRestrictedBackupMode = false;
+            if (mBackupTarget != null && mBackupAppName.equals(processName)) {
+                isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE)
+                        || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL)
+                        || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL);
+            }
+
+            ensurePackageDexOpt(app.instrumentationInfo != null
+                    ? app.instrumentationInfo.packageName
+                    : app.info.packageName);
+            if (app.instrumentationClass != null) {
+                ensurePackageDexOpt(app.instrumentationClass.getPackageName());
+            }
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Binding proc "
+                    + processName + " with config " + mConfiguration);
+            ApplicationInfo appInfo = app.instrumentationInfo != null
+                    ? app.instrumentationInfo : app.info;
+            app.compat = compatibilityInfoForPackageLocked(appInfo);
+            if (profileFd != null) {
+                profileFd = profileFd.dup();
+            }
+            thread.bindApplication(processName, appInfo, providers,
+                    app.instrumentationClass, profileFile, profileFd, profileAutoStop,
+                    app.instrumentationArguments, app.instrumentationWatcher,
+                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
+                    isRestrictedBackupMode || !normalMode, app.persistent,
+                    new Configuration(mConfiguration), app.compat, getCommonServicesLocked(),
+                    mCoreSettingsObserver.getCoreSettingsLocked());
+            updateLruProcessLocked(app, false, null);
+            app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
+        } catch (Exception e) {
+            // todo: Yikes!  What should we do?  For now we will try to
+            // start another process, but that could easily get us in
+            // an infinite loop of restarting processes...
+            Slog.w(TAG, "Exception thrown during bind!", e);
+
+            app.resetPackageList(mProcessStats);
+            app.unlinkDeathRecipient();
+            startProcessLocked(app, "bind fail", processName);
+            return false;
+        }
+
+        // Remove this record from the list of starting applications.
+        mPersistentStartingProcesses.remove(app);
+        if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG,
+                "Attach application locked removing on hold: " + app);
+        mProcessesOnHold.remove(app);
+
+        boolean badApp = false;
+        boolean didSomething = false;
+
+        // See if the top visible activity is waiting to run in this process...
+        if (normalMode) {
+            try {
+                if (mStackSupervisor.attachApplicationLocked(app)) {
+                    didSomething = true;
+                }
+            } catch (Exception e) {
+                badApp = true;
+            }
+        }
+
+        // Find any services that should be running in this process...
+        if (!badApp) {
+            try {
+                didSomething |= mServices.attachApplicationLocked(app, processName);
+            } catch (Exception e) {
+                badApp = true;
+            }
+        }
+
+        // Check if a next-broadcast receiver is in this process...
+        if (!badApp && isPendingBroadcastProcessLocked(pid)) {
+            try {
+                didSomething |= sendPendingBroadcastsLocked(app);
+            } catch (Exception e) {
+                // If the app died trying to launch the receiver we declare it 'bad'
+                badApp = true;
+            }
+        }
+
+        // Check whether the next backup agent is in this process...
+        if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.uid) {
+            if (DEBUG_BACKUP) Slog.v(TAG, "New app is backup target, launching agent for " + app);
+            ensurePackageDexOpt(mBackupTarget.appInfo.packageName);
+            try {
+                thread.scheduleCreateBackupAgent(mBackupTarget.appInfo,
+                        compatibilityInfoForPackageLocked(mBackupTarget.appInfo),
+                        mBackupTarget.backupMode);
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception scheduling backup agent creation: ");
+                e.printStackTrace();
+            }
+        }
+
+        if (badApp) {
+            // todo: Also need to kill application to deal with all
+            // kinds of exceptions.
+            handleAppDiedLocked(app, false, true);
+            return false;
+        }
+
+        if (!didSomething) {
+            updateOomAdjLocked();
+        }
+
+        return true;
+    }
+
+    @Override
+    public final void attachApplication(IApplicationThread thread) {
+        synchronized (this) {
+            int callingPid = Binder.getCallingPid();
+            final long origId = Binder.clearCallingIdentity();
+            attachApplicationLocked(thread, callingPid);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized (this) {
+            ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                ActivityRecord r =
+                        mStackSupervisor.activityIdleInternalLocked(token, false, config);
+                if (stopProfiling) {
+                    if ((mProfileProc == r.app) && (mProfileFd != null)) {
+                        try {
+                            mProfileFd.close();
+                        } catch (IOException e) {
+                        }
+                        clearProfilerLocked();
+                    }
+                }
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    void enableScreenAfterBoot() {
+        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,
+                SystemClock.uptimeMillis());
+        mWindowManager.enableScreenAfterBoot();
+
+        synchronized (this) {
+            updateEventDispatchingLocked();
+        }
+    }
+
+    @Override
+    public void showBootMessage(final CharSequence msg, final boolean always) {
+        enforceNotIsolatedCaller("showBootMessage");
+        mWindowManager.showBootMessage(msg, always);
+    }
+
+    @Override
+    public void dismissKeyguardOnNextActivity() {
+        enforceNotIsolatedCaller("dismissKeyguardOnNextActivity");
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                if (DEBUG_LOCKSCREEN) logLockScreen("");
+                if (mLockScreenShown) {
+                    mLockScreenShown = false;
+                    comeOutOfSleepIfNeededLocked();
+                }
+                mStackSupervisor.setDismissKeyguard(true);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    final void finishBooting() {
+        IntentFilter pkgFilter = new IntentFilter();
+        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        pkgFilter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                if (pkgs != null) {
+                    for (String pkg : pkgs) {
+                        synchronized (ActivityManagerService.this) {
+                            if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0,
+                                    "finished booting")) {
+                                setResultCode(Activity.RESULT_OK);
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+        }, pkgFilter);
+
+        synchronized (this) {
+            // Ensure that any processes we had put on hold are now started
+            // up.
+            final int NP = mProcessesOnHold.size();
+            if (NP > 0) {
+                ArrayList<ProcessRecord> procs =
+                    new ArrayList<ProcessRecord>(mProcessesOnHold);
+                for (int ip=0; ip<NP; ip++) {
+                    if (DEBUG_PROCESSES) Slog.v(TAG, "Starting process on hold: "
+                            + procs.get(ip));
+                    startProcessLocked(procs.get(ip), "on-hold", null);
+                }
+            }
+            
+            if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+                // Start looking for apps that are abusing wake locks.
+                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+                // Tell anyone interested that we are done booting!
+                SystemProperties.set("sys.boot_completed", "1");
+                SystemProperties.set("dev.bootcomplete", "1");
+                for (int i=0; i<mStartedUsers.size(); i++) {
+                    UserStartedState uss = mStartedUsers.valueAt(i);
+                    if (uss.mState == UserStartedState.STATE_BOOTING) {
+                        uss.mState = UserStartedState.STATE_RUNNING;
+                        final int userId = mStartedUsers.keyAt(i);
+                        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                        intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                        intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                        broadcastIntentLocked(null, null, intent, null,
+                                new IIntentReceiver.Stub() {
+                                    @Override
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        synchronized (ActivityManagerService.this) {
+                                            requestPssAllProcsLocked(SystemClock.uptimeMillis(),
+                                                    true, false);
+                                        }
+                                    }
+                                },
+                                0, null, null,
+                                android.Manifest.permission.RECEIVE_BOOT_COMPLETED,
+                                AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID,
+                                userId);
+                    }
+                }
+            }
+        }
+    }
+    
+    final void ensureBootCompleted() {
+        boolean booting;
+        boolean enableScreen;
+        synchronized (this) {
+            booting = mBooting;
+            mBooting = false;
+            enableScreen = !mBooted;
+            mBooted = true;
+        }
+        
+        if (booting) {
+            finishBooting();
+        }
+
+        if (enableScreen) {
+            enableScreenAfterBoot();
+        }
+    }
+
+    @Override
+    public final void activityResumed(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized(this) {
+            ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                ActivityRecord.activityResumedLocked(token);
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    @Override
+    public final void activityPaused(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized(this) {
+            ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                stack.activityPausedLocked(token, false);
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    @Override
+    public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail,
+            CharSequence description) {
+        if (localLOGV) Slog.v(
+            TAG, "Activity stopped: token=" + token);
+
+        // Refuse possible leaked file descriptors
+        if (icicle != null && icicle.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        ActivityRecord r = null;
+
+        final long origId = Binder.clearCallingIdentity();
+
+        synchronized (this) {
+            r = ActivityRecord.isInStackLocked(token);
+            if (r != null) {
+                r.task.stack.activityStoppedLocked(r, icicle, thumbnail, description);
+            }
+        }
+
+        if (r != null) {
+            sendPendingThumbnail(r, null, null, null, false);
+        }
+
+        trimApplications();
+
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    @Override
+    public final void activityDestroyed(IBinder token) {
+        if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token);
+        synchronized (this) {
+            ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                stack.activityDestroyedLocked(token);
+            }
+        }
+    }
+    
+    @Override
+    public String getCallingPackage(IBinder token) {
+        synchronized (this) {
+            ActivityRecord r = getCallingRecordLocked(token);
+            return r != null ? r.info.packageName : null;
+        }
+    }
+
+    @Override
+    public ComponentName getCallingActivity(IBinder token) {
+        synchronized (this) {
+            ActivityRecord r = getCallingRecordLocked(token);
+            return r != null ? r.intent.getComponent() : null;
+        }
+    }
+
+    private ActivityRecord getCallingRecordLocked(IBinder token) {
+        ActivityRecord r = ActivityRecord.isInStackLocked(token);
+        if (r == null) {
+            return null;
+        }
+        return r.resultTo;
+    }
+
+    @Override
+    public ComponentName getActivityClassForToken(IBinder token) {
+        synchronized(this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return null;
+            }
+            return r.intent.getComponent();
+        }
+    }
+
+    @Override
+    public String getPackageForToken(IBinder token) {
+        synchronized(this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return null;
+            }
+            return r.packageName;
+        }
+    }
+
+    @Override
+    public IIntentSender getIntentSender(int type,
+            String packageName, IBinder token, String resultWho,
+            int requestCode, Intent[] intents, String[] resolvedTypes,
+            int flags, Bundle options, int userId) {
+        enforceNotIsolatedCaller("getIntentSender");
+        // Refuse possible leaked file descriptors
+        if (intents != null) {
+            if (intents.length < 1) {
+                throw new IllegalArgumentException("Intents array length must be >= 1");
+            }
+            for (int i=0; i<intents.length; i++) {
+                Intent intent = intents[i];
+                if (intent != null) {
+                    if (intent.hasFileDescriptors()) {
+                        throw new IllegalArgumentException("File descriptors passed in Intent");
+                    }
+                    if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
+                            (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+                        throw new IllegalArgumentException(
+                                "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+                    }
+                    intents[i] = new Intent(intent);
+                }
+            }
+            if (resolvedTypes != null && resolvedTypes.length != intents.length) {
+                throw new IllegalArgumentException(
+                        "Intent array length does not match resolvedTypes length");
+            }
+        }
+        if (options != null) {
+            if (options.hasFileDescriptors()) {
+                throw new IllegalArgumentException("File descriptors passed in options");
+            }
+        }
+        
+        synchronized(this) {
+            int callingUid = Binder.getCallingUid();
+            int origUserId = userId;
+            userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
+                    type == ActivityManager.INTENT_SENDER_BROADCAST, false,
+                    "getIntentSender", null);
+            if (origUserId == UserHandle.USER_CURRENT) {
+                // We don't want to evaluate this until the pending intent is
+                // actually executed.  However, we do want to always do the
+                // security checking for it above.
+                userId = UserHandle.USER_CURRENT;
+            }
+            try {
+                if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+                    int uid = AppGlobals.getPackageManager()
+                            .getPackageUid(packageName, UserHandle.getUserId(callingUid));
+                    if (!UserHandle.isSameApp(callingUid, uid)) {
+                        String msg = "Permission Denial: getIntentSender() from pid="
+                            + Binder.getCallingPid()
+                            + ", uid=" + Binder.getCallingUid()
+                            + ", (need uid=" + uid + ")"
+                            + " is not allowed to send as package " + packageName;
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                }
+
+                return getIntentSenderLocked(type, packageName, callingUid, userId,
+                        token, resultWho, requestCode, intents, resolvedTypes, flags, options);
+                
+            } catch (RemoteException e) {
+                throw new SecurityException(e);
+            }
+        }
+    }
+
+    IIntentSender getIntentSenderLocked(int type, String packageName,
+            int callingUid, int userId, IBinder token, String resultWho,
+            int requestCode, Intent[] intents, String[] resolvedTypes, int flags,
+            Bundle options) {
+        if (DEBUG_MU)
+            Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
+        ActivityRecord activity = null;
+        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+            activity = ActivityRecord.isInStackLocked(token);
+            if (activity == null) {
+                return null;
+            }
+            if (activity.finishing) {
+                return null;
+            }
+        }
+
+        final boolean noCreate = (flags&PendingIntent.FLAG_NO_CREATE) != 0;
+        final boolean cancelCurrent = (flags&PendingIntent.FLAG_CANCEL_CURRENT) != 0;
+        final boolean updateCurrent = (flags&PendingIntent.FLAG_UPDATE_CURRENT) != 0;
+        flags &= ~(PendingIntent.FLAG_NO_CREATE|PendingIntent.FLAG_CANCEL_CURRENT
+                |PendingIntent.FLAG_UPDATE_CURRENT);
+
+        PendingIntentRecord.Key key = new PendingIntentRecord.Key(
+                type, packageName, activity, resultWho,
+                requestCode, intents, resolvedTypes, flags, options, userId);
+        WeakReference<PendingIntentRecord> ref;
+        ref = mIntentSenderRecords.get(key);
+        PendingIntentRecord rec = ref != null ? ref.get() : null;
+        if (rec != null) {
+            if (!cancelCurrent) {
+                if (updateCurrent) {
+                    if (rec.key.requestIntent != null) {
+                        rec.key.requestIntent.replaceExtras(intents != null ?
+                                intents[intents.length - 1] : null);
+                    }
+                    if (intents != null) {
+                        intents[intents.length-1] = rec.key.requestIntent;
+                        rec.key.allIntents = intents;
+                        rec.key.allResolvedTypes = resolvedTypes;
+                    } else {
+                        rec.key.allIntents = null;
+                        rec.key.allResolvedTypes = null;
+                    }
+                }
+                return rec;
+            }
+            rec.canceled = true;
+            mIntentSenderRecords.remove(key);
+        }
+        if (noCreate) {
+            return rec;
+        }
+        rec = new PendingIntentRecord(this, key, callingUid);
+        mIntentSenderRecords.put(key, rec.ref);
+        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
+            if (activity.pendingResults == null) {
+                activity.pendingResults
+                        = new HashSet<WeakReference<PendingIntentRecord>>();
+            }
+            activity.pendingResults.add(rec.ref);
+        }
+        return rec;
+    }
+
+    @Override
+    public void cancelIntentSender(IIntentSender sender) {
+        if (!(sender instanceof PendingIntentRecord)) {
+            return;
+        }
+        synchronized(this) {
+            PendingIntentRecord rec = (PendingIntentRecord)sender;
+            try {
+                int uid = AppGlobals.getPackageManager()
+                        .getPackageUid(rec.key.packageName, UserHandle.getCallingUserId());
+                if (!UserHandle.isSameApp(uid, Binder.getCallingUid())) {
+                    String msg = "Permission Denial: cancelIntentSender() from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + " is not allowed to cancel packges "
+                        + rec.key.packageName;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+            } catch (RemoteException e) {
+                throw new SecurityException(e);
+            }
+            cancelIntentSenderLocked(rec, true);
+        }
+    }
+
+    void cancelIntentSenderLocked(PendingIntentRecord rec, boolean cleanActivity) {
+        rec.canceled = true;
+        mIntentSenderRecords.remove(rec.key);
+        if (cleanActivity && rec.key.activity != null) {
+            rec.key.activity.pendingResults.remove(rec.ref);
+        }
+    }
+
+    @Override
+    public String getPackageForIntentSender(IIntentSender pendingResult) {
+        if (!(pendingResult instanceof PendingIntentRecord)) {
+            return null;
+        }
+        try {
+            PendingIntentRecord res = (PendingIntentRecord)pendingResult;
+            return res.key.packageName;
+        } catch (ClassCastException e) {
+        }
+        return null;
+    }
+
+    @Override
+    public int getUidForIntentSender(IIntentSender sender) {
+        if (sender instanceof PendingIntentRecord) {
+            try {
+                PendingIntentRecord res = (PendingIntentRecord)sender;
+                return res.uid;
+            } catch (ClassCastException e) {
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public boolean isIntentSenderTargetedToPackage(IIntentSender pendingResult) {
+        if (!(pendingResult instanceof PendingIntentRecord)) {
+            return false;
+        }
+        try {
+            PendingIntentRecord res = (PendingIntentRecord)pendingResult;
+            if (res.key.allIntents == null) {
+                return false;
+            }
+            for (int i=0; i<res.key.allIntents.length; i++) {
+                Intent intent = res.key.allIntents[i];
+                if (intent.getPackage() != null && intent.getComponent() != null) {
+                    return false;
+                }
+            }
+            return true;
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isIntentSenderAnActivity(IIntentSender pendingResult) {
+        if (!(pendingResult instanceof PendingIntentRecord)) {
+            return false;
+        }
+        try {
+            PendingIntentRecord res = (PendingIntentRecord)pendingResult;
+            if (res.key.type == ActivityManager.INTENT_SENDER_ACTIVITY) {
+                return true;
+            }
+            return false;
+        } catch (ClassCastException e) {
+        }
+        return false;
+    }
+
+    @Override
+    public Intent getIntentForIntentSender(IIntentSender pendingResult) {
+        if (!(pendingResult instanceof PendingIntentRecord)) {
+            return null;
+        }
+        try {
+            PendingIntentRecord res = (PendingIntentRecord)pendingResult;
+            return res.key.requestIntent != null ? new Intent(res.key.requestIntent) : null;
+        } catch (ClassCastException e) {
+        }
+        return null;
+    }
+
+    @Override
+    public void setProcessLimit(int max) {
+        enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
+                "setProcessLimit()");
+        synchronized (this) {
+            mProcessLimit = max < 0 ? ProcessList.MAX_CACHED_APPS : max;
+            mProcessLimitOverride = max;
+        }
+        trimApplications();
+    }
+
+    @Override
+    public int getProcessLimit() {
+        synchronized (this) {
+            return mProcessLimitOverride;
+        }
+    }
+
+    void foregroundTokenDied(ForegroundToken token) {
+        synchronized (ActivityManagerService.this) {
+            synchronized (mPidsSelfLocked) {
+                ForegroundToken cur
+                    = mForegroundProcesses.get(token.pid);
+                if (cur != token) {
+                    return;
+                }
+                mForegroundProcesses.remove(token.pid);
+                ProcessRecord pr = mPidsSelfLocked.get(token.pid);
+                if (pr == null) {
+                    return;
+                }
+                pr.forcingToForeground = null;
+                pr.foregroundServices = false;
+            }
+            updateOomAdjLocked();
+        }
+    }
+
+    @Override
+    public void setProcessForeground(IBinder token, int pid, boolean isForeground) {
+        enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
+                "setProcessForeground()");
+        synchronized(this) {
+            boolean changed = false;
+            
+            synchronized (mPidsSelfLocked) {
+                ProcessRecord pr = mPidsSelfLocked.get(pid);
+                if (pr == null && isForeground) {
+                    Slog.w(TAG, "setProcessForeground called on unknown pid: " + pid);
+                    return;
+                }
+                ForegroundToken oldToken = mForegroundProcesses.get(pid);
+                if (oldToken != null) {
+                    oldToken.token.unlinkToDeath(oldToken, 0);
+                    mForegroundProcesses.remove(pid);
+                    if (pr != null) {
+                        pr.forcingToForeground = null;
+                    }
+                    changed = true;
+                }
+                if (isForeground && token != null) {
+                    ForegroundToken newToken = new ForegroundToken() {
+                        @Override
+                        public void binderDied() {
+                            foregroundTokenDied(this);
+                        }
+                    };
+                    newToken.pid = pid;
+                    newToken.token = token;
+                    try {
+                        token.linkToDeath(newToken, 0);
+                        mForegroundProcesses.put(pid, newToken);
+                        pr.forcingToForeground = token;
+                        changed = true;
+                    } catch (RemoteException e) {
+                        // If the process died while doing this, we will later
+                        // do the cleanup with the process death link.
+                    }
+                }
+            }
+            
+            if (changed) {
+                updateOomAdjLocked();
+            }
+        }
+    }
+    
+    // =========================================================
+    // PERMISSIONS
+    // =========================================================
+
+    static class PermissionController extends IPermissionController.Stub {
+        ActivityManagerService mActivityManagerService;
+        PermissionController(ActivityManagerService activityManagerService) {
+            mActivityManagerService = activityManagerService;
+        }
+
+        @Override
+        public boolean checkPermission(String permission, int pid, int uid) {
+            return mActivityManagerService.checkPermission(permission, pid,
+                    uid) == PackageManager.PERMISSION_GRANTED;
+        }
+    }
+
+    class IntentFirewallInterface implements IntentFirewall.AMSInterface {
+        @Override
+        public int checkComponentPermission(String permission, int pid, int uid,
+                int owningUid, boolean exported) {
+            return ActivityManagerService.this.checkComponentPermission(permission, pid, uid,
+                    owningUid, exported);
+        }
+
+        @Override
+        public Object getAMSLock() {
+            return ActivityManagerService.this;
+        }
+    }
+
+    /**
+     * This can be called with or without the global lock held.
+     */
+    int checkComponentPermission(String permission, int pid, int uid,
+            int owningUid, boolean exported) {
+        // We might be performing an operation on behalf of an indirect binder
+        // invocation, e.g. via {@link #openContentUri}.  Check and adjust the
+        // client identity accordingly before proceeding.
+        Identity tlsIdentity = sCallerIdentity.get();
+        if (tlsIdentity != null) {
+            Slog.d(TAG, "checkComponentPermission() adjusting {pid,uid} to {"
+                    + tlsIdentity.pid + "," + tlsIdentity.uid + "}");
+            uid = tlsIdentity.uid;
+            pid = tlsIdentity.pid;
+        }
+
+        if (pid == MY_PID) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        return ActivityManager.checkComponentPermission(permission, uid,
+                owningUid, exported);
+    }
+
+    /**
+     * As the only public entry point for permissions checking, this method
+     * can enforce the semantic that requesting a check on a null global
+     * permission is automatically denied.  (Internally a null permission
+     * string is used when calling {@link #checkComponentPermission} in cases
+     * when only uid-based security is needed.)
+     * 
+     * This can be called with or without the global lock held.
+     */
+    @Override
+    public int checkPermission(String permission, int pid, int uid) {
+        if (permission == null) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+        return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
+    }
+
+    /**
+     * Binder IPC calls go through the public entry point.
+     * This can be called with or without the global lock held.
+     */
+    int checkCallingPermission(String permission) {
+        return checkPermission(permission,
+                Binder.getCallingPid(),
+                UserHandle.getAppId(Binder.getCallingUid()));
+    }
+
+    /**
+     * This can be called with or without the global lock held.
+     */
+    void enforceCallingPermission(String permission, String func) {
+        if (checkCallingPermission(permission)
+                == PackageManager.PERMISSION_GRANTED) {
+            return;
+        }
+
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires " + permission;
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
+    }
+
+    /**
+     * Determine if UID is holding permissions required to access {@link Uri} in
+     * the given {@link ProviderInfo}. Final permission checking is always done
+     * in {@link ContentProvider}.
+     */
+    private final boolean checkHoldingPermissionsLocked(
+            IPackageManager pm, ProviderInfo pi, Uri uri, int uid, int modeFlags) {
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                "checkHoldingPermissionsLocked: uri=" + uri + " uid=" + uid);
+
+        if (pi.applicationInfo.uid == uid) {
+            return true;
+        } else if (!pi.exported) {
+            return false;
+        }
+
+        boolean readMet = (modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0;
+        boolean writeMet = (modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0;
+        try {
+            // check if target holds top-level <provider> permissions
+            if (!readMet && pi.readPermission != null
+                    && (pm.checkUidPermission(pi.readPermission, uid) == PERMISSION_GRANTED)) {
+                readMet = true;
+            }
+            if (!writeMet && pi.writePermission != null
+                    && (pm.checkUidPermission(pi.writePermission, uid) == PERMISSION_GRANTED)) {
+                writeMet = true;
+            }
+
+            // track if unprotected read/write is allowed; any denied
+            // <path-permission> below removes this ability
+            boolean allowDefaultRead = pi.readPermission == null;
+            boolean allowDefaultWrite = pi.writePermission == null;
+
+            // check if target holds any <path-permission> that match uri
+            final PathPermission[] pps = pi.pathPermissions;
+            if (pps != null) {
+                final String path = uri.getPath();
+                int i = pps.length;
+                while (i > 0 && (!readMet || !writeMet)) {
+                    i--;
+                    PathPermission pp = pps[i];
+                    if (pp.match(path)) {
+                        if (!readMet) {
+                            final String pprperm = pp.getReadPermission();
+                            if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking read perm for "
+                                    + pprperm + " for " + pp.getPath()
+                                    + ": match=" + pp.match(path)
+                                    + " check=" + pm.checkUidPermission(pprperm, uid));
+                            if (pprperm != null) {
+                                if (pm.checkUidPermission(pprperm, uid) == PERMISSION_GRANTED) {
+                                    readMet = true;
+                                } else {
+                                    allowDefaultRead = false;
+                                }
+                            }
+                        }
+                        if (!writeMet) {
+                            final String ppwperm = pp.getWritePermission();
+                            if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Checking write perm "
+                                    + ppwperm + " for " + pp.getPath()
+                                    + ": match=" + pp.match(path)
+                                    + " check=" + pm.checkUidPermission(ppwperm, uid));
+                            if (ppwperm != null) {
+                                if (pm.checkUidPermission(ppwperm, uid) == PERMISSION_GRANTED) {
+                                    writeMet = true;
+                                } else {
+                                    allowDefaultWrite = false;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            // grant unprotected <provider> read/write, if not blocked by
+            // <path-permission> above
+            if (allowDefaultRead) readMet = true;
+            if (allowDefaultWrite) writeMet = true;
+
+        } catch (RemoteException e) {
+            return false;
+        }
+
+        return readMet && writeMet;
+    }
+
+    private ProviderInfo getProviderInfoLocked(String authority, int userHandle) {
+        ProviderInfo pi = null;
+        ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle);
+        if (cpr != null) {
+            pi = cpr.info;
+        } else {
+            try {
+                pi = AppGlobals.getPackageManager().resolveContentProvider(
+                        authority, PackageManager.GET_URI_PERMISSION_PATTERNS, userHandle);
+            } catch (RemoteException ex) {
+            }
+        }
+        return pi;
+    }
+
+    private UriPermission findUriPermissionLocked(int targetUid, Uri uri) {
+        ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+        if (targetUris != null) {
+            return targetUris.get(uri);
+        } else {
+            return null;
+        }
+    }
+
+    private UriPermission findOrCreateUriPermissionLocked(
+            String sourcePkg, String targetPkg, int targetUid, Uri uri) {
+        ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid);
+        if (targetUris == null) {
+            targetUris = Maps.newArrayMap();
+            mGrantedUriPermissions.put(targetUid, targetUris);
+        }
+
+        UriPermission perm = targetUris.get(uri);
+        if (perm == null) {
+            perm = new UriPermission(sourcePkg, targetPkg, targetUid, uri);
+            targetUris.put(uri, perm);
+        }
+
+        return perm;
+    }
+
+    private final boolean checkUriPermissionLocked(
+            Uri uri, int uid, int modeFlags, int minStrength) {
+        // Root gets to do everything.
+        if (uid == 0) {
+            return true;
+        }
+        ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+        if (perms == null) return false;
+        UriPermission perm = perms.get(uri);
+        if (perm == null) return false;
+        return perm.getStrength(modeFlags) >= minStrength;
+    }
+
+    @Override
+    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+        enforceNotIsolatedCaller("checkUriPermission");
+
+        // Another redirected-binder-call permissions check as in
+        // {@link checkComponentPermission}.
+        Identity tlsIdentity = sCallerIdentity.get();
+        if (tlsIdentity != null) {
+            uid = tlsIdentity.uid;
+            pid = tlsIdentity.pid;
+        }
+
+        // Our own process gets to do everything.
+        if (pid == MY_PID) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        synchronized(this) {
+            return checkUriPermissionLocked(uri, uid, modeFlags, UriPermission.STRENGTH_OWNED)
+                    ? PackageManager.PERMISSION_GRANTED
+                    : PackageManager.PERMISSION_DENIED;
+        }
+    }
+
+    /**
+     * Check if the targetPkg can be granted permission to access uri by
+     * the callingUid using the given modeFlags.  Throws a security exception
+     * if callingUid is not allowed to do this.  Returns the uid of the target
+     * if the URI permission grant should be performed; returns -1 if it is not
+     * needed (for example targetPkg already has permission to access the URI).
+     * If you already know the uid of the target, you can supply it in
+     * lastTargetUid else set that to -1.
+     */
+    int checkGrantUriPermissionLocked(int callingUid, String targetPkg,
+            Uri uri, int modeFlags, int lastTargetUid) {
+        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        if (modeFlags == 0) {
+            return -1;
+        }
+
+        if (targetPkg != null) {
+            if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                    "Checking grant " + targetPkg + " permission to " + uri);
+        }
+        
+        final IPackageManager pm = AppGlobals.getPackageManager();
+
+        // If this is not a content: uri, we can't do anything with it.
+        if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
+            if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
+                    "Can't grant URI permission for non-content URI: " + uri);
+            return -1;
+        }
+
+        final String authority = uri.getAuthority();
+        final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid));
+        if (pi == null) {
+            Slog.w(TAG, "No content provider found for permission check: " + uri.toSafeString());
+            return -1;
+        }
+
+        int targetUid = lastTargetUid;
+        if (targetUid < 0 && targetPkg != null) {
+            try {
+                targetUid = pm.getPackageUid(targetPkg, UserHandle.getUserId(callingUid));
+                if (targetUid < 0) {
+                    if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                            "Can't grant URI permission no uid for: " + targetPkg);
+                    return -1;
+                }
+            } catch (RemoteException ex) {
+                return -1;
+            }
+        }
+
+        if (targetUid >= 0) {
+            // First...  does the target actually need this permission?
+            if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+                // No need to grant the target this permission.
+                if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                        "Target " + targetPkg + " already has full permission to " + uri);
+                return -1;
+            }
+        } else {
+            // First...  there is no target package, so can anyone access it?
+            boolean allowed = pi.exported;
+            if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                if (pi.readPermission != null) {
+                    allowed = false;
+                }
+            }
+            if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                if (pi.writePermission != null) {
+                    allowed = false;
+                }
+            }
+            if (allowed) {
+                return -1;
+            }
+        }
+
+        // Second...  is the provider allowing granting of URI permissions?
+        if (!pi.grantUriPermissions) {
+            throw new SecurityException("Provider " + pi.packageName
+                    + "/" + pi.name
+                    + " does not allow granting of Uri permissions (uri "
+                    + uri + ")");
+        }
+        if (pi.uriPermissionPatterns != null) {
+            final int N = pi.uriPermissionPatterns.length;
+            boolean allowed = false;
+            for (int i=0; i<N; i++) {
+                if (pi.uriPermissionPatterns[i] != null
+                        && pi.uriPermissionPatterns[i].match(uri.getPath())) {
+                    allowed = true;
+                    break;
+                }
+            }
+            if (!allowed) {
+                throw new SecurityException("Provider " + pi.packageName
+                        + "/" + pi.name
+                        + " does not allow granting of permission to path of Uri "
+                        + uri);
+            }
+        }
+
+        // Third...  does the caller itself have permission to access
+        // this uri?
+        if (callingUid != Process.myUid()) {
+            if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+                // Require they hold a strong enough Uri permission
+                final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE
+                        : UriPermission.STRENGTH_OWNED;
+                if (!checkUriPermissionLocked(uri, callingUid, modeFlags, minStrength)) {
+                    throw new SecurityException("Uid " + callingUid
+                            + " does not have permission to uri " + uri);
+                }
+            }
+        }
+
+        return targetUid;
+    }
+
+    @Override
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        enforceNotIsolatedCaller("checkGrantUriPermission");
+        synchronized(this) {
+            return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
+        }
+    }
+
+    void grantUriPermissionUncheckedLocked(
+            int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+        final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0;
+        modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        if (modeFlags == 0) {
+            return;
+        }
+
+        // So here we are: the caller has the assumed permission
+        // to the uri, and the target doesn't.  Let's now give this to
+        // the target.
+
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
+                "Granting " + targetPkg + "/" + targetUid + " permission to " + uri);
+
+        final String authority = uri.getAuthority();
+        final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(targetUid));
+        if (pi == null) {
+            Slog.w(TAG, "No content provider found for grant: " + uri.toSafeString());
+            return;
+        }
+
+        final UriPermission perm = findOrCreateUriPermissionLocked(
+                pi.packageName, targetPkg, targetUid, uri);
+        perm.grantModes(modeFlags, persistable, owner);
+    }
+
+    void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri,
+            int modeFlags, UriPermissionOwner owner) {
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
+        int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags, -1);
+        if (targetUid < 0) {
+            return;
+        }
+
+        grantUriPermissionUncheckedLocked(targetUid, targetPkg, uri, modeFlags, owner);
+    }
+
+    static class NeededUriGrants extends ArrayList<Uri> {
+        final String targetPkg;
+        final int targetUid;
+        final int flags;
+
+        NeededUriGrants(String targetPkg, int targetUid, int flags) {
+            this.targetPkg = targetPkg;
+            this.targetUid = targetUid;
+            this.flags = flags;
+        }
+    }
+
+    /**
+     * Like checkGrantUriPermissionLocked, but takes an Intent.
+     */
+    NeededUriGrants checkGrantUriPermissionFromIntentLocked(int callingUid,
+            String targetPkg, Intent intent, int mode, NeededUriGrants needed) {
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                "Checking URI perm to data=" + (intent != null ? intent.getData() : null)
+                + " clip=" + (intent != null ? intent.getClipData() : null)
+                + " from " + intent + "; flags=0x"
+                + Integer.toHexString(intent != null ? intent.getFlags() : 0));
+
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
+        if (intent == null) {
+            return null;
+        }
+        Uri data = intent.getData();
+        ClipData clip = intent.getClipData();
+        if (data == null && clip == null) {
+            return null;
+        }
+
+        if (data != null) {
+            int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, data,
+                mode, needed != null ? needed.targetUid : -1);
+            if (targetUid > 0) {
+                if (needed == null) {
+                    needed = new NeededUriGrants(targetPkg, targetUid, mode);
+                }
+                needed.add(data);
+            }
+        }
+        if (clip != null) {
+            for (int i=0; i<clip.getItemCount(); i++) {
+                Uri uri = clip.getItemAt(i).getUri();
+                if (uri != null) {
+                    int targetUid = -1;
+                    targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri,
+                            mode, needed != null ? needed.targetUid : -1);
+                    if (targetUid > 0) {
+                        if (needed == null) {
+                            needed = new NeededUriGrants(targetPkg, targetUid, mode);
+                        }
+                        needed.add(uri);
+                    }
+                } else {
+                    Intent clipIntent = clip.getItemAt(i).getIntent();
+                    if (clipIntent != null) {
+                        NeededUriGrants newNeeded = checkGrantUriPermissionFromIntentLocked(
+                                callingUid, targetPkg, clipIntent, mode, needed);
+                        if (newNeeded != null) {
+                            needed = newNeeded;
+                        }
+                    }
+                }
+            }
+        }
+
+        return needed;
+    }
+
+    /**
+     * Like grantUriPermissionUncheckedLocked, but takes an Intent.
+     */
+    void grantUriPermissionUncheckedFromIntentLocked(NeededUriGrants needed,
+            UriPermissionOwner owner) {
+        if (needed != null) {
+            for (int i=0; i<needed.size(); i++) {
+                grantUriPermissionUncheckedLocked(needed.targetUid, needed.targetPkg,
+                        needed.get(i), needed.flags, owner);
+            }
+        }
+    }
+
+    void grantUriPermissionFromIntentLocked(int callingUid,
+            String targetPkg, Intent intent, UriPermissionOwner owner) {
+        NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
+                intent, intent != null ? intent.getFlags() : 0, null);
+        if (needed == null) {
+            return;
+        }
+
+        grantUriPermissionUncheckedFromIntentLocked(needed, owner);
+    }
+
+    @Override
+    public void grantUriPermission(IApplicationThread caller, String targetPkg,
+            Uri uri, int modeFlags) {
+        enforceNotIsolatedCaller("grantUriPermission");
+        synchronized(this) {
+            final ProcessRecord r = getRecordForAppLocked(caller);
+            if (r == null) {
+                throw new SecurityException("Unable to find app for caller "
+                        + caller
+                        + " when granting permission to uri " + uri);
+            }
+            if (targetPkg == null) {
+                throw new IllegalArgumentException("null target");
+            }
+            if (uri == null) {
+                throw new IllegalArgumentException("null uri");
+            }
+
+            // Persistable only supported through Intents
+            Preconditions.checkFlagsArgument(modeFlags,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+            grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags,
+                    null);
+        }
+    }
+
+    void removeUriPermissionIfNeededLocked(UriPermission perm) {
+        if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) {
+            ArrayMap<Uri, UriPermission> perms
+                    = mGrantedUriPermissions.get(perm.targetUid);
+            if (perms != null) {
+                if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
+                        "Removing " + perm.targetUid + " permission to " + perm.uri);
+                perms.remove(perm.uri);
+                if (perms.size() == 0) {
+                    mGrantedUriPermissions.remove(perm.targetUid);
+                }
+            }
+        }
+    }
+
+    private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) {
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri);
+
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final String authority = uri.getAuthority();
+        final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid));
+        if (pi == null) {
+            Slog.w(TAG, "No content provider found for permission revoke: " + uri.toSafeString());
+            return;
+        }
+
+        // Does the caller have this permission on the URI?
+        if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+            // Right now, if you are not the original owner of the permission,
+            // you are not allowed to revoke it.
+            //if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+                throw new SecurityException("Uid " + callingUid
+                        + " does not have permission to uri " + uri);
+            //}
+        }
+
+        boolean persistChanged = false;
+
+        // Go through all of the permissions and remove any that match.
+        final List<String> SEGMENTS = uri.getPathSegments();
+        if (SEGMENTS != null) {
+            final int NS = SEGMENTS.size();
+            int N = mGrantedUriPermissions.size();
+            for (int i=0; i<N; i++) {
+                ArrayMap<Uri, UriPermission> perms
+                        = mGrantedUriPermissions.valueAt(i);
+                Iterator<UriPermission> it = perms.values().iterator();
+            toploop:
+                while (it.hasNext()) {
+                    UriPermission perm = it.next();
+                    Uri targetUri = perm.uri;
+                    if (!authority.equals(targetUri.getAuthority())) {
+                        continue;
+                    }
+                    List<String> targetSegments = targetUri.getPathSegments();
+                    if (targetSegments == null) {
+                        continue;
+                    }
+                    if (targetSegments.size() < NS) {
+                        continue;
+                    }
+                    for (int j=0; j<NS; j++) {
+                        if (!SEGMENTS.get(j).equals(targetSegments.get(j))) {
+                            continue toploop;
+                        }
+                    }
+                    if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
+                            "Revoking " + perm.targetUid + " permission to " + perm.uri);
+                    persistChanged |= perm.clearModes(modeFlags, true);
+                    if (perm.modeFlags == 0) {
+                        it.remove();
+                    }
+                }
+                if (perms.size() == 0) {
+                    mGrantedUriPermissions.remove(
+                            mGrantedUriPermissions.keyAt(i));
+                    N--;
+                    i--;
+                }
+            }
+        }
+
+        if (persistChanged) {
+            schedulePersistUriGrants();
+        }
+    }
+
+    @Override
+    public void revokeUriPermission(IApplicationThread caller, Uri uri,
+            int modeFlags) {
+        enforceNotIsolatedCaller("revokeUriPermission");
+        synchronized(this) {
+            final ProcessRecord r = getRecordForAppLocked(caller);
+            if (r == null) {
+                throw new SecurityException("Unable to find app for caller "
+                        + caller
+                        + " when revoking permission to uri " + uri);
+            }
+            if (uri == null) {
+                Slog.w(TAG, "revokeUriPermission: null uri");
+                return;
+            }
+
+            modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            if (modeFlags == 0) {
+                return;
+            }
+
+            final IPackageManager pm = AppGlobals.getPackageManager();
+            final String authority = uri.getAuthority();
+            final ProviderInfo pi = getProviderInfoLocked(authority, r.userId);
+            if (pi == null) {
+                Slog.w(TAG, "No content provider found for permission revoke: "
+                        + uri.toSafeString());
+                return;
+            }
+
+            revokeUriPermissionLocked(r.uid, uri, modeFlags);
+        }
+    }
+
+    /**
+     * Remove any {@link UriPermission} granted <em>from</em> or <em>to</em> the
+     * given package.
+     *
+     * @param packageName Package name to match, or {@code null} to apply to all
+     *            packages.
+     * @param userHandle User to match, or {@link UserHandle#USER_ALL} to apply
+     *            to all users.
+     * @param persistable If persistable grants should be removed.
+     */
+    private void removeUriPermissionsForPackageLocked(
+            String packageName, int userHandle, boolean persistable) {
+        if (userHandle == UserHandle.USER_ALL && packageName == null) {
+            throw new IllegalArgumentException("Must narrow by either package or user");
+        }
+
+        boolean persistChanged = false;
+
+        final int size = mGrantedUriPermissions.size();
+        for (int i = 0; i < size; i++) {
+            // Only inspect grants matching user
+            if (userHandle == UserHandle.USER_ALL
+                    || userHandle == UserHandle.getUserId(mGrantedUriPermissions.keyAt(i))) {
+                final Iterator<UriPermission> it = mGrantedUriPermissions.valueAt(i)
+                        .values().iterator();
+                while (it.hasNext()) {
+                    final UriPermission perm = it.next();
+
+                    // Only inspect grants matching package
+                    if (packageName == null || perm.sourcePkg.equals(packageName)
+                            || perm.targetPkg.equals(packageName)) {
+                        persistChanged |= perm.clearModes(~0, persistable);
+
+                        // Only remove when no modes remain; any persisted grants
+                        // will keep this alive.
+                        if (perm.modeFlags == 0) {
+                            it.remove();
+                        }
+                    }
+                }
+            }
+        }
+
+        if (persistChanged) {
+            schedulePersistUriGrants();
+        }
+    }
+
+    @Override
+    public IBinder newUriPermissionOwner(String name) {
+        enforceNotIsolatedCaller("newUriPermissionOwner");
+        synchronized(this) {
+            UriPermissionOwner owner = new UriPermissionOwner(this, name);
+            return owner.getExternalTokenLocked();
+        }
+    }
+
+    @Override
+    public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        synchronized(this) {
+            UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
+            if (owner == null) {
+                throw new IllegalArgumentException("Unknown owner: " + token);
+            }
+            if (fromUid != Binder.getCallingUid()) {
+                if (Binder.getCallingUid() != Process.myUid()) {
+                    // Only system code can grant URI permissions on behalf
+                    // of other users.
+                    throw new SecurityException("nice try");
+                }
+            }
+            if (targetPkg == null) {
+                throw new IllegalArgumentException("null target");
+            }
+            if (uri == null) {
+                throw new IllegalArgumentException("null uri");
+            }
+
+            grantUriPermissionLocked(fromUid, targetPkg, uri, modeFlags, owner);
+        }
+    }
+
+    @Override
+    public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode) {
+        synchronized(this) {
+            UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
+            if (owner == null) {
+                throw new IllegalArgumentException("Unknown owner: " + token);
+            }
+
+            if (uri == null) {
+                owner.removeUriPermissionsLocked(mode);
+            } else {
+                owner.removeUriPermissionLocked(uri, mode);
+            }
+        }
+    }
+
+    private void schedulePersistUriGrants() {
+        if (!mHandler.hasMessages(PERSIST_URI_GRANTS_MSG)) {
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(PERSIST_URI_GRANTS_MSG),
+                    10 * DateUtils.SECOND_IN_MILLIS);
+        }
+    }
+
+    private void writeGrantedUriPermissions() {
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG, "writeGrantedUriPermissions()");
+
+        // Snapshot permissions so we can persist without lock
+        ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList();
+        synchronized (this) {
+            final int size = mGrantedUriPermissions.size();
+            for (int i = 0 ; i < size; i++) {
+                for (UriPermission perm : mGrantedUriPermissions.valueAt(i).values()) {
+                    if (perm.persistedModeFlags != 0) {
+                        persist.add(perm.snapshot());
+                    }
+                }
+            }
+        }
+
+        FileOutputStream fos = null;
+        try {
+            fos = mGrantFile.startWrite();
+
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.startTag(null, TAG_URI_GRANTS);
+            for (UriPermission.Snapshot perm : persist) {
+                out.startTag(null, TAG_URI_GRANT);
+                writeIntAttribute(out, ATTR_USER_HANDLE, perm.userHandle);
+                out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg);
+                out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg);
+                out.attribute(null, ATTR_URI, String.valueOf(perm.uri));
+                writeIntAttribute(out, ATTR_MODE_FLAGS, perm.persistedModeFlags);
+                writeLongAttribute(out, ATTR_CREATED_TIME, perm.persistedCreateTime);
+                out.endTag(null, TAG_URI_GRANT);
+            }
+            out.endTag(null, TAG_URI_GRANTS);
+            out.endDocument();
+
+            mGrantFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mGrantFile.failWrite(fos);
+            }
+        }
+    }
+
+    private void readGrantedUriPermissionsLocked() {
+        if (DEBUG_URI_PERMISSION) Slog.v(TAG, "readGrantedUriPermissions()");
+
+        final long now = System.currentTimeMillis();
+
+        FileInputStream fis = null;
+        try {
+            fis = mGrantFile.openRead();
+            final XmlPullParser in = Xml.newPullParser();
+            in.setInput(fis, null);
+
+            int type;
+            while ((type = in.next()) != END_DOCUMENT) {
+                final String tag = in.getName();
+                if (type == START_TAG) {
+                    if (TAG_URI_GRANT.equals(tag)) {
+                        final int userHandle = readIntAttribute(in, ATTR_USER_HANDLE);
+                        final String sourcePkg = in.getAttributeValue(null, ATTR_SOURCE_PKG);
+                        final String targetPkg = in.getAttributeValue(null, ATTR_TARGET_PKG);
+                        final Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI));
+                        final int modeFlags = readIntAttribute(in, ATTR_MODE_FLAGS);
+                        final long createdTime = readLongAttribute(in, ATTR_CREATED_TIME, now);
+
+                        // Sanity check that provider still belongs to source package
+                        final ProviderInfo pi = getProviderInfoLocked(
+                                uri.getAuthority(), userHandle);
+                        if (pi != null && sourcePkg.equals(pi.packageName)) {
+                            int targetUid = -1;
+                            try {
+                                targetUid = AppGlobals.getPackageManager()
+                                        .getPackageUid(targetPkg, userHandle);
+                            } catch (RemoteException e) {
+                            }
+                            if (targetUid != -1) {
+                                final UriPermission perm = findOrCreateUriPermissionLocked(
+                                        sourcePkg, targetPkg, targetUid, uri);
+                                perm.initPersistedModes(modeFlags, createdTime);
+                            }
+                        } else {
+                            Slog.w(TAG, "Persisted grant for " + uri + " had source " + sourcePkg
+                                    + " but instead found " + pi);
+                        }
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Missing grants is okay
+        } catch (IOException e) {
+            Log.wtf(TAG, "Failed reading Uri grants", e);
+        } catch (XmlPullParserException e) {
+            Log.wtf(TAG, "Failed reading Uri grants", e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+    }
+
+    @Override
+    public void takePersistableUriPermission(Uri uri, int modeFlags) {
+        enforceNotIsolatedCaller("takePersistableUriPermission");
+
+        Preconditions.checkFlagsArgument(modeFlags,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        synchronized (this) {
+            final int callingUid = Binder.getCallingUid();
+            final UriPermission perm = findUriPermissionLocked(callingUid, uri);
+            if (perm == null) {
+                throw new SecurityException("No permission grant found for UID " + callingUid
+                        + " and Uri " + uri.toSafeString());
+            }
+
+            boolean persistChanged = perm.takePersistableModes(modeFlags);
+            persistChanged |= maybePrunePersistedUriGrantsLocked(callingUid);
+
+            if (persistChanged) {
+                schedulePersistUriGrants();
+            }
+        }
+    }
+
+    @Override
+    public void releasePersistableUriPermission(Uri uri, int modeFlags) {
+        enforceNotIsolatedCaller("releasePersistableUriPermission");
+
+        Preconditions.checkFlagsArgument(modeFlags,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        synchronized (this) {
+            final int callingUid = Binder.getCallingUid();
+
+            final UriPermission perm = findUriPermissionLocked(callingUid, uri);
+            if (perm == null) {
+                Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri "
+                        + uri.toSafeString());
+                return;
+            }
+
+            final boolean persistChanged = perm.releasePersistableModes(modeFlags);
+            removeUriPermissionIfNeededLocked(perm);
+            if (persistChanged) {
+                schedulePersistUriGrants();
+            }
+        }
+    }
+
+    /**
+     * Prune any older {@link UriPermission} for the given UID until outstanding
+     * persisted grants are below {@link #MAX_PERSISTED_URI_GRANTS}.
+     *
+     * @return if any mutations occured that require persisting.
+     */
+    private boolean maybePrunePersistedUriGrantsLocked(int uid) {
+        final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid);
+        if (perms == null) return false;
+        if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false;
+
+        final ArrayList<UriPermission> persisted = Lists.newArrayList();
+        for (UriPermission perm : perms.values()) {
+            if (perm.persistedModeFlags != 0) {
+                persisted.add(perm);
+            }
+        }
+
+        final int trimCount = persisted.size() - MAX_PERSISTED_URI_GRANTS;
+        if (trimCount <= 0) return false;
+
+        Collections.sort(persisted, new UriPermission.PersistedTimeComparator());
+        for (int i = 0; i < trimCount; i++) {
+            final UriPermission perm = persisted.get(i);
+
+            if (DEBUG_URI_PERMISSION) {
+                Slog.v(TAG, "Trimming grant created at " + perm.persistedCreateTime);
+            }
+
+            perm.releasePersistableModes(~0);
+            removeUriPermissionIfNeededLocked(perm);
+        }
+
+        return true;
+    }
+
+    @Override
+    public ParceledListSlice<android.content.UriPermission> getPersistedUriPermissions(
+            String packageName, boolean incoming) {
+        enforceNotIsolatedCaller("getPersistedUriPermissions");
+        Preconditions.checkNotNull(packageName, "packageName");
+
+        final int callingUid = Binder.getCallingUid();
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            final int packageUid = pm.getPackageUid(packageName, UserHandle.getUserId(callingUid));
+            if (packageUid != callingUid) {
+                throw new SecurityException(
+                        "Package " + packageName + " does not belong to calling UID " + callingUid);
+            }
+        } catch (RemoteException e) {
+            throw new SecurityException("Failed to verify package name ownership");
+        }
+
+        final ArrayList<android.content.UriPermission> result = Lists.newArrayList();
+        synchronized (this) {
+            if (incoming) {
+                final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+                if (perms == null) {
+                    Slog.w(TAG, "No permission grants found for " + packageName);
+                } else {
+                    final int size = perms.size();
+                    for (int i = 0; i < size; i++) {
+                        final UriPermission perm = perms.valueAt(i);
+                        if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) {
+                            result.add(perm.buildPersistedPublicApiObject());
+                        }
+                    }
+                }
+            } else {
+                final int size = mGrantedUriPermissions.size();
+                for (int i = 0; i < size; i++) {
+                    final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i);
+                    final int permsSize = perms.size();
+                    for (int j = 0; j < permsSize; j++) {
+                        final UriPermission perm = perms.valueAt(j);
+                        if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) {
+                            result.add(perm.buildPersistedPublicApiObject());
+                        }
+                    }
+                }
+            }
+        }
+        return new ParceledListSlice<android.content.UriPermission>(result);
+    }
+
+    @Override
+    public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
+        synchronized (this) {
+            ProcessRecord app =
+                who != null ? getRecordForAppLocked(who) : null;
+            if (app == null) return;
+
+            Message msg = Message.obtain();
+            msg.what = WAIT_FOR_DEBUGGER_MSG;
+            msg.obj = app;
+            msg.arg1 = waiting ? 1 : 0;
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    @Override
+    public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
+        final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ);
+        final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ);
+        outInfo.availMem = Process.getFreeMemory();
+        outInfo.totalMem = Process.getTotalMemory();
+        outInfo.threshold = homeAppMem;
+        outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2));
+        outInfo.hiddenAppThreshold = cachedAppMem;
+        outInfo.secondaryServerThreshold = mProcessList.getMemLevel(
+                ProcessList.SERVICE_ADJ);
+        outInfo.visibleAppThreshold = mProcessList.getMemLevel(
+                ProcessList.VISIBLE_APP_ADJ);
+        outInfo.foregroundAppThreshold = mProcessList.getMemLevel(
+                ProcessList.FOREGROUND_APP_ADJ);
+    }
+    
+    // =========================================================
+    // TASK MANAGEMENT
+    // =========================================================
+
+    @Override
+    public List<RunningTaskInfo> getTasks(int maxNum, int flags,
+                         IThumbnailReceiver receiver) {
+        ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
+
+        PendingThumbnailsRecord pending = new PendingThumbnailsRecord(receiver);
+        ActivityRecord topRecord = null;
+
+        synchronized(this) {
+            if (localLOGV) Slog.v(
+                TAG, "getTasks: max=" + maxNum + ", flags=" + flags
+                + ", receiver=" + receiver);
+
+            if (checkCallingPermission(android.Manifest.permission.GET_TASKS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (receiver != null) {
+                    // If the caller wants to wait for pending thumbnails,
+                    // it ain't gonna get them.
+                    try {
+                        receiver.finished();
+                    } catch (RemoteException ex) {
+                    }
+                }
+                String msg = "Permission Denial: getTasks() from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + " requires " + android.Manifest.permission.GET_TASKS;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+
+            // TODO: Improve with MRU list from all ActivityStacks.
+            topRecord = mStackSupervisor.getTasksLocked(maxNum, receiver, pending, list);
+
+            if (!pending.pendingRecords.isEmpty()) {
+                mPendingThumbnails.add(pending);
+            }
+        }
+
+        if (localLOGV) Slog.v(TAG, "We have pending thumbnails: " + pending);
+
+        if (topRecord != null) {
+            if (localLOGV) Slog.v(TAG, "Requesting top thumbnail");
+            try {
+                IApplicationThread topThumbnail = topRecord.app.thread;
+                topThumbnail.requestThumbnail(topRecord.appToken);
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown when requesting thumbnail", e);
+                sendPendingThumbnail(null, topRecord.appToken, null, null, true);
+            }
+        }
+
+        if (pending == null && receiver != null) {
+            // In this case all thumbnails were available and the client
+            // is being asked to be told when the remaining ones come in...
+            // which is unusually, since the top-most currently running
+            // activity should never have a canned thumbnail!  Oh well.
+            try {
+                receiver.finished();
+            } catch (RemoteException ex) {
+            }
+        }
+
+        return list;
+    }
+
+    TaskRecord getMostRecentTask() {
+        return mRecentTasks.get(0);
+    }
+
+    @Override
+    public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
+            int flags, int userId) {
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "getRecentTasks", null);
+
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.GET_TASKS,
+                    "getRecentTasks()");
+            final boolean detailed = checkCallingPermission(
+                    android.Manifest.permission.GET_DETAILED_TASKS)
+                    == PackageManager.PERMISSION_GRANTED;
+
+            IPackageManager pm = AppGlobals.getPackageManager();
+
+            final int N = mRecentTasks.size();
+            ArrayList<ActivityManager.RecentTaskInfo> res
+                    = new ArrayList<ActivityManager.RecentTaskInfo>(
+                            maxNum < N ? maxNum : N);
+            for (int i=0; i<N && maxNum > 0; i++) {
+                TaskRecord tr = mRecentTasks.get(i);
+                // Only add calling user's recent tasks
+                if (tr.userId != userId) continue;
+                // Return the entry if desired by the caller.  We always return
+                // the first entry, because callers always expect this to be the
+                // foreground app.  We may filter others if the caller has
+                // not supplied RECENT_WITH_EXCLUDED and there is some reason
+                // we should exclude the entry.
+
+                if (i == 0
+                        || ((flags&ActivityManager.RECENT_WITH_EXCLUDED) != 0)
+                        || (tr.intent == null)
+                        || ((tr.intent.getFlags()
+                                &Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)) {
+                    ActivityManager.RecentTaskInfo rti
+                            = new ActivityManager.RecentTaskInfo();
+                    rti.id = tr.numActivities > 0 ? tr.taskId : -1;
+                    rti.persistentId = tr.taskId;
+                    rti.baseIntent = new Intent(
+                            tr.intent != null ? tr.intent : tr.affinityIntent);
+                    if (!detailed) {
+                        rti.baseIntent.replaceExtras((Bundle)null);
+                    }
+                    rti.origActivity = tr.origActivity;
+                    rti.description = tr.lastDescription;
+                    rti.stackId = tr.stack.mStackId;
+
+                    if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
+                        // Check whether this activity is currently available.
+                        try {
+                            if (rti.origActivity != null) {
+                                if (pm.getActivityInfo(rti.origActivity, 0, userId)
+                                        == null) {
+                                    continue;
+                                }
+                            } else if (rti.baseIntent != null) {
+                                if (pm.queryIntentActivities(rti.baseIntent,
+                                        null, 0, userId) == null) {
+                                    continue;
+                                }
+                            }
+                        } catch (RemoteException e) {
+                            // Will never happen.
+                        }
+                    }
+                    
+                    res.add(rti);
+                    maxNum--;
+                }
+            }
+            return res;
+        }
+    }
+
+    private TaskRecord recentTaskForIdLocked(int id) {
+        final int N = mRecentTasks.size();
+            for (int i=0; i<N; i++) {
+                TaskRecord tr = mRecentTasks.get(i);
+                if (tr.taskId == id) {
+                    return tr;
+                }
+            }
+            return null;
+    }
+
+    @Override
+    public ActivityManager.TaskThumbnails getTaskThumbnails(int id) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+                    "getTaskThumbnails()");
+            TaskRecord tr = recentTaskForIdLocked(id);
+            if (tr != null) {
+                return tr.getTaskThumbnailsLocked();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Bitmap getTaskTopThumbnail(int id) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+                    "getTaskTopThumbnail()");
+            TaskRecord tr = recentTaskForIdLocked(id);
+            if (tr != null) {
+                return tr.getTaskTopThumbnailLocked();
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public boolean removeSubTask(int taskId, int subTaskIndex) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+                    "removeSubTask()");
+            long ident = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = recentTaskForIdLocked(taskId);
+                if (tr != null) {
+                    return tr.removeTaskActivitiesLocked(subTaskIndex, true) != null;
+                }
+                return false;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    private void killUnneededProcessLocked(ProcessRecord pr, String reason) {
+        if (!pr.killedByAm) {
+            Slog.i(TAG, "Killing " + pr.toShortString() + " (adj " + pr.setAdj + "): " + reason);
+            EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid,
+                    pr.processName, pr.setAdj, reason);
+            pr.killedByAm = true;
+            Process.killProcessQuiet(pr.pid);
+        }
+    }
+
+    private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
+        tr.disposeThumbnail();
+        mRecentTasks.remove(tr);
+        final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0;
+        Intent baseIntent = new Intent(
+                tr.intent != null ? tr.intent : tr.affinityIntent);
+        ComponentName component = baseIntent.getComponent();
+        if (component == null) {
+            Slog.w(TAG, "Now component for base intent of task: " + tr);
+            return;
+        }
+
+        // Find any running services associated with this app.
+        mServices.cleanUpRemovedTaskLocked(tr, component, baseIntent);
+
+        if (killProcesses) {
+            // Find any running processes associated with this app.
+            final String pkg = component.getPackageName();
+            ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+            ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap();
+            for (int i=0; i<pmap.size(); i++) {
+                SparseArray<ProcessRecord> uids = pmap.valueAt(i);
+                for (int j=0; j<uids.size(); j++) {
+                    ProcessRecord proc = uids.valueAt(j);
+                    if (proc.userId != tr.userId) {
+                        continue;
+                    }
+                    if (!proc.pkgList.containsKey(pkg)) {
+                        continue;
+                    }
+                    procs.add(proc);
+                }
+            }
+
+            // Kill the running processes.
+            for (int i=0; i<procs.size(); i++) {
+                ProcessRecord pr = procs.get(i);
+                if (pr == mHomeProcess) {
+                    // Don't kill the home process along with tasks from the same package.
+                    continue;
+                }
+                if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+                    killUnneededProcessLocked(pr, "remove task");
+                } else {
+                    pr.waitingToKill = "remove task";
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean removeTask(int taskId, int flags) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+                    "removeTask()");
+            long ident = Binder.clearCallingIdentity();
+            try {
+                TaskRecord tr = recentTaskForIdLocked(taskId);
+                if (tr != null) {
+                    ActivityRecord r = tr.removeTaskActivitiesLocked(-1, false);
+                    if (r != null) {
+                        cleanUpRemovedTaskLocked(tr, flags);
+                        return true;
+                    }
+                    if (tr.mActivities.size() == 0) {
+                        // Caller is just removing a recent task that is
+                        // not actively running.  That is easy!
+                        cleanUpRemovedTaskLocked(tr, flags);
+                        return true;
+                    }
+                    Slog.w(TAG, "removeTask: task " + taskId
+                            + " does not have activities to remove, "
+                            + " but numActivities=" + tr.numActivities
+                            + ": " + tr);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * TODO: Add mController hook
+     */
+    @Override
+    public void moveTaskToFront(int task, int flags, Bundle options) {
+        enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+                "moveTaskToFront()");
+
+        if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving task=" + task);
+        synchronized(this) {
+            if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+                    Binder.getCallingUid(), "Task to front")) {
+                ActivityOptions.abort(options);
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+            ActivityOptions.abort(options);
+        }
+    }
+
+    @Override
+    public void moveTaskToBack(int taskId) {
+        enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+                "moveTaskToBack()");
+
+        synchronized(this) {
+            TaskRecord tr = recentTaskForIdLocked(taskId);
+            if (tr != null) {
+                if (DEBUG_STACK) Slog.d(TAG, "moveTaskToBack: moving task=" + tr);
+                ActivityStack stack = tr.stack;
+                if (stack.mResumedActivity != null && stack.mResumedActivity.task == tr) {
+                    if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+                            Binder.getCallingUid(), "Task to back")) {
+                        return;
+                    }
+                }
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    stack.moveTaskToBackLocked(taskId, null);
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    /**
+     * Moves an activity, and all of the other activities within the same task, to the bottom
+     * of the history stack.  The activity's order within the task is unchanged.
+     * 
+     * @param token A reference to the activity we wish to move
+     * @param nonRoot If false then this only works if the activity is the root
+     *                of a task; if true it will work for any activity in a task.
+     * @return Returns true if the move completed, false if not.
+     */
+    @Override
+    public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) {
+        enforceNotIsolatedCaller("moveActivityTaskToBack");
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot);
+            if (taskId >= 0) {
+                return ActivityRecord.getStackLocked(token).moveTaskToBackLocked(taskId, null);
+            }
+            Binder.restoreCallingIdentity(origId);
+        }
+        return false;
+    }
+
+    @Override
+    public void moveTaskBackwards(int task) {
+        enforceCallingPermission(android.Manifest.permission.REORDER_TASKS,
+                "moveTaskBackwards()");
+
+        synchronized(this) {
+            if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+                    Binder.getCallingUid(), "Task backwards")) {
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            moveTaskBackwardsLocked(task);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private final void moveTaskBackwardsLocked(int task) {
+        Slog.e(TAG, "moveTaskBackwards not yet implemented!");
+    }
+
+    @Override
+    public IBinder getHomeActivityToken() throws RemoteException {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "getHomeActivityToken()");
+        synchronized (this) {
+            return mStackSupervisor.getHomeActivityToken();
+        }
+    }
+
+    @Override
+    public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+            IActivityContainerCallback callback) throws RemoteException {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "createActivityContainer()");
+        synchronized (this) {
+            if (parentActivityToken == null) {
+                throw new IllegalArgumentException("parent token must not be null");
+            }
+            ActivityRecord r = ActivityRecord.forToken(parentActivityToken);
+            if (r == null) {
+                return null;
+            }
+            return mStackSupervisor.createActivityContainer(r, callback);
+        }
+    }
+
+    @Override
+    public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "moveTaskToStack()");
+        if (stackId == HOME_STACK_ID) {
+            Slog.e(TAG, "moveTaskToStack: Attempt to move task " + taskId + " to home stack",
+                    new RuntimeException("here").fillInStackTrace());
+        }
+        synchronized (this) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG_STACK) Slog.d(TAG, "moveTaskToStack: moving task=" + taskId + " to stackId="
+                        + stackId + " toTop=" + toTop);
+                mStackSupervisor.moveTaskToStack(taskId, stackId, toTop);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void resizeStack(int stackBoxId, Rect bounds) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "resizeStackBox()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mWindowManager.resizeStack(stackBoxId, bounds);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public List<StackInfo> getAllStackInfos() {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "getAllStackInfos()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                return mStackSupervisor.getAllStackInfosLocked();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public StackInfo getStackInfo(int stackId) {
+        enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
+                "getStackInfo()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                return mStackSupervisor.getStackInfoLocked(stackId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public int getTaskForActivity(IBinder token, boolean onlyRoot) {
+        synchronized(this) {
+            return ActivityRecord.getTaskForActivityLocked(token, onlyRoot);
+        }
+    }
+
+    // =========================================================
+    // THUMBNAILS
+    // =========================================================
+
+    public void reportThumbnail(IBinder token,
+            Bitmap thumbnail, CharSequence description) {
+        //System.out.println("Report thumbnail for " + token + ": " + thumbnail);
+        final long origId = Binder.clearCallingIdentity();
+        sendPendingThumbnail(null, token, thumbnail, description, true);
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    final void sendPendingThumbnail(ActivityRecord r, IBinder token,
+            Bitmap thumbnail, CharSequence description, boolean always) {
+        TaskRecord task;
+        ArrayList<PendingThumbnailsRecord> receivers = null;
+
+        //System.out.println("Send pending thumbnail: " + r);
+
+        synchronized(this) {
+            if (r == null) {
+                r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    return;
+                }
+            }
+            if (thumbnail == null && r.thumbHolder != null) {
+                thumbnail = r.thumbHolder.lastThumbnail;
+                description = r.thumbHolder.lastDescription;
+            }
+            if (thumbnail == null && !always) {
+                // If there is no thumbnail, and this entry is not actually
+                // going away, then abort for now and pick up the next
+                // thumbnail we get.
+                return;
+            }
+            task = r.task;
+
+            int N = mPendingThumbnails.size();
+            int i=0;
+            while (i<N) {
+                PendingThumbnailsRecord pr = mPendingThumbnails.get(i);
+                //System.out.println("Looking in " + pr.pendingRecords);
+                if (pr.pendingRecords.remove(r)) {
+                    if (receivers == null) {
+                        receivers = new ArrayList<PendingThumbnailsRecord>();
+                    }
+                    receivers.add(pr);
+                    if (pr.pendingRecords.size() == 0) {
+                        pr.finished = true;
+                        mPendingThumbnails.remove(i);
+                        N--;
+                        continue;
+                    }
+                }
+                i++;
+            }
+        }
+
+        if (receivers != null) {
+            final int N = receivers.size();
+            for (int i=0; i<N; i++) {
+                try {
+                    PendingThumbnailsRecord pr = receivers.get(i);
+                    pr.receiver.newThumbnail(
+                        task != null ? task.taskId : -1, thumbnail, description);
+                    if (pr.finished) {
+                        pr.receiver.finished();
+                    }
+                } catch (Exception e) {
+                    Slog.w(TAG, "Exception thrown when sending thumbnail", e);
+                }
+            }
+        }
+    }
+
+    // =========================================================
+    // CONTENT PROVIDERS
+    // =========================================================
+
+    private final List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
+        List<ProviderInfo> providers = null;
+        try {
+            providers = AppGlobals.getPackageManager().
+                queryContentProviders(app.processName, app.uid,
+                        STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS);
+        } catch (RemoteException ex) {
+        }
+        if (DEBUG_MU)
+            Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid);
+        int userId = app.userId;
+        if (providers != null) {
+            int N = providers.size();
+            app.pubProviders.ensureCapacity(N + app.pubProviders.size());
+            for (int i=0; i<N; i++) {
+                ProviderInfo cpi =
+                    (ProviderInfo)providers.get(i);
+                boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
+                        cpi.name, cpi.flags);
+                if (singleton && UserHandle.getUserId(app.uid) != 0) {
+                    // This is a singleton provider, but a user besides the
+                    // default user is asking to initialize a process it runs
+                    // in...  well, no, it doesn't actually run in this process,
+                    // it runs in the process of the default user.  Get rid of it.
+                    providers.remove(i);
+                    N--;
+                    i--;
+                    continue;
+                }
+
+                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
+                ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId);
+                if (cpr == null) {
+                    cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton);
+                    mProviderMap.putProviderByClass(comp, cpr);
+                }
+                if (DEBUG_MU)
+                    Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid);
+                app.pubProviders.put(cpi.name, cpr);
+                if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
+                    // Don't add this if it is a platform component that is marked
+                    // to run in multiple processes, because this is actually
+                    // part of the framework so doesn't make sense to track as a
+                    // separate apk in the process.
+                    app.addPackage(cpi.applicationInfo.packageName, mProcessStats);
+                }
+                ensurePackageDexOpt(cpi.applicationInfo.packageName);
+            }
+        }
+        return providers;
+    }
+
+    /**
+     * Check if {@link ProcessRecord} has a possible chance at accessing the
+     * given {@link ProviderInfo}. Final permission checking is always done
+     * in {@link ContentProvider}.
+     */
+    private final String checkContentProviderPermissionLocked(
+            ProviderInfo cpi, ProcessRecord r) {
+        final int callingPid = (r != null) ? r.pid : Binder.getCallingPid();
+        final int callingUid = (r != null) ? r.uid : Binder.getCallingUid();
+        if (checkComponentPermission(cpi.readPermission, callingPid, callingUid,
+                cpi.applicationInfo.uid, cpi.exported)
+                == PackageManager.PERMISSION_GRANTED) {
+            return null;
+        }
+        if (checkComponentPermission(cpi.writePermission, callingPid, callingUid,
+                cpi.applicationInfo.uid, cpi.exported)
+                == PackageManager.PERMISSION_GRANTED) {
+            return null;
+        }
+        
+        PathPermission[] pps = cpi.pathPermissions;
+        if (pps != null) {
+            int i = pps.length;
+            while (i > 0) {
+                i--;
+                PathPermission pp = pps[i];
+                if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid,
+                        cpi.applicationInfo.uid, cpi.exported)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    return null;
+                }
+                if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid,
+                        cpi.applicationInfo.uid, cpi.exported)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    return null;
+                }
+            }
+        }
+        
+        ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid);
+        if (perms != null) {
+            for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) {
+                if (uri.getKey().getAuthority().equals(cpi.authority)) {
+                    return null;
+                }
+            }
+        }
+
+        String msg;
+        if (!cpi.exported) {
+            msg = "Permission Denial: opening provider " + cpi.name
+                    + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ") that is not exported from uid "
+                    + cpi.applicationInfo.uid;
+        } else {
+            msg = "Permission Denial: opening provider " + cpi.name
+                    + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
+                    + ", uid=" + callingUid + ") requires "
+                    + cpi.readPermission + " or " + cpi.writePermission;
+        }
+        Slog.w(TAG, msg);
+        return msg;
+    }
+
+    ContentProviderConnection incProviderCountLocked(ProcessRecord r,
+            final ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
+        if (r != null) {
+            for (int i=0; i<r.conProviders.size(); i++) {
+                ContentProviderConnection conn = r.conProviders.get(i);
+                if (conn.provider == cpr) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "Adding provider requested by "
+                            + r.processName + " from process "
+                            + cpr.info.processName + ": " + cpr.name.flattenToShortString()
+                            + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+                    if (stable) {
+                        conn.stableCount++;
+                        conn.numStableIncs++;
+                    } else {
+                        conn.unstableCount++;
+                        conn.numUnstableIncs++;
+                    }
+                    return conn;
+                }
+            }
+            ContentProviderConnection conn = new ContentProviderConnection(cpr, r);
+            if (stable) {
+                conn.stableCount = 1;
+                conn.numStableIncs = 1;
+            } else {
+                conn.unstableCount = 1;
+                conn.numUnstableIncs = 1;
+            }
+            cpr.connections.add(conn);
+            r.conProviders.add(conn);
+            return conn;
+        }
+        cpr.addExternalProcessHandleLocked(externalProcessToken);
+        return null;
+    }
+
+    boolean decProviderCountLocked(ContentProviderConnection conn,
+            ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
+        if (conn != null) {
+            cpr = conn.provider;
+            if (DEBUG_PROVIDER) Slog.v(TAG,
+                    "Removing provider requested by "
+                    + conn.client.processName + " from process "
+                    + cpr.info.processName + ": " + cpr.name.flattenToShortString()
+                    + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+            if (stable) {
+                conn.stableCount--;
+            } else {
+                conn.unstableCount--;
+            }
+            if (conn.stableCount == 0 && conn.unstableCount == 0) {
+                cpr.connections.remove(conn);
+                conn.client.conProviders.remove(conn);
+                return true;
+            }
+            return false;
+        }
+        cpr.removeExternalProcessHandleLocked(externalProcessToken);
+        return false;
+    }
+
+    private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
+            String name, IBinder token, boolean stable, int userId) {
+        ContentProviderRecord cpr;
+        ContentProviderConnection conn = null;
+        ProviderInfo cpi = null;
+
+        synchronized(this) {
+            ProcessRecord r = null;
+            if (caller != null) {
+                r = getRecordForAppLocked(caller);
+                if (r == null) {
+                    throw new SecurityException(
+                            "Unable to find app for caller " + caller
+                          + " (pid=" + Binder.getCallingPid()
+                          + ") when getting content provider " + name);
+                }
+            }
+
+            // First check if this content provider has been published...
+            cpr = mProviderMap.getProviderByName(name, userId);
+            boolean providerRunning = cpr != null;
+            if (providerRunning) {
+                cpi = cpr.info;
+                String msg;
+                if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {
+                    throw new SecurityException(msg);
+                }
+
+                if (r != null && cpr.canRunHere(r)) {
+                    // This provider has been published or is in the process
+                    // of being published...  but it is also allowed to run
+                    // in the caller's process, so don't make a connection
+                    // and just let the caller instantiate its own instance.
+                    ContentProviderHolder holder = cpr.newHolder(null);
+                    // don't give caller the provider object, it needs
+                    // to make its own.
+                    holder.provider = null;
+                    return holder;
+                }
+
+                final long origId = Binder.clearCallingIdentity();
+
+                // In this case the provider instance already exists, so we can
+                // return it right away.
+                conn = incProviderCountLocked(r, cpr, token, stable);
+                if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
+                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        // If this is a perceptible app accessing the provider,
+                        // make sure to count it as being accessed and thus
+                        // back up on the LRU list.  This is good because
+                        // content providers are often expensive to start.
+                        updateLruProcessLocked(cpr.proc, false, null);
+                    }
+                }
+
+                if (cpr.proc != null) {
+                    if (false) {
+                        if (cpr.name.flattenToShortString().equals(
+                                "com.android.providers.calendar/.CalendarProvider2")) {
+                            Slog.v(TAG, "****************** KILLING "
+                                + cpr.name.flattenToShortString());
+                            Process.killProcess(cpr.proc.pid);
+                        }
+                    }
+                    boolean success = updateOomAdjLocked(cpr.proc);
+                    if (DEBUG_PROVIDER) Slog.i(TAG, "Adjust success: " + success);
+                    // NOTE: there is still a race here where a signal could be
+                    // pending on the process even though we managed to update its
+                    // adj level.  Not sure what to do about this, but at least
+                    // the race is now smaller.
+                    if (!success) {
+                        // Uh oh...  it looks like the provider's process
+                        // has been killed on us.  We need to wait for a new
+                        // process to be started, and make sure its death
+                        // doesn't kill our process.
+                        Slog.i(TAG,
+                                "Existing provider " + cpr.name.flattenToShortString()
+                                + " is crashing; detaching " + r);
+                        boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
+                        appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);
+                        if (!lastRef) {
+                            // This wasn't the last ref our process had on
+                            // the provider...  we have now been killed, bail.
+                            return null;
+                        }
+                        providerRunning = false;
+                        conn = null;
+                    }
+                }
+
+                Binder.restoreCallingIdentity(origId);
+            }
+
+            boolean singleton;
+            if (!providerRunning) {
+                try {
+                    cpi = AppGlobals.getPackageManager().
+                        resolveContentProvider(name,
+                            STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS, userId);
+                } catch (RemoteException ex) {
+                }
+                if (cpi == null) {
+                    return null;
+                }
+                singleton = isSingleton(cpi.processName, cpi.applicationInfo,
+                        cpi.name, cpi.flags); 
+                if (singleton) {
+                    userId = 0;
+                }
+                cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId);
+
+                String msg;
+                if ((msg=checkContentProviderPermissionLocked(cpi, r)) != null) {
+                    throw new SecurityException(msg);
+                }
+
+                if (!mProcessesReady && !mDidUpdate && !mWaitingUpdate
+                        && !cpi.processName.equals("system")) {
+                    // If this content provider does not run in the system
+                    // process, and the system is not yet ready to run other
+                    // processes, then fail fast instead of hanging.
+                    throw new IllegalArgumentException(
+                            "Attempt to launch content provider before system ready");
+                }
+
+                // Make sure that the user who owns this provider is started.  If not,
+                // we don't want to allow it to run.
+                if (mStartedUsers.get(userId) == null) {
+                    Slog.w(TAG, "Unable to launch app "
+                            + cpi.applicationInfo.packageName + "/"
+                            + cpi.applicationInfo.uid + " for provider "
+                            + name + ": user " + userId + " is stopped");
+                    return null;
+                }
+
+                ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
+                cpr = mProviderMap.getProviderByClass(comp, userId);
+                final boolean firstClass = cpr == null;
+                if (firstClass) {
+                    try {
+                        ApplicationInfo ai =
+                            AppGlobals.getPackageManager().
+                                getApplicationInfo(
+                                        cpi.applicationInfo.packageName,
+                                        STOCK_PM_FLAGS, userId);
+                        if (ai == null) {
+                            Slog.w(TAG, "No package info for content provider "
+                                    + cpi.name);
+                            return null;
+                        }
+                        ai = getAppInfoForUser(ai, userId);
+                        cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton);
+                    } catch (RemoteException ex) {
+                        // pm is in same process, this will never happen.
+                    }
+                }
+
+                if (r != null && cpr.canRunHere(r)) {
+                    // If this is a multiprocess provider, then just return its
+                    // info and allow the caller to instantiate it.  Only do
+                    // this if the provider is the same user as the caller's
+                    // process, or can run as root (so can be in any process).
+                    return cpr.newHolder(null);
+                }
+
+                if (DEBUG_PROVIDER) {
+                    RuntimeException e = new RuntimeException("here");
+                    Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + (r != null ? r.uid : null)
+                          + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e);
+                }
+
+                // This is single process, and our app is now connecting to it.
+                // See if we are already in the process of launching this
+                // provider.
+                final int N = mLaunchingProviders.size();
+                int i;
+                for (i=0; i<N; i++) {
+                    if (mLaunchingProviders.get(i) == cpr) {
+                        break;
+                    }
+                }
+
+                // If the provider is not already being launched, then get it
+                // started.
+                if (i >= N) {
+                    final long origId = Binder.clearCallingIdentity();
+
+                    try {
+                        // Content provider is now in use, its package can't be stopped.
+                        try {
+                            AppGlobals.getPackageManager().setPackageStoppedState(
+                                    cpr.appInfo.packageName, false, userId);
+                        } catch (RemoteException e) {
+                        } catch (IllegalArgumentException e) {
+                            Slog.w(TAG, "Failed trying to unstop package "
+                                    + cpr.appInfo.packageName + ": " + e);
+                        }
+
+                        // Use existing process if already started
+                        ProcessRecord proc = getProcessRecordLocked(
+                                cpi.processName, cpr.appInfo.uid, false);
+                        if (proc != null && proc.thread != null) {
+                            if (DEBUG_PROVIDER) {
+                                Slog.d(TAG, "Installing in existing process " + proc);
+                            }
+                            proc.pubProviders.put(cpi.name, cpr);
+                            try {
+                                proc.thread.scheduleInstallProvider(cpi);
+                            } catch (RemoteException e) {
+                            }
+                        } else {
+                            proc = startProcessLocked(cpi.processName,
+                                    cpr.appInfo, false, 0, "content provider",
+                                    new ComponentName(cpi.applicationInfo.packageName,
+                                            cpi.name), false, false, false);
+                            if (proc == null) {
+                                Slog.w(TAG, "Unable to launch app "
+                                        + cpi.applicationInfo.packageName + "/"
+                                        + cpi.applicationInfo.uid + " for provider "
+                                        + name + ": process is bad");
+                                return null;
+                            }
+                        }
+                        cpr.launchingApp = proc;
+                        mLaunchingProviders.add(cpr);
+                    } finally {
+                        Binder.restoreCallingIdentity(origId);
+                    }
+                }
+
+                // Make sure the provider is published (the same provider class
+                // may be published under multiple names).
+                if (firstClass) {
+                    mProviderMap.putProviderByClass(comp, cpr);
+                }
+
+                mProviderMap.putProviderByName(name, cpr);
+                conn = incProviderCountLocked(r, cpr, token, stable);
+                if (conn != null) {
+                    conn.waiting = true;
+                }
+            }
+        }
+
+        // Wait for the provider to be published...
+        synchronized (cpr) {
+            while (cpr.provider == null) {
+                if (cpr.launchingApp == null) {
+                    Slog.w(TAG, "Unable to launch app "
+                            + cpi.applicationInfo.packageName + "/"
+                            + cpi.applicationInfo.uid + " for provider "
+                            + name + ": launching app became null");
+                    EventLog.writeEvent(EventLogTags.AM_PROVIDER_LOST_PROCESS,
+                            UserHandle.getUserId(cpi.applicationInfo.uid),
+                            cpi.applicationInfo.packageName,
+                            cpi.applicationInfo.uid, name);
+                    return null;
+                }
+                try {
+                    if (DEBUG_MU) {
+                        Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp="
+                                + cpr.launchingApp);
+                    }
+                    if (conn != null) {
+                        conn.waiting = true;
+                    }
+                    cpr.wait();
+                } catch (InterruptedException ex) {
+                } finally {
+                    if (conn != null) {
+                        conn.waiting = false;
+                    }
+                }
+            }
+        }
+        return cpr != null ? cpr.newHolder(conn) : null;
+    }
+
+    public final ContentProviderHolder getContentProvider(
+            IApplicationThread caller, String name, int userId, boolean stable) {
+        enforceNotIsolatedCaller("getContentProvider");
+        if (caller == null) {
+            String msg = "null IApplicationThread when getting content provider "
+                    + name;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "getContentProvider", null);
+        return getContentProviderImpl(caller, name, null, stable, userId);
+    }
+
+    public ContentProviderHolder getContentProviderExternal(
+            String name, int userId, IBinder token) {
+        enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+            "Do not have permission in call getContentProviderExternal()");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "getContentProvider", null);
+        return getContentProviderExternalUnchecked(name, token, userId);
+    }
+
+    private ContentProviderHolder getContentProviderExternalUnchecked(String name,
+            IBinder token, int userId) {
+        return getContentProviderImpl(null, name, token, true, userId);
+    }
+
+    /**
+     * Drop a content provider from a ProcessRecord's bookkeeping
+     */
+    public void removeContentProvider(IBinder connection, boolean stable) {
+        enforceNotIsolatedCaller("removeContentProvider");
+        synchronized (this) {
+            ContentProviderConnection conn;
+            try {
+                conn = (ContentProviderConnection)connection;
+            } catch (ClassCastException e) {
+                String msg ="removeContentProvider: " + connection
+                        + " not a ContentProviderConnection";
+                Slog.w(TAG, msg);
+                throw new IllegalArgumentException(msg);
+            }
+            if (conn == null) {
+                throw new NullPointerException("connection is null");
+            }
+            if (decProviderCountLocked(conn, null, null, stable)) {
+                updateOomAdjLocked();
+            }
+        }
+    }
+
+    public void removeContentProviderExternal(String name, IBinder token) {
+        enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
+            "Do not have permission in call removeContentProviderExternal()");
+        removeContentProviderExternalUnchecked(name, token, UserHandle.getCallingUserId());
+    }
+
+    private void removeContentProviderExternalUnchecked(String name, IBinder token, int userId) {
+        synchronized (this) {
+            ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId);
+            if(cpr == null) {
+                //remove from mProvidersByClass
+                if(localLOGV) Slog.v(TAG, name+" content provider not found in providers list");
+                return;
+            }
+
+            //update content provider record entry info
+            ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
+            ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
+            if (localCpr.hasExternalProcessHandles()) {
+                if (localCpr.removeExternalProcessHandleLocked(token)) {
+                    updateOomAdjLocked();
+                } else {
+                    Slog.e(TAG, "Attmpt to remove content provider " + localCpr
+                            + " with no external reference for token: "
+                            + token + ".");
+                }
+            } else {
+                Slog.e(TAG, "Attmpt to remove content provider: " + localCpr
+                        + " with no external references.");
+            }
+        }
+    }
+    
+    public final void publishContentProviders(IApplicationThread caller,
+            List<ContentProviderHolder> providers) {
+        if (providers == null) {
+            return;
+        }
+
+        enforceNotIsolatedCaller("publishContentProviders");
+        synchronized (this) {
+            final ProcessRecord r = getRecordForAppLocked(caller);
+            if (DEBUG_MU)
+                Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
+            if (r == null) {
+                throw new SecurityException(
+                        "Unable to find app for caller " + caller
+                      + " (pid=" + Binder.getCallingPid()
+                      + ") when publishing content providers");
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+
+            final int N = providers.size();
+            for (int i=0; i<N; i++) {
+                ContentProviderHolder src = providers.get(i);
+                if (src == null || src.info == null || src.provider == null) {
+                    continue;
+                }
+                ContentProviderRecord dst = r.pubProviders.get(src.info.name);
+                if (DEBUG_MU)
+                    Slog.v(TAG_MU, "ContentProviderRecord uid = " + dst.uid);
+                if (dst != null) {
+                    ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
+                    mProviderMap.putProviderByClass(comp, dst);
+                    String names[] = dst.info.authority.split(";");
+                    for (int j = 0; j < names.length; j++) {
+                        mProviderMap.putProviderByName(names[j], dst);
+                    }
+
+                    int NL = mLaunchingProviders.size();
+                    int j;
+                    for (j=0; j<NL; j++) {
+                        if (mLaunchingProviders.get(j) == dst) {
+                            mLaunchingProviders.remove(j);
+                            j--;
+                            NL--;
+                        }
+                    }
+                    synchronized (dst) {
+                        dst.provider = src.provider;
+                        dst.proc = r;
+                        dst.notifyAll();
+                    }
+                    updateOomAdjLocked(r);
+                }
+            }
+
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public boolean refContentProvider(IBinder connection, int stable, int unstable) {
+        ContentProviderConnection conn;
+        try {
+            conn = (ContentProviderConnection)connection;
+        } catch (ClassCastException e) {
+            String msg ="refContentProvider: " + connection
+                    + " not a ContentProviderConnection";
+            Slog.w(TAG, msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (conn == null) {
+            throw new NullPointerException("connection is null");
+        }
+
+        synchronized (this) {
+            if (stable > 0) {
+                conn.numStableIncs += stable;
+            }
+            stable = conn.stableCount + stable;
+            if (stable < 0) {
+                throw new IllegalStateException("stableCount < 0: " + stable);
+            }
+
+            if (unstable > 0) {
+                conn.numUnstableIncs += unstable;
+            }
+            unstable = conn.unstableCount + unstable;
+            if (unstable < 0) {
+                throw new IllegalStateException("unstableCount < 0: " + unstable);
+            }
+
+            if ((stable+unstable) <= 0) {
+                throw new IllegalStateException("ref counts can't go to zero here: stable="
+                        + stable + " unstable=" + unstable);
+            }
+            conn.stableCount = stable;
+            conn.unstableCount = unstable;
+            return !conn.dead;
+        }
+    }
+
+    public void unstableProviderDied(IBinder connection) {
+        ContentProviderConnection conn;
+        try {
+            conn = (ContentProviderConnection)connection;
+        } catch (ClassCastException e) {
+            String msg ="refContentProvider: " + connection
+                    + " not a ContentProviderConnection";
+            Slog.w(TAG, msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (conn == null) {
+            throw new NullPointerException("connection is null");
+        }
+
+        // Safely retrieve the content provider associated with the connection.
+        IContentProvider provider;
+        synchronized (this) {
+            provider = conn.provider.provider;
+        }
+
+        if (provider == null) {
+            // Um, yeah, we're way ahead of you.
+            return;
+        }
+
+        // Make sure the caller is being honest with us.
+        if (provider.asBinder().pingBinder()) {
+            // Er, no, still looks good to us.
+            synchronized (this) {
+                Slog.w(TAG, "unstableProviderDied: caller " + Binder.getCallingUid()
+                        + " says " + conn + " died, but we don't agree");
+                return;
+            }
+        }
+
+        // Well look at that!  It's dead!
+        synchronized (this) {
+            if (conn.provider.provider != provider) {
+                // But something changed...  good enough.
+                return;
+            }
+
+            ProcessRecord proc = conn.provider.proc;
+            if (proc == null || proc.thread == null) {
+                // Seems like the process is already cleaned up.
+                return;
+            }
+
+            // As far as we're concerned, this is just like receiving a
+            // death notification...  just a bit prematurely.
+            Slog.i(TAG, "Process " + proc.processName + " (pid " + proc.pid
+                    + ") early provider death");
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                appDiedLocked(proc, proc.pid, proc.thread);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void appNotRespondingViaProvider(IBinder connection) {
+        enforceCallingPermission(
+                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+
+        final ContentProviderConnection conn = (ContentProviderConnection) connection;
+        if (conn == null) {
+            Slog.w(TAG, "ContentProviderConnection is null");
+            return;
+        }
+
+        final ProcessRecord host = conn.provider.proc;
+        if (host == null) {
+            Slog.w(TAG, "Failed to find hosting ProcessRecord");
+            return;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            appNotResponding(host, null, null, false, "ContentProvider not responding");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    public static final void installSystemProviders() {
+        List<ProviderInfo> providers;
+        synchronized (mSelf) {
+            ProcessRecord app = mSelf.mProcessNames.get("system", Process.SYSTEM_UID);
+            providers = mSelf.generateApplicationProvidersLocked(app);
+            if (providers != null) {
+                for (int i=providers.size()-1; i>=0; i--) {
+                    ProviderInfo pi = (ProviderInfo)providers.get(i);
+                    if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+                        Slog.w(TAG, "Not installing system proc provider " + pi.name
+                                + ": not system .apk");
+                        providers.remove(i);
+                    }
+                }
+            }
+        }
+        if (providers != null) {
+            mSystemThread.installSystemProviders(providers);
+        }
+
+        mSelf.mCoreSettingsObserver = new CoreSettingsObserver(mSelf);
+
+        mSelf.mUsageStatsService.monitorPackages();
+    }
+
+    /**
+     * Allows app to retrieve the MIME type of a URI without having permission
+     * to access its content provider.
+     *
+     * CTS tests for this functionality can be run with "runtest cts-appsecurity".
+     *
+     * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
+     *     src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+     */
+    public String getProviderMimeType(Uri uri, int userId) {
+        enforceNotIsolatedCaller("getProviderMimeType");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, true, "getProviderMimeType", null);
+        final String name = uri.getAuthority();
+        final long ident = Binder.clearCallingIdentity();
+        ContentProviderHolder holder = null;
+
+        try {
+            holder = getContentProviderExternalUnchecked(name, null, userId);
+            if (holder != null) {
+                return holder.provider.getType(uri);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Content provider dead retrieving " + uri, e);
+            return null;
+        } finally {
+            if (holder != null) {
+                removeContentProviderExternalUnchecked(name, null, userId);
+            }
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return null;
+    }
+
+    // =========================================================
+    // GLOBAL MANAGEMENT
+    // =========================================================
+
+    final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess,
+            boolean isolated) {
+        String proc = customProcess != null ? customProcess : info.processName;
+        BatteryStatsImpl.Uid.Proc ps = null;
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        int uid = info.uid;
+        if (isolated) {
+            int userId = UserHandle.getUserId(uid);
+            int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1;
+            while (true) {
+                if (mNextIsolatedProcessUid < Process.FIRST_ISOLATED_UID
+                        || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) {
+                    mNextIsolatedProcessUid = Process.FIRST_ISOLATED_UID;
+                }
+                uid = UserHandle.getUid(userId, mNextIsolatedProcessUid);
+                mNextIsolatedProcessUid++;
+                if (mIsolatedProcesses.indexOfKey(uid) < 0) {
+                    // No process for this uid, use it.
+                    break;
+                }
+                stepsLeft--;
+                if (stepsLeft <= 0) {
+                    return null;
+                }
+            }
+        }
+        return new ProcessRecord(stats, info, proc, uid);
+    }
+
+    final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) {
+        ProcessRecord app;
+        if (!isolated) {
+            app = getProcessRecordLocked(info.processName, info.uid, true);
+        } else {
+            app = null;
+        }
+
+        if (app == null) {
+            app = newProcessRecordLocked(info, null, isolated);
+            mProcessNames.put(info.processName, app.uid, app);
+            if (isolated) {
+                mIsolatedProcesses.put(app.uid, app);
+            }
+            updateLruProcessLocked(app, false, null);
+            updateOomAdjLocked();
+        }
+
+        // This package really, really can not be stopped.
+        try {
+            AppGlobals.getPackageManager().setPackageStoppedState(
+                    info.packageName, false, UserHandle.getUserId(app.uid));
+        } catch (RemoteException e) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + info.packageName + ": " + e);
+        }
+
+        if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
+                == (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT)) {
+            app.persistent = true;
+            app.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
+        }
+        if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0) {
+            mPersistentStartingProcesses.add(app);
+            startProcessLocked(app, "added application", app.processName);
+        }
+
+        return app;
+    }
+
+    public void unhandledBack() {
+        enforceCallingPermission(android.Manifest.permission.FORCE_BACK,
+                "unhandledBack()");
+
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                getFocusedStack().unhandledBackLocked();
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException {
+        enforceNotIsolatedCaller("openContentUri");
+        final int userId = UserHandle.getCallingUserId();
+        String name = uri.getAuthority();
+        ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null, userId);
+        ParcelFileDescriptor pfd = null;
+        if (cph != null) {
+            // We record the binder invoker's uid in thread-local storage before
+            // going to the content provider to open the file.  Later, in the code
+            // that handles all permissions checks, we look for this uid and use
+            // that rather than the Activity Manager's own uid.  The effect is that
+            // we do the check against the caller's permissions even though it looks
+            // to the content provider like the Activity Manager itself is making
+            // the request.
+            sCallerIdentity.set(new Identity(
+                    Binder.getCallingPid(), Binder.getCallingUid()));
+            try {
+                pfd = cph.provider.openFile(null, uri, "r", null);
+            } catch (FileNotFoundException e) {
+                // do nothing; pfd will be returned null
+            } finally {
+                // Ensure that whatever happens, we clean up the identity state
+                sCallerIdentity.remove();
+            }
+
+            // We've got the fd now, so we're done with the provider.
+            removeContentProviderExternalUnchecked(name, null, userId);
+        } else {
+            Slog.d(TAG, "Failed to get provider for authority '" + name + "'");
+        }
+        return pfd;
+    }
+
+    // Actually is sleeping or shutting down or whatever else in the future
+    // is an inactive state.
+    public boolean isSleepingOrShuttingDown() {
+        return mSleeping || mShuttingDown;
+    }
+
+    public void goingToSleep() {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
+        synchronized(this) {
+            mWentToSleep = true;
+            updateEventDispatchingLocked();
+
+            if (!mSleeping) {
+                mSleeping = true;
+                mStackSupervisor.goingToSleepLocked();
+
+                // Initialize the wake times of all processes.
+                checkExcessivePowerUsageLocked(false);
+                mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
+            }
+        }
+    }
+
+    @Override
+    public boolean shutdown(int timeout) {
+        if (checkCallingPermission(android.Manifest.permission.SHUTDOWN)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SHUTDOWN);
+        }
+
+        boolean timedout = false;
+
+        synchronized(this) {
+            mShuttingDown = true;
+            updateEventDispatchingLocked();
+            timedout = mStackSupervisor.shutdownLocked(timeout);
+        }
+
+        mAppOpsService.shutdown();
+        mUsageStatsService.shutdown();
+        mBatteryStatsService.shutdown();
+        synchronized (this) {
+            mProcessStats.shutdownLocked();
+        }
+
+        return timedout;
+    }
+    
+    public final void activitySlept(IBinder token) {
+        if (localLOGV) Slog.v(TAG, "Activity slept: token=" + token);
+
+        final long origId = Binder.clearCallingIdentity();
+
+        synchronized (this) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r != null) {
+                mStackSupervisor.activitySleptLocked(r);
+            }
+        }
+
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    void logLockScreen(String msg) {
+        if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg +
+                " mLockScreenShown=" + mLockScreenShown + " mWentToSleep=" +
+                mWentToSleep + " mSleeping=" + mSleeping + " mDismissKeyguardOnNextActivity=" +
+                mStackSupervisor.mDismissKeyguardOnNextActivity);
+    }
+
+    private void comeOutOfSleepIfNeededLocked() {
+        if (!mWentToSleep && !mLockScreenShown) {
+            if (mSleeping) {
+                mSleeping = false;
+                mStackSupervisor.comeOutOfSleepIfNeededLocked();
+            }
+        }
+    }
+
+    public void wakingUp() {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
+        synchronized(this) {
+            mWentToSleep = false;
+            updateEventDispatchingLocked();
+            comeOutOfSleepIfNeededLocked();
+        }
+    }
+
+    private void updateEventDispatchingLocked() {
+        mWindowManager.setEventDispatching(mBooted && !mWentToSleep && !mShuttingDown);
+    }
+
+    public void setLockScreenShown(boolean shown) {
+        if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.DEVICE_POWER);
+        }
+
+        synchronized(this) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (DEBUG_LOCKSCREEN) logLockScreen(" shown=" + shown);
+                mLockScreenShown = shown;
+                comeOutOfSleepIfNeededLocked();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    public void stopAppSwitches() {
+        if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.STOP_APP_SWITCHES);
+        }
+        
+        synchronized(this) {
+            mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+                    + APP_SWITCH_DELAY_TIME;
+            mDidAppSwitch = false;
+            mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+            Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+            mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
+        }
+    }
+    
+    public void resumeAppSwitches() {
+        if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.STOP_APP_SWITCHES);
+        }
+        
+        synchronized(this) {
+            // Note that we don't execute any pending app switches... we will
+            // let those wait until either the timeout, or the next start
+            // activity request.
+            mAppSwitchesAllowedTime = 0;
+        }
+    }
+    
+    boolean checkAppSwitchAllowedLocked(int callingPid, int callingUid,
+            String name) {
+        if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
+            return true;
+        }
+            
+        final int perm = checkComponentPermission(
+                android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
+                callingUid, -1, true);
+        if (perm == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        
+        Slog.w(TAG, name + " request from " + callingUid + " stopped");
+        return false;
+    }
+    
+    public void setDebugApp(String packageName, boolean waitForDebugger,
+            boolean persistent) {
+        enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
+                "setDebugApp()");
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            // Note that this is not really thread safe if there are multiple
+            // callers into it at the same time, but that's not a situation we
+            // care about.
+            if (persistent) {
+                final ContentResolver resolver = mContext.getContentResolver();
+                Settings.Global.putString(
+                    resolver, Settings.Global.DEBUG_APP,
+                    packageName);
+                Settings.Global.putInt(
+                    resolver, Settings.Global.WAIT_FOR_DEBUGGER,
+                    waitForDebugger ? 1 : 0);
+            }
+
+            synchronized (this) {
+                if (!persistent) {
+                    mOrigDebugApp = mDebugApp;
+                    mOrigWaitForDebugger = mWaitForDebugger;
+                }
+                mDebugApp = packageName;
+                mWaitForDebugger = waitForDebugger;
+                mDebugTransient = !persistent;
+                if (packageName != null) {
+                    forceStopPackageLocked(packageName, -1, false, false, true, true,
+                            UserHandle.USER_ALL, "set debug app");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    void setOpenGlTraceApp(ApplicationInfo app, String processName) {
+        synchronized (this) {
+            boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+            if (!isDebuggable) {
+                if ((app.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                    throw new SecurityException("Process not debuggable: " + app.packageName);
+                }
+            }
+
+            mOpenGlTraceApp = processName;
+        }
+    }
+
+    void setProfileApp(ApplicationInfo app, String processName, String profileFile,
+            ParcelFileDescriptor profileFd, boolean autoStopProfiler) {
+        synchronized (this) {
+            boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+            if (!isDebuggable) {
+                if ((app.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                    throw new SecurityException("Process not debuggable: " + app.packageName);
+                }
+            }
+            mProfileApp = processName;
+            mProfileFile = profileFile;
+            if (mProfileFd != null) {
+                try {
+                    mProfileFd.close();
+                } catch (IOException e) {
+                }
+                mProfileFd = null;
+            }
+            mProfileFd = profileFd;
+            mProfileType = 0;
+            mAutoStopProfiler = autoStopProfiler;
+        }
+    }
+
+    @Override
+    public void setAlwaysFinish(boolean enabled) {
+        enforceCallingPermission(android.Manifest.permission.SET_ALWAYS_FINISH,
+                "setAlwaysFinish()");
+
+        Settings.Global.putInt(
+                mContext.getContentResolver(),
+                Settings.Global.ALWAYS_FINISH_ACTIVITIES, enabled ? 1 : 0);
+        
+        synchronized (this) {
+            mAlwaysFinishActivities = enabled;
+        }
+    }
+
+    @Override
+    public void setActivityController(IActivityController controller) {
+        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "setActivityController()");
+        synchronized (this) {
+            mController = controller;
+            Watchdog.getInstance().setActivityController(controller);
+        }
+    }
+
+    @Override
+    public void setUserIsMonkey(boolean userIsMonkey) {
+        synchronized (this) {
+            synchronized (mPidsSelfLocked) {
+                final int callingPid = Binder.getCallingPid();
+                ProcessRecord precessRecord = mPidsSelfLocked.get(callingPid);
+                if (precessRecord == null) {
+                    throw new SecurityException("Unknown process: " + callingPid);
+                }
+                if (precessRecord.instrumentationUiAutomationConnection  == null) {
+                    throw new SecurityException("Only an instrumentation process "
+                            + "with a UiAutomation can call setUserIsMonkey");
+                }
+            }
+            mUserIsMonkey = userIsMonkey;
+        }
+    }
+
+    @Override
+    public boolean isUserAMonkey() {
+        synchronized (this) {
+            // If there is a controller also implies the user is a monkey.
+            return (mUserIsMonkey || mController != null);
+        }
+    }
+
+    public void requestBugReport() {
+        enforceCallingPermission(android.Manifest.permission.DUMP, "requestBugReport");
+        SystemProperties.set("ctl.start", "bugreport");
+    }
+
+    public static long getInputDispatchingTimeoutLocked(ActivityRecord r) {
+        return r != null ? getInputDispatchingTimeoutLocked(r.app) : KEY_DISPATCHING_TIMEOUT;
+    }
+
+    public static long getInputDispatchingTimeoutLocked(ProcessRecord r) {
+        if (r != null && (r.instrumentationClass != null || r.usingWrapper)) {
+            return INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT;
+        }
+        return KEY_DISPATCHING_TIMEOUT;
+    }
+
+    @Override
+    public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {
+        if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.FILTER_EVENTS);
+        }
+        ProcessRecord proc;
+        long timeout;
+        synchronized (this) {
+            synchronized (mPidsSelfLocked) {
+                proc = mPidsSelfLocked.get(pid);
+            }
+            timeout = getInputDispatchingTimeoutLocked(proc);
+        }
+
+        if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) {
+            return -1;
+        }
+
+        return timeout;
+    }
+
+    /**
+     * Handle input dispatching timeouts.
+     * Returns whether input dispatching should be aborted or not.
+     */
+    public boolean inputDispatchingTimedOut(final ProcessRecord proc,
+            final ActivityRecord activity, final ActivityRecord parent,
+            final boolean aboveSystem, String reason) {
+        if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.FILTER_EVENTS);
+        }
+
+        final String annotation;
+        if (reason == null) {
+            annotation = "Input dispatching timed out";
+        } else {
+            annotation = "Input dispatching timed out (" + reason + ")";
+        }
+
+        if (proc != null) {
+            synchronized (this) {
+                if (proc.debugging) {
+                    return false;
+                }
+
+                if (mDidDexOpt) {
+                    // Give more time since we were dexopting.
+                    mDidDexOpt = false;
+                    return false;
+                }
+
+                if (proc.instrumentationClass != null) {
+                    Bundle info = new Bundle();
+                    info.putString("shortMsg", "keyDispatchingTimedOut");
+                    info.putString("longMsg", annotation);
+                    finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info);
+                    return true;
+                }
+            }
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    appNotResponding(proc, activity, parent, aboveSystem, annotation);
+                }
+            });
+        }
+
+        return true;
+    }
+
+    public Bundle getAssistContextExtras(int requestType) {
+        enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO,
+                "getAssistContextExtras()");
+        PendingAssistExtras pae;
+        Bundle extras = new Bundle();
+        synchronized (this) {
+            ActivityRecord activity = getFocusedStack().mResumedActivity;
+            if (activity == null) {
+                Slog.w(TAG, "getAssistContextExtras failed: no resumed activity");
+                return null;
+            }
+            extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName);
+            if (activity.app == null || activity.app.thread == null) {
+                Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity);
+                return extras;
+            }
+            if (activity.app.pid == Binder.getCallingPid()) {
+                Slog.w(TAG, "getAssistContextExtras failed: request process same as " + activity);
+                return extras;
+            }
+            pae = new PendingAssistExtras(activity);
+            try {
+                activity.app.thread.requestAssistContextExtras(activity.appToken, pae,
+                        requestType);
+                mPendingAssistExtras.add(pae);
+                mHandler.postDelayed(pae, PENDING_ASSIST_EXTRAS_TIMEOUT);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "getAssistContextExtras failed: crash calling " + activity);
+                return extras;
+            }
+        }
+        synchronized (pae) {
+            while (!pae.haveResult) {
+                try {
+                    pae.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+            if (pae.result != null) {
+                extras.putBundle(Intent.EXTRA_ASSIST_CONTEXT, pae.result);
+            }
+        }
+        synchronized (this) {
+            mPendingAssistExtras.remove(pae);
+            mHandler.removeCallbacks(pae);
+        }
+        return extras;
+    }
+
+    public void reportAssistContextExtras(IBinder token, Bundle extras) {
+        PendingAssistExtras pae = (PendingAssistExtras)token;
+        synchronized (pae) {
+            pae.result = extras;
+            pae.haveResult = true;
+            pae.notifyAll();
+        }
+    }
+
+    public void registerProcessObserver(IProcessObserver observer) {
+        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
+                "registerProcessObserver()");
+        synchronized (this) {
+            mProcessObservers.register(observer);
+        }
+    }
+
+    @Override
+    public void unregisterProcessObserver(IProcessObserver observer) {
+        synchronized (this) {
+            mProcessObservers.unregister(observer);
+        }
+    }
+
+    @Override
+    public boolean convertFromTranslucent(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    return false;
+                }
+                if (r.changeWindowTranslucency(true)) {
+                    mWindowManager.setAppFullscreen(token, true);
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+                    return true;
+                }
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public boolean convertToTranslucent(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r == null) {
+                    return false;
+                }
+                if (r.changeWindowTranslucency(false)) {
+                    r.task.stack.convertToTranslucent(r);
+                    mWindowManager.setAppFullscreen(token, false);
+                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
+                    return true;
+                }
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void setImmersive(IBinder token, boolean immersive) {
+        synchronized(this) {
+            final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                throw new IllegalArgumentException();
+            }
+            r.immersive = immersive;
+
+            // update associated state if we're frontmost
+            if (r == mFocusedActivity) {
+                if (DEBUG_IMMERSIVE) {
+                    Slog.d(TAG, "Frontmost changed immersion: "+ r);
+                }
+                applyUpdateLockStateLocked(r);
+            }
+        }
+    }
+
+    @Override
+    public boolean isImmersive(IBinder token) {
+        synchronized (this) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                throw new IllegalArgumentException();
+            }
+            return r.immersive;
+        }
+    }
+
+    public boolean isTopActivityImmersive() {
+        enforceNotIsolatedCaller("startActivity");
+        synchronized (this) {
+            ActivityRecord r = getFocusedStack().topRunningActivityLocked(null);
+            return (r != null) ? r.immersive : false;
+        }
+    }
+
+    public final void enterSafeMode() {
+        synchronized(this) {
+            // It only makes sense to do this before the system is ready
+            // and started launching other packages.
+            if (!mSystemReady) {
+                try {
+                    AppGlobals.getPackageManager().enterSafeMode();
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    public final void showSafeModeOverlay() {
+        View v = LayoutInflater.from(mContext).inflate(
+                com.android.internal.R.layout.safe_mode, null);
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
+        lp.type = WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+        lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        lp.gravity = Gravity.BOTTOM | Gravity.START;
+        lp.format = v.getBackground().getOpacity();
+        lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        ((WindowManager)mContext.getSystemService(
+                Context.WINDOW_SERVICE)).addView(v, lp);
+    }
+
+    public void noteWakeupAlarm(IIntentSender sender) {
+        if (!(sender instanceof PendingIntentRecord)) {
+            return;
+        }
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        synchronized (stats) {
+            if (mBatteryStatsService.isOnBattery()) {
+                mBatteryStatsService.enforceCallingPermission();
+                PendingIntentRecord rec = (PendingIntentRecord)sender;
+                int MY_UID = Binder.getCallingUid();
+                int uid = rec.uid == MY_UID ? Process.SYSTEM_UID : rec.uid;
+                BatteryStatsImpl.Uid.Pkg pkg =
+                    stats.getPackageStatsLocked(uid, rec.key.packageName);
+                pkg.incWakeupsLocked();
+            }
+        }
+    }
+
+    public boolean killPids(int[] pids, String pReason, boolean secure) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killPids only available to the system");
+        }
+        String reason = (pReason == null) ? "Unknown" : pReason;
+        // XXX Note: don't acquire main activity lock here, because the window
+        // manager calls in with its locks held.
+
+        boolean killed = false;
+        synchronized (mPidsSelfLocked) {
+            int[] types = new int[pids.length];
+            int worstType = 0;
+            for (int i=0; i<pids.length; i++) {
+                ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+                if (proc != null) {
+                    int type = proc.setAdj;
+                    types[i] = type;
+                    if (type > worstType) {
+                        worstType = type;
+                    }
+                }
+            }
+
+            // If the worst oom_adj is somewhere in the cached proc LRU range,
+            // then constrain it so we will kill all cached procs.
+            if (worstType < ProcessList.CACHED_APP_MAX_ADJ
+                    && worstType > ProcessList.CACHED_APP_MIN_ADJ) {
+                worstType = ProcessList.CACHED_APP_MIN_ADJ;
+            }
+
+            // If this is not a secure call, don't let it kill processes that
+            // are important.
+            if (!secure && worstType < ProcessList.SERVICE_ADJ) {
+                worstType = ProcessList.SERVICE_ADJ;
+            }
+
+            Slog.w(TAG, "Killing processes " + reason + " at adjustment " + worstType);
+            for (int i=0; i<pids.length; i++) {
+                ProcessRecord proc = mPidsSelfLocked.get(pids[i]);
+                if (proc == null) {
+                    continue;
+                }
+                int adj = proc.setAdj;
+                if (adj >= worstType && !proc.killedByAm) {
+                    killUnneededProcessLocked(proc, reason);
+                    killed = true;
+                }
+            }
+        }
+        return killed;
+    }
+
+    @Override
+    public void killUid(int uid, String reason) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killUid only available to the system");
+        }
+        synchronized (this) {
+            killPackageProcessesLocked(null, UserHandle.getAppId(uid), UserHandle.getUserId(uid),
+                    ProcessList.FOREGROUND_APP_ADJ-1, false, true, true, false,
+                    reason != null ? reason : "kill uid");
+        }
+    }
+
+    @Override
+    public boolean killProcessesBelowForeground(String reason) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killProcessesBelowForeground() only available to system");
+        }
+
+        return killProcessesBelowAdj(ProcessList.FOREGROUND_APP_ADJ, reason);
+    }
+
+    private boolean killProcessesBelowAdj(int belowAdj, String reason) {
+        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("killProcessesBelowAdj() only available to system");
+        }
+
+        boolean killed = false;
+        synchronized (mPidsSelfLocked) {
+            final int size = mPidsSelfLocked.size();
+            for (int i = 0; i < size; i++) {
+                final int pid = mPidsSelfLocked.keyAt(i);
+                final ProcessRecord proc = mPidsSelfLocked.valueAt(i);
+                if (proc == null) continue;
+
+                final int adj = proc.setAdj;
+                if (adj > belowAdj && !proc.killedByAm) {
+                    killUnneededProcessLocked(proc, reason);
+                    killed = true;
+                }
+            }
+        }
+        return killed;
+    }
+
+    @Override
+    public void hang(final IBinder who, boolean allowRestart) {
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
+
+        final IBinder.DeathRecipient death = new DeathRecipient() {
+            @Override
+            public void binderDied() {
+                synchronized (this) {
+                    notifyAll();
+                }
+            }
+        };
+
+        try {
+            who.linkToDeath(death, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "hang: given caller IBinder is already dead.");
+            return;
+        }
+
+        synchronized (this) {
+            Watchdog.getInstance().setAllowRestart(allowRestart);
+            Slog.i(TAG, "Hanging system process at request of pid " + Binder.getCallingPid());
+            synchronized (death) {
+                while (who.isBinderAlive()) {
+                    try {
+                        death.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+            Watchdog.getInstance().setAllowRestart(true);
+        }
+    }
+
+    @Override
+    public void restart() {
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
+
+        Log.i(TAG, "Sending shutdown broadcast...");
+
+        BroadcastReceiver br = new BroadcastReceiver() {
+            @Override public void onReceive(Context context, Intent intent) {
+                // Now the broadcast is done, finish up the low-level shutdown.
+                Log.i(TAG, "Shutting down activity manager...");
+                shutdown(10000);
+                Log.i(TAG, "Shutdown complete, restarting!");
+                Process.killProcess(Process.myPid());
+                System.exit(10);
+            }
+        };
+
+        // First send the high-level shut down broadcast.
+        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        intent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
+        /* For now we are not doing a clean shutdown, because things seem to get unhappy.
+        mContext.sendOrderedBroadcastAsUser(intent,
+                UserHandle.ALL, null, br, mHandler, 0, null, null);
+        */
+        br.onReceive(mContext, intent);
+    }
+
+    private long getLowRamTimeSinceIdle(long now) {
+        return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now-mLowRamStartTime) : 0);
+    }
+
+    @Override
+    public void performIdleMaintenance() {
+        if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires permission "
+                    + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+        }
+
+        synchronized (this) {
+            final long now = SystemClock.uptimeMillis();
+            final long timeSinceLastIdle = now - mLastIdleTime;
+            final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
+            mLastIdleTime = now;
+            mLowRamTimeSinceLastIdle = 0;
+            if (mLowRamStartTime != 0) {
+                mLowRamStartTime = now;
+            }
+
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Idle maintenance over ");
+            TimeUtils.formatDuration(timeSinceLastIdle, sb);
+            sb.append(" low RAM for ");
+            TimeUtils.formatDuration(lowRamSinceLastIdle, sb);
+            Slog.i(TAG, sb.toString());
+
+            // If at least 1/3 of our time since the last idle period has been spent
+            // with RAM low, then we want to kill processes.
+            boolean doKilling = lowRamSinceLastIdle > (timeSinceLastIdle/3);
+
+            for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                ProcessRecord proc = mLruProcesses.get(i);
+                if (proc.notCachedSinceIdle) {
+                    if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP
+                            && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
+                        if (doKilling && proc.initialIdlePss != 0
+                                && proc.lastPss > ((proc.initialIdlePss*3)/2)) {
+                            killUnneededProcessLocked(proc, "idle maint (pss " + proc.lastPss
+                                    + " from " + proc.initialIdlePss + ")");
+                        }
+                    }
+                } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME) {
+                    proc.notCachedSinceIdle = true;
+                    proc.initialIdlePss = 0;
+                    proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true,
+                            mSleeping, now);
+                }
+            }
+
+            mHandler.removeMessages(REQUEST_ALL_PSS_MSG);
+            mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000);
+        }
+    }
+
+    public final void startRunning(String pkg, String cls, String action,
+            String data) {
+        synchronized(this) {
+            if (mStartRunning) {
+                return;
+            }
+            mStartRunning = true;
+            mTopComponent = pkg != null && cls != null
+                    ? new ComponentName(pkg, cls) : null;
+            mTopAction = action != null ? action : Intent.ACTION_MAIN;
+            mTopData = data;
+            if (!mSystemReady) {
+                return;
+            }
+        }
+
+        systemReady(null);
+    }
+
+    private void retrieveSettings() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        String debugApp = Settings.Global.getString(
+            resolver, Settings.Global.DEBUG_APP);
+        boolean waitForDebugger = Settings.Global.getInt(
+            resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0;
+        boolean alwaysFinishActivities = Settings.Global.getInt(
+            resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0;
+        boolean forceRtl = Settings.Global.getInt(
+                resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0;
+        // Transfer any global setting for forcing RTL layout, into a System Property
+        SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
+
+        Configuration configuration = new Configuration();
+        Settings.System.getConfiguration(resolver, configuration);
+        if (forceRtl) {
+            // This will take care of setting the correct layout direction flags
+            configuration.setLayoutDirection(configuration.locale);
+        }
+
+        synchronized (this) {
+            mDebugApp = mOrigDebugApp = debugApp;
+            mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
+            mAlwaysFinishActivities = alwaysFinishActivities;
+            // This happens before any activities are started, so we can
+            // change mConfiguration in-place.
+            updateConfigurationLocked(configuration, null, false, true);
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Initial config: " + mConfiguration);
+        }
+    }
+
+    public boolean testIsSystemReady() {
+        // no need to synchronize(this) just to read & return the value
+        return mSystemReady;
+    }
+
+    private static File getCalledPreBootReceiversFile() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File fname = new File(systemDir, "called_pre_boots.dat");
+        return fname;
+    }
+
+    static final int LAST_DONE_VERSION = 10000;
+
+    private static ArrayList<ComponentName> readLastDonePreBootReceivers() {
+        ArrayList<ComponentName> lastDoneReceivers = new ArrayList<ComponentName>();
+        File file = getCalledPreBootReceiversFile();
+        FileInputStream fis = null;
+        try {
+            fis = new FileInputStream(file);
+            DataInputStream dis = new DataInputStream(new BufferedInputStream(fis, 2048));
+            int fvers = dis.readInt();
+            if (fvers == LAST_DONE_VERSION) {
+                String vers = dis.readUTF();
+                String codename = dis.readUTF();
+                String build = dis.readUTF();
+                if (android.os.Build.VERSION.RELEASE.equals(vers)
+                        && android.os.Build.VERSION.CODENAME.equals(codename)
+                        && android.os.Build.VERSION.INCREMENTAL.equals(build)) {
+                    int num = dis.readInt();
+                    while (num > 0) {
+                        num--;
+                        String pkg = dis.readUTF();
+                        String cls = dis.readUTF();
+                        lastDoneReceivers.add(new ComponentName(pkg, cls));
+                    }
+                }
+            }
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
+            Slog.w(TAG, "Failure reading last done pre-boot receivers", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return lastDoneReceivers;
+    }
+    
+    private static void writeLastDonePreBootReceivers(ArrayList<ComponentName> list) {
+        File file = getCalledPreBootReceiversFile();
+        FileOutputStream fos = null;
+        DataOutputStream dos = null;
+        try {
+            Slog.i(TAG, "Writing new set of last done pre-boot receivers...");
+            fos = new FileOutputStream(file);
+            dos = new DataOutputStream(new BufferedOutputStream(fos, 2048));
+            dos.writeInt(LAST_DONE_VERSION);
+            dos.writeUTF(android.os.Build.VERSION.RELEASE);
+            dos.writeUTF(android.os.Build.VERSION.CODENAME);
+            dos.writeUTF(android.os.Build.VERSION.INCREMENTAL);
+            dos.writeInt(list.size());
+            for (int i=0; i<list.size(); i++) {
+                dos.writeUTF(list.get(i).getPackageName());
+                dos.writeUTF(list.get(i).getClassName());
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Failure writing last done pre-boot receivers", e);
+            file.delete();
+        } finally {
+            FileUtils.sync(fos);
+            if (dos != null) {
+                try {
+                    dos.close();
+                } catch (IOException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+    
+    public void systemReady(final Runnable goingCallback) {
+        synchronized(this) {
+            if (mSystemReady) {
+                if (goingCallback != null) goingCallback.run();
+                return;
+            }
+            
+            // Check to see if there are any update receivers to run.
+            if (!mDidUpdate) {
+                if (mWaitingUpdate) {
+                    return;
+                }
+                Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+                List<ResolveInfo> ris = null;
+                try {
+                    ris = AppGlobals.getPackageManager().queryIntentReceivers(
+                            intent, null, 0, 0);
+                } catch (RemoteException e) {
+                }
+                if (ris != null) {
+                    for (int i=ris.size()-1; i>=0; i--) {
+                        if ((ris.get(i).activityInfo.applicationInfo.flags
+                                &ApplicationInfo.FLAG_SYSTEM) == 0) {
+                            ris.remove(i);
+                        }
+                    }
+                    intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
+
+                    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
+
+                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
+                    for (int i=0; i<ris.size(); i++) {
+                        ActivityInfo ai = ris.get(i).activityInfo;
+                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                        if (lastDoneReceivers.contains(comp)) {
+                            ris.remove(i);
+                            i--;
+                        }
+                    }
+
+                    final int[] users = getUsersLocked();
+                    for (int i=0; i<ris.size(); i++) {
+                        ActivityInfo ai = ris.get(i).activityInfo;
+                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
+                        doneReceivers.add(comp);
+                        intent.setComponent(comp);
+                        for (int j=0; j<users.length; j++) {
+                            IIntentReceiver finisher = null;
+                            if (i == ris.size()-1 && j == users.length-1) {
+                                finisher = new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        // The raw IIntentReceiver interface is called
+                                        // with the AM lock held, so redispatch to
+                                        // execute our code without the lock.
+                                        mHandler.post(new Runnable() {
+                                            public void run() {
+                                                synchronized (ActivityManagerService.this) {
+                                                    mDidUpdate = true;
+                                                }
+                                                writeLastDonePreBootReceivers(doneReceivers);
+                                                showBootMessage(mContext.getText(
+                                                        R.string.android_upgrading_complete),
+                                                        false);
+                                                systemReady(goingCallback);
+                                            }
+                                        });
+                                    }
+                                };
+                            }
+                            Slog.i(TAG, "Sending system update to " + intent.getComponent()
+                                    + " for user " + users[j]);
+                            broadcastIntentLocked(null, null, intent, null, finisher,
+                                    0, null, null, null, AppOpsManager.OP_NONE,
+                                    true, false, MY_PID, Process.SYSTEM_UID,
+                                    users[j]);
+                            if (finisher != null) {
+                                mWaitingUpdate = true;
+                            }
+                        }
+                    }
+                }
+                if (mWaitingUpdate) {
+                    return;
+                }
+                mDidUpdate = true;
+            }
+
+            mAppOpsService.systemReady();
+            mSystemReady = true;
+            if (!mStartRunning) {
+                return;
+            }
+        }
+
+        ArrayList<ProcessRecord> procsToKill = null;
+        synchronized(mPidsSelfLocked) {
+            for (int i=mPidsSelfLocked.size()-1; i>=0; i--) {
+                ProcessRecord proc = mPidsSelfLocked.valueAt(i);
+                if (!isAllowedWhileBooting(proc.info)){
+                    if (procsToKill == null) {
+                        procsToKill = new ArrayList<ProcessRecord>();
+                    }
+                    procsToKill.add(proc);
+                }
+            }
+        }
+        
+        synchronized(this) {
+            if (procsToKill != null) {
+                for (int i=procsToKill.size()-1; i>=0; i--) {
+                    ProcessRecord proc = procsToKill.get(i);
+                    Slog.i(TAG, "Removing system update proc: " + proc);
+                    removeProcessLocked(proc, true, false, "system update done");
+                }
+            }
+            
+            // Now that we have cleaned up any update processes, we
+            // are ready to start launching real processes and know that
+            // we won't trample on them any more.
+            mProcessesReady = true;
+        }
+        
+        Slog.i(TAG, "System now ready");
+        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_AMS_READY,
+            SystemClock.uptimeMillis());
+
+        synchronized(this) {
+            // Make sure we have no pre-ready processes sitting around.
+            
+            if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {
+                ResolveInfo ri = mContext.getPackageManager()
+                        .resolveActivity(new Intent(Intent.ACTION_FACTORY_TEST),
+                                STOCK_PM_FLAGS);
+                CharSequence errorMsg = null;
+                if (ri != null) {
+                    ActivityInfo ai = ri.activityInfo;
+                    ApplicationInfo app = ai.applicationInfo;
+                    if ((app.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+                        mTopAction = Intent.ACTION_FACTORY_TEST;
+                        mTopData = null;
+                        mTopComponent = new ComponentName(app.packageName,
+                                ai.name);
+                    } else {
+                        errorMsg = mContext.getResources().getText(
+                                com.android.internal.R.string.factorytest_not_system);
+                    }
+                } else {
+                    errorMsg = mContext.getResources().getText(
+                            com.android.internal.R.string.factorytest_no_action);
+                }
+                if (errorMsg != null) {
+                    mTopAction = null;
+                    mTopData = null;
+                    mTopComponent = null;
+                    Message msg = Message.obtain();
+                    msg.what = SHOW_FACTORY_ERROR_MSG;
+                    msg.getData().putCharSequence("msg", errorMsg);
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+
+        retrieveSettings();
+
+        synchronized (this) {
+            readGrantedUriPermissionsLocked();
+        }
+
+        if (goingCallback != null) goingCallback.run();
+        
+        synchronized (this) {
+            if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
+                try {
+                    List apps = AppGlobals.getPackageManager().
+                        getPersistentApplications(STOCK_PM_FLAGS);
+                    if (apps != null) {
+                        int N = apps.size();
+                        int i;
+                        for (i=0; i<N; i++) {
+                            ApplicationInfo info
+                                = (ApplicationInfo)apps.get(i);
+                            if (info != null &&
+                                    !info.packageName.equals("android")) {
+                                addAppLocked(info, false);
+                            }
+                        }
+                    }
+                } catch (RemoteException ex) {
+                    // pm is in same process, this will never happen.
+                }
+            }
+
+            // Start up initial activity.
+            mBooting = true;
+            
+            try {
+                if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
+                    Message msg = Message.obtain();
+                    msg.what = SHOW_UID_ERROR_MSG;
+                    mHandler.sendMessage(msg);
+                }
+            } catch (RemoteException e) {
+            }
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        false, false, MY_PID, Process.SYSTEM_UID, mCurrentUserId);
+                intent = new Intent(Intent.ACTION_USER_STARTING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, mCurrentUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode, String data,
+                                    Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                                    throws RemoteException {
+                            }
+                        }, 0, null, null,
+                        android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                        true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            mStackSupervisor.resumeTopActivitiesLocked();
+            sendUserSwitchBroadcastsLocked(-1, mCurrentUserId);
+        }
+    }
+
+    private boolean makeAppCrashingLocked(ProcessRecord app,
+            String shortMsg, String longMsg, String stackTrace) {
+        app.crashing = true;
+        app.crashingReport = generateProcessError(app,
+                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
+        startAppProblemLocked(app);
+        app.stopFreezingAllLocked();
+        return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
+    }
+
+    private void makeAppNotRespondingLocked(ProcessRecord app,
+            String activity, String shortMsg, String longMsg) {
+        app.notResponding = true;
+        app.notRespondingReport = generateProcessError(app,
+                ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+                activity, shortMsg, longMsg, null);
+        startAppProblemLocked(app);
+        app.stopFreezingAllLocked();
+    }
+    
+    /**
+     * Generate a process error record, suitable for attachment to a ProcessRecord.
+     * 
+     * @param app The ProcessRecord in which the error occurred.
+     * @param condition Crashing, Application Not Responding, etc.  Values are defined in 
+     *                      ActivityManager.AppErrorStateInfo
+     * @param activity The activity associated with the crash, if known.
+     * @param shortMsg Short message describing the crash.
+     * @param longMsg Long message describing the crash.
+     * @param stackTrace Full crash stack trace, may be null.
+     *
+     * @return Returns a fully-formed AppErrorStateInfo record.
+     */
+    private ActivityManager.ProcessErrorStateInfo generateProcessError(ProcessRecord app, 
+            int condition, String activity, String shortMsg, String longMsg, String stackTrace) {
+        ActivityManager.ProcessErrorStateInfo report = new ActivityManager.ProcessErrorStateInfo();
+
+        report.condition = condition;
+        report.processName = app.processName;
+        report.pid = app.pid;
+        report.uid = app.info.uid;
+        report.tag = activity;
+        report.shortMsg = shortMsg;
+        report.longMsg = longMsg;
+        report.stackTrace = stackTrace;
+
+        return report;
+    }
+
+    void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
+        synchronized (this) {
+            app.crashing = false;
+            app.crashingReport = null;
+            app.notResponding = false;
+            app.notRespondingReport = null;
+            if (app.anrDialog == fromDialog) {
+                app.anrDialog = null;
+            }
+            if (app.waitDialog == fromDialog) {
+                app.waitDialog = null;
+            }
+            if (app.pid > 0 && app.pid != MY_PID) {
+                handleAppCrashLocked(app, null, null, null);
+                killUnneededProcessLocked(app, "user request after error");
+            }
+        }
+    }
+
+    private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
+            String stackTrace) {
+        long now = SystemClock.uptimeMillis();
+
+        Long crashTime;
+        if (!app.isolated) {
+            crashTime = mProcessCrashTimes.get(app.info.processName, app.uid);
+        } else {
+            crashTime = null;
+        }
+        if (crashTime != null && now < crashTime+ProcessList.MIN_CRASH_INTERVAL) {
+            // This process loses!
+            Slog.w(TAG, "Process " + app.info.processName
+                    + " has crashed too many times: killing!");
+            EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH,
+                    app.userId, app.info.processName, app.uid);
+            mStackSupervisor.handleAppCrashLocked(app);
+            if (!app.persistent) {
+                // We don't want to start this process again until the user
+                // explicitly does so...  but for persistent process, we really
+                // need to keep it running.  If a persistent process is actually
+                // repeatedly crashing, then badness for everyone.
+                EventLog.writeEvent(EventLogTags.AM_PROC_BAD, app.userId, app.uid,
+                        app.info.processName);
+                if (!app.isolated) {
+                    // XXX We don't have a way to mark isolated processes
+                    // as bad, since they don't have a peristent identity.
+                    mBadProcesses.put(app.info.processName, app.uid,
+                            new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
+                    mProcessCrashTimes.remove(app.info.processName, app.uid);
+                }
+                app.bad = true;
+                app.removed = true;
+                // Don't let services in this process be restarted and potentially
+                // annoy the user repeatedly.  Unless it is persistent, since those
+                // processes run critical code.
+                removeProcessLocked(app, false, false, "crash");
+                mStackSupervisor.resumeTopActivitiesLocked();
+                return false;
+            }
+            mStackSupervisor.resumeTopActivitiesLocked();
+        } else {
+            mStackSupervisor.finishTopRunningActivityLocked(app);
+        }
+
+        // Bump up the crash count of any services currently running in the proc.
+        for (int i=app.services.size()-1; i>=0; i--) {
+            // Any services running in the application need to be placed
+            // back in the pending list.
+            ServiceRecord sr = app.services.valueAt(i);
+            sr.crashCount++;
+        }
+
+        // If the crashing process is what we consider to be the "home process" and it has been
+        // replaced by a third-party app, clear the package preferred activities from packages
+        // with a home activity running in the process to prevent a repeatedly crashing app
+        // from blocking the user to manually clear the list.
+        final ArrayList<ActivityRecord> activities = app.activities;
+        if (app == mHomeProcess && activities.size() > 0
+                    && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.isHomeActivity()) {
+                    Log.i(TAG, "Clearing package preferred activities from " + r.packageName);
+                    try {
+                        ActivityThread.getPackageManager()
+                                .clearPackagePreferredActivities(r.packageName);
+                    } catch (RemoteException c) {
+                        // pm is in same process, this will never happen.
+                    }
+                }
+            }
+        }
+
+        if (!app.isolated) {
+            // XXX Can't keep track of crash times for isolated processes,
+            // because they don't have a perisistent identity.
+            mProcessCrashTimes.put(app.info.processName, app.uid, now);
+        }
+
+        return true;
+    }
+
+    void startAppProblemLocked(ProcessRecord app) {
+        if (app.userId == mCurrentUserId) {
+            app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
+                    mContext, app.info.packageName, app.info.flags);
+        } else {
+            // If this app is not running under the current user, then we
+            // can't give it a report button because that would require
+            // launching the report UI under a different user.
+            app.errorReportReceiver = null;
+        }
+        skipCurrentReceiverLocked(app);
+    }
+
+    void skipCurrentReceiverLocked(ProcessRecord app) {
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            queue.skipCurrentReceiverLocked(app);
+        }
+    }
+
+    /**
+     * Used by {@link com.android.internal.os.RuntimeInit} to report when an application crashes.
+     * The application process will exit immediately after this call returns.
+     * @param app object of the crashing app, null for the system server
+     * @param crashInfo describing the exception
+     */
+    public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
+        ProcessRecord r = findAppProcess(app, "Crash");
+        final String processName = app == null ? "system_server"
+                : (r == null ? "unknown" : r.processName);
+
+        handleApplicationCrashInner("crash", r, processName, crashInfo);
+    }
+
+    /* Native crash reporting uses this inner version because it needs to be somewhat
+     * decoupled from the AM-managed cleanup lifecycle
+     */
+    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
+            ApplicationErrorReport.CrashInfo crashInfo) {
+        EventLog.writeEvent(EventLogTags.AM_CRASH, Binder.getCallingPid(),
+                UserHandle.getUserId(Binder.getCallingUid()), processName,
+                r == null ? -1 : r.info.flags,
+                crashInfo.exceptionClassName,
+                crashInfo.exceptionMessage,
+                crashInfo.throwFileName,
+                crashInfo.throwLineNumber);
+
+        addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
+
+        crashApplication(r, crashInfo);
+    }
+
+    public void handleApplicationStrictModeViolation(
+            IBinder app,
+            int violationMask,
+            StrictMode.ViolationInfo info) {
+        ProcessRecord r = findAppProcess(app, "StrictMode");
+        if (r == null) {
+            return;
+        }
+
+        if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
+            Integer stackFingerprint = info.hashCode();
+            boolean logIt = true;
+            synchronized (mAlreadyLoggedViolatedStacks) {
+                if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) {
+                    logIt = false;
+                    // TODO: sub-sample into EventLog for these, with
+                    // the info.durationMillis?  Then we'd get
+                    // the relative pain numbers, without logging all
+                    // the stack traces repeatedly.  We'd want to do
+                    // likewise in the client code, which also does
+                    // dup suppression, before the Binder call.
+                } else {
+                    if (mAlreadyLoggedViolatedStacks.size() >= MAX_DUP_SUPPRESSED_STACKS) {
+                        mAlreadyLoggedViolatedStacks.clear();
+                    }
+                    mAlreadyLoggedViolatedStacks.add(stackFingerprint);
+                }
+            }
+            if (logIt) {
+                logStrictModeViolationToDropBox(r, info);
+            }
+        }
+
+        if ((violationMask & StrictMode.PENALTY_DIALOG) != 0) {
+            AppErrorResult result = new AppErrorResult();
+            synchronized (this) {
+                final long origId = Binder.clearCallingIdentity();
+
+                Message msg = Message.obtain();
+                msg.what = SHOW_STRICT_MODE_VIOLATION_MSG;
+                HashMap<String, Object> data = new HashMap<String, Object>();
+                data.put("result", result);
+                data.put("app", r);
+                data.put("violationMask", violationMask);
+                data.put("info", info);
+                msg.obj = data;
+                mHandler.sendMessage(msg);
+
+                Binder.restoreCallingIdentity(origId);
+            }
+            int res = result.get();
+            Slog.w(TAG, "handleApplicationStrictModeViolation; res=" + res);
+        }
+    }
+
+    // Depending on the policy in effect, there could be a bunch of
+    // these in quick succession so we try to batch these together to
+    // minimize disk writes, number of dropbox entries, and maximize
+    // compression, by having more fewer, larger records.
+    private void logStrictModeViolationToDropBox(
+            ProcessRecord process,
+            StrictMode.ViolationInfo info) {
+        if (info == null) {
+            return;
+        }
+        final boolean isSystemApp = process == null ||
+                (process.info.flags & (ApplicationInfo.FLAG_SYSTEM |
+                                       ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0;
+        final String processName = process == null ? "unknown" : process.processName;
+        final String dropboxTag = isSystemApp ? "system_app_strictmode" : "data_app_strictmode";
+        final DropBoxManager dbox = (DropBoxManager)
+                mContext.getSystemService(Context.DROPBOX_SERVICE);
+
+        // Exit early if the dropbox isn't configured to accept this report type.
+        if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+
+        boolean bufferWasEmpty;
+        boolean needsFlush;
+        final StringBuilder sb = isSystemApp ? mStrictModeBuffer : new StringBuilder(1024);
+        synchronized (sb) {
+            bufferWasEmpty = sb.length() == 0;
+            appendDropBoxProcessHeaders(process, processName, sb);
+            sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
+            sb.append("System-App: ").append(isSystemApp).append("\n");
+            sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n");
+            if (info.violationNumThisLoop != 0) {
+                sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n");
+            }
+            if (info.numAnimationsRunning != 0) {
+                sb.append("Animations-Running: ").append(info.numAnimationsRunning).append("\n");
+            }
+            if (info.broadcastIntentAction != null) {
+                sb.append("Broadcast-Intent-Action: ").append(info.broadcastIntentAction).append("\n");
+            }
+            if (info.durationMillis != -1) {
+                sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
+            }
+            if (info.numInstances != -1) {
+                sb.append("Instance-Count: ").append(info.numInstances).append("\n");
+            }
+            if (info.tags != null) {
+                for (String tag : info.tags) {
+                    sb.append("Span-Tag: ").append(tag).append("\n");
+                }
+            }
+            sb.append("\n");
+            if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
+                sb.append(info.crashInfo.stackTrace);
+            }
+            sb.append("\n");
+
+            // Only buffer up to ~64k.  Various logging bits truncate
+            // things at 128k.
+            needsFlush = (sb.length() > 64 * 1024);
+        }
+
+        // Flush immediately if the buffer's grown too large, or this
+        // is a non-system app.  Non-system apps are isolated with a
+        // different tag & policy and not batched.
+        //
+        // Batching is useful during internal testing with
+        // StrictMode settings turned up high.  Without batching,
+        // thousands of separate files could be created on boot.
+        if (!isSystemApp || needsFlush) {
+            new Thread("Error dump: " + dropboxTag) {
+                @Override
+                public void run() {
+                    String report;
+                    synchronized (sb) {
+                        report = sb.toString();
+                        sb.delete(0, sb.length());
+                        sb.trimToSize();
+                    }
+                    if (report.length() != 0) {
+                        dbox.addText(dropboxTag, report);
+                    }
+                }
+            }.start();
+            return;
+        }
+
+        // System app batching:
+        if (!bufferWasEmpty) {
+            // An existing dropbox-writing thread is outstanding, so
+            // we don't need to start it up.  The existing thread will
+            // catch the buffer appends we just did.
+            return;
+        }
+
+        // Worker thread to both batch writes and to avoid blocking the caller on I/O.
+        // (After this point, we shouldn't access AMS internal data structures.)
+        new Thread("Error dump: " + dropboxTag) {
+            @Override
+            public void run() {
+                // 5 second sleep to let stacks arrive and be batched together
+                try {
+                    Thread.sleep(5000);  // 5 seconds
+                } catch (InterruptedException e) {}
+
+                String errorReport;
+                synchronized (mStrictModeBuffer) {
+                    errorReport = mStrictModeBuffer.toString();
+                    if (errorReport.length() == 0) {
+                        return;
+                    }
+                    mStrictModeBuffer.delete(0, mStrictModeBuffer.length());
+                    mStrictModeBuffer.trimToSize();
+                }
+                dbox.addText(dropboxTag, errorReport);
+            }
+        }.start();
+    }
+
+    /**
+     * Used by {@link Log} via {@link com.android.internal.os.RuntimeInit} to report serious errors.
+     * @param app object of the crashing app, null for the system server
+     * @param tag reported by the caller
+     * @param crashInfo describing the context of the error
+     * @return true if the process should exit immediately (WTF is fatal)
+     */
+    public boolean handleApplicationWtf(IBinder app, String tag,
+            ApplicationErrorReport.CrashInfo crashInfo) {
+        ProcessRecord r = findAppProcess(app, "WTF");
+        final String processName = app == null ? "system_server"
+                : (r == null ? "unknown" : r.processName);
+
+        EventLog.writeEvent(EventLogTags.AM_WTF,
+                UserHandle.getUserId(Binder.getCallingUid()), Binder.getCallingPid(),
+                processName,
+                r == null ? -1 : r.info.flags,
+                tag, crashInfo.exceptionMessage);
+
+        addErrorToDropBox("wtf", r, processName, null, null, tag, null, null, crashInfo);
+
+        if (r != null && r.pid != Process.myPid() &&
+                Settings.Global.getInt(mContext.getContentResolver(),
+                        Settings.Global.WTF_IS_FATAL, 0) != 0) {
+            crashApplication(r, crashInfo);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * @param app object of some object (as stored in {@link com.android.internal.os.RuntimeInit})
+     * @return the corresponding {@link ProcessRecord} object, or null if none could be found
+     */
+    private ProcessRecord findAppProcess(IBinder app, String reason) {
+        if (app == null) {
+            return null;
+        }
+
+        synchronized (this) {
+            final int NP = mProcessNames.getMap().size();
+            for (int ip=0; ip<NP; ip++) {
+                SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+                final int NA = apps.size();
+                for (int ia=0; ia<NA; ia++) {
+                    ProcessRecord p = apps.valueAt(ia);
+                    if (p.thread != null && p.thread.asBinder() == app) {
+                        return p;
+                    }
+                }
+            }
+
+            Slog.w(TAG, "Can't find mystery application for " + reason
+                    + " from pid=" + Binder.getCallingPid()
+                    + " uid=" + Binder.getCallingUid() + ": " + app);
+            return null;
+        }
+    }
+
+    /**
+     * Utility function for addErrorToDropBox and handleStrictModeViolation's logging
+     * to append various headers to the dropbox log text.
+     */
+    private void appendDropBoxProcessHeaders(ProcessRecord process, String processName,
+            StringBuilder sb) {
+        // Watchdog thread ends up invoking this function (with
+        // a null ProcessRecord) to add the stack file to dropbox.
+        // Do not acquire a lock on this (am) in such cases, as it
+        // could cause a potential deadlock, if and when watchdog
+        // is invoked due to unavailability of lock on am and it
+        // would prevent watchdog from killing system_server.
+        if (process == null) {
+            sb.append("Process: ").append(processName).append("\n");
+            return;
+        }
+        // Note: ProcessRecord 'process' is guarded by the service
+        // instance.  (notably process.pkgList, which could otherwise change
+        // concurrently during execution of this method)
+        synchronized (this) {
+            sb.append("Process: ").append(processName).append("\n");
+            int flags = process.info.flags;
+            IPackageManager pm = AppGlobals.getPackageManager();
+            sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n");
+            for (int ip=0; ip<process.pkgList.size(); ip++) {
+                String pkg = process.pkgList.keyAt(ip);
+                sb.append("Package: ").append(pkg);
+                try {
+                    PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId());
+                    if (pi != null) {
+                        sb.append(" v").append(pi.versionCode);
+                        if (pi.versionName != null) {
+                            sb.append(" (").append(pi.versionName).append(")");
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error getting package info: " + pkg, e);
+                }
+                sb.append("\n");
+            }
+        }
+    }
+
+    private static String processClass(ProcessRecord process) {
+        if (process == null || process.pid == MY_PID) {
+            return "system_server";
+        } else if ((process.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            return "system_app";
+        } else {
+            return "data_app";
+        }
+    }
+
+    /**
+     * Write a description of an error (crash, WTF, ANR) to the drop box.
+     * @param eventType to include in the drop box tag ("crash", "wtf", etc.)
+     * @param process which caused the error, null means the system server
+     * @param activity which triggered the error, null if unknown
+     * @param parent activity related to the error, null if unknown
+     * @param subject line related to the error, null if absent
+     * @param report in long form describing the error, null if absent
+     * @param logFile to include in the report, null if none
+     * @param crashInfo giving an application stack trace, null if absent
+     */
+    public void addErrorToDropBox(String eventType,
+            ProcessRecord process, String processName, ActivityRecord activity,
+            ActivityRecord parent, String subject,
+            final String report, final File logFile,
+            final ApplicationErrorReport.CrashInfo crashInfo) {
+        // NOTE -- this must never acquire the ActivityManagerService lock,
+        // otherwise the watchdog may be prevented from resetting the system.
+
+        final String dropboxTag = processClass(process) + "_" + eventType;
+        final DropBoxManager dbox = (DropBoxManager)
+                mContext.getSystemService(Context.DROPBOX_SERVICE);
+
+        // Exit early if the dropbox isn't configured to accept this report type.
+        if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
+
+        final StringBuilder sb = new StringBuilder(1024);
+        appendDropBoxProcessHeaders(process, processName, sb);
+        if (activity != null) {
+            sb.append("Activity: ").append(activity.shortComponentName).append("\n");
+        }
+        if (parent != null && parent.app != null && parent.app.pid != process.pid) {
+            sb.append("Parent-Process: ").append(parent.app.processName).append("\n");
+        }
+        if (parent != null && parent != activity) {
+            sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n");
+        }
+        if (subject != null) {
+            sb.append("Subject: ").append(subject).append("\n");
+        }
+        sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
+        if (Debug.isDebuggerConnected()) {
+            sb.append("Debugger: Connected\n");
+        }
+        sb.append("\n");
+
+        // Do the rest in a worker thread to avoid blocking the caller on I/O
+        // (After this point, we shouldn't access AMS internal data structures.)
+        Thread worker = new Thread("Error dump: " + dropboxTag) {
+            @Override
+            public void run() {
+                if (report != null) {
+                    sb.append(report);
+                }
+                if (logFile != null) {
+                    try {
+                        sb.append(FileUtils.readTextFile(logFile, DROPBOX_MAX_SIZE,
+                                    "\n\n[[TRUNCATED]]"));
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Error reading " + logFile, e);
+                    }
+                }
+                if (crashInfo != null && crashInfo.stackTrace != null) {
+                    sb.append(crashInfo.stackTrace);
+                }
+
+                String setting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag;
+                int lines = Settings.Global.getInt(mContext.getContentResolver(), setting, 0);
+                if (lines > 0) {
+                    sb.append("\n");
+
+                    // Merge several logcat streams, and take the last N lines
+                    InputStreamReader input = null;
+                    try {
+                        java.lang.Process logcat = new ProcessBuilder("/system/bin/logcat",
+                                "-v", "time", "-b", "events", "-b", "system", "-b", "main",
+                                "-t", String.valueOf(lines)).redirectErrorStream(true).start();
+
+                        try { logcat.getOutputStream().close(); } catch (IOException e) {}
+                        try { logcat.getErrorStream().close(); } catch (IOException e) {}
+                        input = new InputStreamReader(logcat.getInputStream());
+
+                        int num;
+                        char[] buf = new char[8192];
+                        while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
+                    } catch (IOException e) {
+                        Slog.e(TAG, "Error running logcat", e);
+                    } finally {
+                        if (input != null) try { input.close(); } catch (IOException e) {}
+                    }
+                }
+
+                dbox.addText(dropboxTag, sb.toString());
+            }
+        };
+
+        if (process == null) {
+            // If process is null, we are being called from some internal code
+            // and may be about to die -- run this synchronously.
+            worker.run();
+        } else {
+            worker.start();
+        }
+    }
+
+    /**
+     * Bring up the "unexpected error" dialog box for a crashing app.
+     * Deal with edge cases (intercepts from instrumented applications,
+     * ActivityController, error intent receivers, that sort of thing).
+     * @param r the application crashing
+     * @param crashInfo describing the failure
+     */
+    private void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+        long timeMillis = System.currentTimeMillis();
+        String shortMsg = crashInfo.exceptionClassName;
+        String longMsg = crashInfo.exceptionMessage;
+        String stackTrace = crashInfo.stackTrace;
+        if (shortMsg != null && longMsg != null) {
+            longMsg = shortMsg + ": " + longMsg;
+        } else if (shortMsg != null) {
+            longMsg = shortMsg;
+        }
+
+        AppErrorResult result = new AppErrorResult();
+        synchronized (this) {
+            if (mController != null) {
+                try {
+                    String name = r != null ? r.processName : null;
+                    int pid = r != null ? r.pid : Binder.getCallingPid();
+                    if (!mController.appCrashed(name, pid,
+                            shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {
+                        Slog.w(TAG, "Force-killing crashed app " + name
+                                + " at watcher's request");
+                        Process.killProcess(pid);
+                        return;
+                    }
+                } catch (RemoteException e) {
+                    mController = null;
+                    Watchdog.getInstance().setActivityController(null);
+                }
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+
+            // If this process is running instrumentation, finish it.
+            if (r != null && r.instrumentationClass != null) {
+                Slog.w(TAG, "Error in app " + r.processName
+                      + " running instrumentation " + r.instrumentationClass + ":");
+                if (shortMsg != null) Slog.w(TAG, "  " + shortMsg);
+                if (longMsg != null) Slog.w(TAG, "  " + longMsg);
+                Bundle info = new Bundle();
+                info.putString("shortMsg", shortMsg);
+                info.putString("longMsg", longMsg);
+                finishInstrumentationLocked(r, Activity.RESULT_CANCELED, info);
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            // If we can't identify the process or it's already exceeded its crash quota,
+            // quit right away without showing a crash dialog.
+            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace)) {
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+
+            Message msg = Message.obtain();
+            msg.what = SHOW_ERROR_MSG;
+            HashMap data = new HashMap();
+            data.put("result", result);
+            data.put("app", r);
+            msg.obj = data;
+            mHandler.sendMessage(msg);
+
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        int res = result.get();
+
+        Intent appErrorIntent = null;
+        synchronized (this) {
+            if (r != null && !r.isolated) {
+                // XXX Can't keep track of crash time for isolated processes,
+                // since they don't have a persistent identity.
+                mProcessCrashTimes.put(r.info.processName, r.uid,
+                        SystemClock.uptimeMillis());
+            }
+            if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
+                appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
+            }
+        }
+
+        if (appErrorIntent != null) {
+            try {
+                mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
+            } catch (ActivityNotFoundException e) {
+                Slog.w(TAG, "bug report receiver dissappeared", e);
+            }
+        }
+    }
+
+    Intent createAppErrorIntentLocked(ProcessRecord r,
+            long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
+        ApplicationErrorReport report = createAppErrorReportLocked(r, timeMillis, crashInfo);
+        if (report == null) {
+            return null;
+        }
+        Intent result = new Intent(Intent.ACTION_APP_ERROR);
+        result.setComponent(r.errorReportReceiver);
+        result.putExtra(Intent.EXTRA_BUG_REPORT, report);
+        result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return result;
+    }
+
+    private ApplicationErrorReport createAppErrorReportLocked(ProcessRecord r,
+            long timeMillis, ApplicationErrorReport.CrashInfo crashInfo) {
+        if (r.errorReportReceiver == null) {
+            return null;
+        }
+
+        if (!r.crashing && !r.notResponding && !r.forceCrashReport) {
+            return null;
+        }
+
+        ApplicationErrorReport report = new ApplicationErrorReport();
+        report.packageName = r.info.packageName;
+        report.installerPackageName = r.errorReportReceiver.getPackageName();
+        report.processName = r.processName;
+        report.time = timeMillis;
+        report.systemApp = (r.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+
+        if (r.crashing || r.forceCrashReport) {
+            report.type = ApplicationErrorReport.TYPE_CRASH;
+            report.crashInfo = crashInfo;
+        } else if (r.notResponding) {
+            report.type = ApplicationErrorReport.TYPE_ANR;
+            report.anrInfo = new ApplicationErrorReport.AnrInfo();
+
+            report.anrInfo.activity = r.notRespondingReport.tag;
+            report.anrInfo.cause = r.notRespondingReport.shortMsg;
+            report.anrInfo.info = r.notRespondingReport.longMsg;
+        }
+
+        return report;
+    }
+
+    public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() {
+        enforceNotIsolatedCaller("getProcessesInErrorState");
+        // assume our apps are happy - lazy create the list
+        List<ActivityManager.ProcessErrorStateInfo> errList = null;
+
+        final boolean allUsers = ActivityManager.checkUidPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
+        int userId = UserHandle.getUserId(Binder.getCallingUid());
+
+        synchronized (this) {
+
+            // iterate across all processes
+            for (int i=mLruProcesses.size()-1; i>=0; i--) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (!allUsers && app.userId != userId) {
+                    continue;
+                }
+                if ((app.thread != null) && (app.crashing || app.notResponding)) {
+                    // This one's in trouble, so we'll generate a report for it
+                    // crashes are higher priority (in case there's a crash *and* an anr)
+                    ActivityManager.ProcessErrorStateInfo report = null;
+                    if (app.crashing) {
+                        report = app.crashingReport;
+                    } else if (app.notResponding) {
+                        report = app.notRespondingReport;
+                    }
+                    
+                    if (report != null) {
+                        if (errList == null) {
+                            errList = new ArrayList<ActivityManager.ProcessErrorStateInfo>(1);
+                        }
+                        errList.add(report);
+                    } else {
+                        Slog.w(TAG, "Missing app error report, app = " + app.processName + 
+                                " crashing = " + app.crashing +
+                                " notResponding = " + app.notResponding);
+                    }
+                }
+            }
+        }
+
+        return errList;
+    }
+
+    static int oomAdjToImportance(int adj, ActivityManager.RunningAppProcessInfo currApp) {
+        if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
+            if (currApp != null) {
+                currApp.lru = adj - ProcessList.CACHED_APP_MIN_ADJ + 1;
+            }
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+        } else if (adj >= ProcessList.SERVICE_B_ADJ) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+        } else if (adj >= ProcessList.HOME_APP_ADJ) {
+            if (currApp != null) {
+                currApp.lru = 0;
+            }
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+        } else if (adj >= ProcessList.SERVICE_ADJ) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+        } else if (adj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
+        } else if (adj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
+        } else if (adj >= ProcessList.VISIBLE_APP_ADJ) {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+        } else {
+            return ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+        }
+    }
+
+    private void fillInProcMemInfo(ProcessRecord app,
+            ActivityManager.RunningAppProcessInfo outInfo) {
+        outInfo.pid = app.pid;
+        outInfo.uid = app.info.uid;
+        if (mHeavyWeightProcess == app) {
+            outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE;
+        }
+        if (app.persistent) {
+            outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT;
+        }
+        if (app.activities.size() > 0) {
+            outInfo.flags |= ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES;
+        }
+        outInfo.lastTrimLevel = app.trimMemoryLevel;
+        int adj = app.curAdj;
+        outInfo.importance = oomAdjToImportance(adj, outInfo);
+        outInfo.importanceReasonCode = app.adjTypeCode;
+    }
+
+    public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() {
+        enforceNotIsolatedCaller("getRunningAppProcesses");
+        // Lazy instantiation of list
+        List<ActivityManager.RunningAppProcessInfo> runList = null;
+        final boolean allUsers = ActivityManager.checkUidPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                Binder.getCallingUid()) == PackageManager.PERMISSION_GRANTED;
+        int userId = UserHandle.getUserId(Binder.getCallingUid());
+        synchronized (this) {
+            // Iterate across all processes
+            for (int i=mLruProcesses.size()-1; i>=0; i--) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (!allUsers && app.userId != userId) {
+                    continue;
+                }
+                if ((app.thread != null) && (!app.crashing && !app.notResponding)) {
+                    // Generate process state info for running application
+                    ActivityManager.RunningAppProcessInfo currApp = 
+                        new ActivityManager.RunningAppProcessInfo(app.processName,
+                                app.pid, app.getPackageList());
+                    fillInProcMemInfo(app, currApp);
+                    if (app.adjSource instanceof ProcessRecord) {
+                        currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
+                        currApp.importanceReasonImportance = oomAdjToImportance(
+                                app.adjSourceOom, null);
+                    } else if (app.adjSource instanceof ActivityRecord) {
+                        ActivityRecord r = (ActivityRecord)app.adjSource;
+                        if (r.app != null) currApp.importanceReasonPid = r.app.pid;
+                    }
+                    if (app.adjTarget instanceof ComponentName) {
+                        currApp.importanceReasonComponent = (ComponentName)app.adjTarget;
+                    }
+                    //Slog.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance
+                    //        + " lru=" + currApp.lru);
+                    if (runList == null) {
+                        runList = new ArrayList<ActivityManager.RunningAppProcessInfo>();
+                    }
+                    runList.add(currApp);
+                }
+            }
+        }
+        return runList;
+    }
+
+    public List<ApplicationInfo> getRunningExternalApplications() {
+        enforceNotIsolatedCaller("getRunningExternalApplications");
+        List<ActivityManager.RunningAppProcessInfo> runningApps = getRunningAppProcesses();
+        List<ApplicationInfo> retList = new ArrayList<ApplicationInfo>();
+        if (runningApps != null && runningApps.size() > 0) {
+            Set<String> extList = new HashSet<String>();
+            for (ActivityManager.RunningAppProcessInfo app : runningApps) {
+                if (app.pkgList != null) {
+                    for (String pkg : app.pkgList) {
+                        extList.add(pkg);
+                    }
+                }
+            }
+            IPackageManager pm = AppGlobals.getPackageManager();
+            for (String pkg : extList) {
+                try {
+                    ApplicationInfo info = pm.getApplicationInfo(pkg, 0, UserHandle.getCallingUserId());
+                    if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) {
+                        retList.add(info);
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        return retList;
+    }
+
+    @Override
+    public void getMyMemoryState(ActivityManager.RunningAppProcessInfo outInfo) {
+        enforceNotIsolatedCaller("getMyMemoryState");
+        synchronized (this) {
+            ProcessRecord proc;
+            synchronized (mPidsSelfLocked) {
+                proc = mPidsSelfLocked.get(Binder.getCallingPid());
+            }
+            fillInProcMemInfo(proc, outInfo);
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (checkCallingPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump ActivityManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        boolean dumpAll = false;
+        boolean dumpClient = false;
+        String dumpPackage = null;
+        
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                dumpAll = true;
+            } else if ("-c".equals(opt)) {
+                dumpClient = true;
+            } else if ("-h".equals(opt)) {
+                pw.println("Activity manager dump options:");
+                pw.println("  [-a] [-c] [-h] [cmd] ...");
+                pw.println("  cmd may be one of:");
+                pw.println("    a[ctivities]: activity stack state");
+                pw.println("    b[roadcasts] [PACKAGE_NAME] [history [-s]]: broadcast state");
+                pw.println("    i[ntents] [PACKAGE_NAME]: pending intent state");
+                pw.println("    p[rocesses] [PACKAGE_NAME]: process state");
+                pw.println("    o[om]: out of memory management");
+                pw.println("    prov[iders] [COMP_SPEC ...]: content provider state");
+                pw.println("    provider [COMP_SPEC]: provider client-side state");
+                pw.println("    s[ervices] [COMP_SPEC ...]: service state");
+                pw.println("    service [COMP_SPEC]: service client-side state");
+                pw.println("    package [PACKAGE_NAME]: all state related to given package");
+                pw.println("    all: dump all activities");
+                pw.println("    top: dump the top activity");
+                pw.println("  cmd may also be a COMP_SPEC to dump activities.");
+                pw.println("  COMP_SPEC may be a component name (com.foo/.myApp),");
+                pw.println("    a partial substring in a component name, a");
+                pw.println("    hex object identifier.");
+                pw.println("  -a: include all available server state.");
+                pw.println("  -c: include client state.");
+                return;
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+
+        long origId = Binder.clearCallingIdentity();
+        boolean more = false;
+        // Is the caller requesting to dump a particular piece of data?
+        if (opti < args.length) {
+            String cmd = args[opti];
+            opti++;
+            if ("activities".equals(cmd) || "a".equals(cmd)) {
+                synchronized (this) {
+                    dumpActivitiesLocked(fd, pw, args, opti, true, dumpClient, null);
+                }
+            } else if ("broadcasts".equals(cmd) || "b".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                synchronized (this) {
+                    dumpBroadcastsLocked(fd, pw, args, opti, true, name);
+                }
+            } else if ("intents".equals(cmd) || "i".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                synchronized (this) {
+                    dumpPendingIntentsLocked(fd, pw, args, opti, true, name);
+                }
+            } else if ("processes".equals(cmd) || "p".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                synchronized (this) {
+                    dumpProcessesLocked(fd, pw, args, opti, true, name);
+                }
+            } else if ("oom".equals(cmd) || "o".equals(cmd)) {
+                synchronized (this) {
+                    dumpOomLocked(fd, pw, args, opti, true);
+                }
+            } else if ("provider".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti);
+                }
+                if (!dumpProvider(fd, pw, name, newArgs, 0, dumpAll)) {
+                    pw.println("No providers match: " + name);
+                    pw.println("Use -h for help.");
+                }
+            } else if ("providers".equals(cmd) || "prov".equals(cmd)) {
+                synchronized (this) {
+                    dumpProvidersLocked(fd, pw, args, opti, true, null);
+                }
+            } else if ("service".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                if (!mServices.dumpService(fd, pw, name, newArgs, 0, dumpAll)) {
+                    pw.println("No services match: " + name);
+                    pw.println("Use -h for help.");
+                }
+            } else if ("package".equals(cmd)) {
+                String[] newArgs;
+                if (opti >= args.length) {
+                    pw.println("package: no package name specified");
+                    pw.println("Use -h for help.");
+                } else {
+                    dumpPackage = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                    args = newArgs;
+                    opti = 0;
+                    more = true;
+                }
+            } else if ("services".equals(cmd) || "s".equals(cmd)) {
+                synchronized (this) {
+                    mServices.dumpServicesLocked(fd, pw, args, opti, true, dumpClient, null);
+                }
+            } else {
+                // Dumping a single activity?
+                if (!dumpActivity(fd, pw, cmd, args, opti, dumpAll)) {
+                    pw.println("Bad activity command, or no activities match: " + cmd);
+                    pw.println("Use -h for help.");
+                }
+            }
+            if (!more) {
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+        }
+
+        // No piece of data specified, dump everything.
+        synchronized (this) {
+            dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            mServices.dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
+        pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)");
+
+        boolean printedAnything = mStackSupervisor.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient,
+                dumpPackage);
+        boolean needSep = printedAnything;
+
+        boolean printed = ActivityStackSupervisor.printThisActivity(pw, mFocusedActivity,
+                dumpPackage, needSep, "  mFocusedActivity: ");
+        if (printed) {
+            printedAnything = true;
+            needSep = false;
+        }
+
+        if (dumpPackage == null) {
+            if (needSep) {
+                pw.println();
+            }
+            needSep = true;
+            printedAnything = true;
+            mStackSupervisor.dump(pw, "  ");
+        }
+
+        if (mRecentTasks.size() > 0) {
+            boolean printedHeader = false;
+
+            final int N = mRecentTasks.size();
+            for (int i=0; i<N; i++) {
+                TaskRecord tr = mRecentTasks.get(i);
+                if (dumpPackage != null) {
+                    if (tr.realActivity == null ||
+                            !dumpPackage.equals(tr.realActivity)) {
+                        continue;
+                    }
+                }
+                if (!printedHeader) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    pw.println("  Recent tasks:");
+                    printedHeader = true;
+                    printedAnything = true;
+                }
+                pw.print("  * Recent #"); pw.print(i); pw.print(": ");
+                        pw.println(tr);
+                if (dumpAll) {
+                    mRecentTasks.get(i).dump(pw, "    ");
+                }
+            }
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean needSep = false;
+        boolean printedAnything = false;
+        int numPers = 0;
+
+        pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
+
+        if (dumpAll) {
+            final int NP = mProcessNames.getMap().size();
+            for (int ip=0; ip<NP; ip++) {
+                SparseArray<ProcessRecord> procs = mProcessNames.getMap().valueAt(ip);
+                final int NA = procs.size();
+                for (int ia=0; ia<NA; ia++) {
+                    ProcessRecord r = procs.valueAt(ia);
+                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                        continue;
+                    }
+                    if (!needSep) {
+                        pw.println("  All known processes:");
+                        needSep = true;
+                        printedAnything = true;
+                    }
+                    pw.print(r.persistent ? "  *PERS*" : "  *APP*");
+                        pw.print(" UID "); pw.print(procs.keyAt(ia));
+                        pw.print(" "); pw.println(r);
+                    r.dump(pw, "    ");
+                    if (r.persistent) {
+                        numPers++;
+                    }
+                }
+            }
+        }
+
+        if (mIsolatedProcesses.size() > 0) {
+            boolean printed = false;
+            for (int i=0; i<mIsolatedProcesses.size(); i++) {
+                ProcessRecord r = mIsolatedProcesses.valueAt(i);
+                if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    pw.println("  Isolated process list (sorted by uid):");
+                    printedAnything = true;
+                    printed = true;
+                    needSep = true;
+                }
+                pw.println(String.format("%sIsolated #%2d: %s",
+                        "    ", i, r.toString()));
+            }
+        }
+
+        if (mLruProcesses.size() > 0) {
+            if (needSep) {
+                pw.println();
+            }
+            pw.print("  Process LRU list (sorted by oom_adj, "); pw.print(mLruProcesses.size());
+                    pw.print(" total, non-act at ");
+                    pw.print(mLruProcesses.size()-mLruProcessActivityStart);
+                    pw.print(", non-svc at ");
+                    pw.print(mLruProcesses.size()-mLruProcessServiceStart);
+                    pw.println("):");
+            dumpProcessOomList(pw, this, mLruProcesses, "    ", "Proc", "PERS", false, dumpPackage);
+            needSep = true;
+            printedAnything = true;
+        }
+
+        if (dumpAll || dumpPackage != null) {
+            synchronized (mPidsSelfLocked) {
+                boolean printed = false;
+                for (int i=0; i<mPidsSelfLocked.size(); i++) {
+                    ProcessRecord r = mPidsSelfLocked.valueAt(i);
+                    if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  PID mappings:");
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("    PID #"); pw.print(mPidsSelfLocked.keyAt(i));
+                        pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
+                }
+            }
+        }
+        
+        if (mForegroundProcesses.size() > 0) {
+            synchronized (mPidsSelfLocked) {
+                boolean printed = false;
+                for (int i=0; i<mForegroundProcesses.size(); i++) {
+                    ProcessRecord r = mPidsSelfLocked.get( 
+                            mForegroundProcesses.valueAt(i).pid);
+                    if (dumpPackage != null && (r == null
+                            || !r.pkgList.containsKey(dumpPackage))) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  Foreground Processes:");
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("    PID #"); pw.print(mForegroundProcesses.keyAt(i));
+                            pw.print(": "); pw.println(mForegroundProcesses.valueAt(i));
+                }
+            }
+        }
+        
+        if (mPersistentStartingProcesses.size() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            printedAnything = true;
+            pw.println("  Persisent processes that are starting:");
+            dumpProcessList(pw, this, mPersistentStartingProcesses, "    ",
+                    "Starting Norm", "Restarting PERS", dumpPackage);
+        }
+
+        if (mRemovedProcesses.size() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            printedAnything = true;
+            pw.println("  Processes that are being removed:");
+            dumpProcessList(pw, this, mRemovedProcesses, "    ",
+                    "Removed Norm", "Removed PERS", dumpPackage);
+        }
+        
+        if (mProcessesOnHold.size() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            printedAnything = true;
+            pw.println("  Processes that are on old until the system is ready:");
+            dumpProcessList(pw, this, mProcessesOnHold, "    ",
+                    "OnHold Norm", "OnHold PERS", dumpPackage);
+        }
+
+        needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
+        
+        if (mProcessCrashTimes.getMap().size() > 0) {
+            boolean printed = false;
+            long now = SystemClock.uptimeMillis();
+            final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap();
+            final int NP = pmap.size();
+            for (int ip=0; ip<NP; ip++) {
+                String pname = pmap.keyAt(ip);
+                SparseArray<Long> uids = pmap.valueAt(ip);
+                final int N = uids.size();
+                for (int i=0; i<N; i++) {
+                    int puid = uids.keyAt(i);
+                    ProcessRecord r = mProcessNames.get(pname, puid);
+                    if (dumpPackage != null && (r == null
+                            || !r.pkgList.containsKey(dumpPackage))) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  Time since processes crashed:");
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("    Process "); pw.print(pname);
+                            pw.print(" uid "); pw.print(puid);
+                            pw.print(": last crashed ");
+                            TimeUtils.formatDuration(now-uids.valueAt(i), pw);
+                            pw.println(" ago");
+                }
+            }
+        }
+
+        if (mBadProcesses.getMap().size() > 0) {
+            boolean printed = false;
+            final ArrayMap<String, SparseArray<BadProcessInfo>> pmap = mBadProcesses.getMap();
+            final int NP = pmap.size();
+            for (int ip=0; ip<NP; ip++) {
+                String pname = pmap.keyAt(ip);
+                SparseArray<BadProcessInfo> uids = pmap.valueAt(ip);
+                final int N = uids.size();
+                for (int i=0; i<N; i++) {
+                    int puid = uids.keyAt(i);
+                    ProcessRecord r = mProcessNames.get(pname, puid);
+                    if (dumpPackage != null && (r == null
+                            || !r.pkgList.containsKey(dumpPackage))) {
+                        continue;
+                    }
+                    if (!printed) {
+                        if (needSep) pw.println();
+                        needSep = true;
+                        pw.println("  Bad processes:");
+                        printedAnything = true;
+                    }
+                    BadProcessInfo info = uids.valueAt(i);
+                    pw.print("    Bad process "); pw.print(pname);
+                            pw.print(" uid "); pw.print(puid);
+                            pw.print(": crashed at time "); pw.println(info.time);
+                    if (info.shortMsg != null) {
+                        pw.print("      Short msg: "); pw.println(info.shortMsg);
+                    }
+                    if (info.longMsg != null) {
+                        pw.print("      Long msg: "); pw.println(info.longMsg);
+                    }
+                    if (info.stack != null) {
+                        pw.println("      Stack:");
+                        int lastPos = 0;
+                        for (int pos=0; pos<info.stack.length(); pos++) {
+                            if (info.stack.charAt(pos) == '\n') {
+                                pw.print("        ");
+                                pw.write(info.stack, lastPos, pos-lastPos);
+                                pw.println();
+                                lastPos = pos+1;
+                            }
+                        }
+                        if (lastPos < info.stack.length()) {
+                            pw.print("        ");
+                            pw.write(info.stack, lastPos, info.stack.length()-lastPos);
+                            pw.println();
+                        }
+                    }
+                }
+            }
+        }
+
+        if (dumpPackage == null) {
+            pw.println();
+            needSep = false;
+            pw.println("  mStartedUsers:");
+            for (int i=0; i<mStartedUsers.size(); i++) {
+                UserStartedState uss = mStartedUsers.valueAt(i);
+                pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
+                        pw.print(": "); uss.dump("", pw);
+            }
+            pw.print("  mStartedUserArray: [");
+            for (int i=0; i<mStartedUserArray.length; i++) {
+                if (i > 0) pw.print(", ");
+                pw.print(mStartedUserArray[i]);
+            }
+            pw.println("]");
+            pw.print("  mUserLru: [");
+            for (int i=0; i<mUserLru.size(); i++) {
+                if (i > 0) pw.print(", ");
+                pw.print(mUserLru.get(i));
+            }
+            pw.println("]");
+            if (dumpAll) {
+                pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
+            }
+        }
+        if (mHomeProcess != null && (dumpPackage == null
+                || mHomeProcess.pkgList.containsKey(dumpPackage))) {
+            if (needSep) {
+                pw.println();
+                needSep = false;
+            }
+            pw.println("  mHomeProcess: " + mHomeProcess);
+        }
+        if (mPreviousProcess != null && (dumpPackage == null
+                || mPreviousProcess.pkgList.containsKey(dumpPackage))) {
+            if (needSep) {
+                pw.println();
+                needSep = false;
+            }
+            pw.println("  mPreviousProcess: " + mPreviousProcess);
+        }
+        if (dumpAll) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("  mPreviousProcessVisibleTime: ");
+            TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb);
+            pw.println(sb);
+        }
+        if (mHeavyWeightProcess != null && (dumpPackage == null
+                || mHeavyWeightProcess.pkgList.containsKey(dumpPackage))) {
+            if (needSep) {
+                pw.println();
+                needSep = false;
+            }
+            pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
+        }
+        if (dumpPackage == null) {
+            pw.println("  mConfiguration: " + mConfiguration);
+        }
+        if (dumpAll) {
+            pw.println("  mConfigWillChange: " + getFocusedStack().mConfigWillChange);
+            if (mCompatModePackages.getPackages().size() > 0) {
+                boolean printed = false;
+                for (Map.Entry<String, Integer> entry
+                        : mCompatModePackages.getPackages().entrySet()) {
+                    String pkg = entry.getKey();
+                    int mode = entry.getValue();
+                    if (dumpPackage != null && !dumpPackage.equals(pkg)) {
+                        continue;
+                    }
+                    if (!printed) {
+                        pw.println("  mScreenCompatPackages:");
+                        printed = true;
+                    }
+                    pw.print("    "); pw.print(pkg); pw.print(": ");
+                            pw.print(mode); pw.println();
+                }
+            }
+        }
+        if (dumpPackage == null) {
+            if (mSleeping || mWentToSleep || mLockScreenShown) {
+                pw.println("  mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep
+                        + " mLockScreenShown " + mLockScreenShown);
+            }
+            if (mShuttingDown) {
+                pw.println("  mShuttingDown=" + mShuttingDown);
+            }
+        }
+        if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient
+                || mOrigWaitForDebugger) {
+            if (dumpPackage == null || dumpPackage.equals(mDebugApp)
+                    || dumpPackage.equals(mOrigDebugApp)) {
+                if (needSep) {
+                    pw.println();
+                    needSep = false;
+                }
+                pw.println("  mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp
+                        + " mDebugTransient=" + mDebugTransient
+                        + " mOrigWaitForDebugger=" + mOrigWaitForDebugger);
+            }
+        }
+        if (mOpenGlTraceApp != null) {
+            if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) {
+                if (needSep) {
+                    pw.println();
+                    needSep = false;
+                }
+                pw.println("  mOpenGlTraceApp=" + mOpenGlTraceApp);
+            }
+        }
+        if (mProfileApp != null || mProfileProc != null || mProfileFile != null
+                || mProfileFd != null) {
+            if (dumpPackage == null || dumpPackage.equals(mProfileApp)) {
+                if (needSep) {
+                    pw.println();
+                    needSep = false;
+                }
+                pw.println("  mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc);
+                pw.println("  mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd);
+                pw.println("  mProfileType=" + mProfileType + " mAutoStopProfiler="
+                        + mAutoStopProfiler);
+            }
+        }
+        if (dumpPackage == null) {
+            if (mAlwaysFinishActivities || mController != null) {
+                pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities
+                        + " mController=" + mController);
+            }
+            if (dumpAll) {
+                pw.println("  Total persistent processes: " + numPers);
+                pw.println("  mStartRunning=" + mStartRunning
+                        + " mProcessesReady=" + mProcessesReady
+                        + " mSystemReady=" + mSystemReady);
+                pw.println("  mBooting=" + mBooting
+                        + " mBooted=" + mBooted
+                        + " mFactoryTest=" + mFactoryTest);
+                pw.print("  mLastPowerCheckRealtime=");
+                        TimeUtils.formatDuration(mLastPowerCheckRealtime, pw);
+                        pw.println("");
+                pw.print("  mLastPowerCheckUptime=");
+                        TimeUtils.formatDuration(mLastPowerCheckUptime, pw);
+                        pw.println("");
+                pw.println("  mGoingToSleep=" + mStackSupervisor.mGoingToSleep);
+                pw.println("  mLaunchingActivity=" + mStackSupervisor.mLaunchingActivity);
+                pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq);
+                pw.println("  mNumNonCachedProcs=" + mNumNonCachedProcs
+                        + " (" + mLruProcesses.size() + " total)"
+                        + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
+                        + " mNumServiceProcs=" + mNumServiceProcs
+                        + " mNewNumServiceProcs=" + mNewNumServiceProcs);
+                pw.println("  mAllowLowerMemLevel=" + mAllowLowerMemLevel
+                        + " mLastMemoryLevel" + mLastMemoryLevel
+                        + " mLastNumProcesses" + mLastNumProcesses);
+                long now = SystemClock.uptimeMillis();
+                pw.print("  mLastIdleTime=");
+                        TimeUtils.formatDuration(now, mLastIdleTime, pw);
+                        pw.print(" mLowRamSinceLastIdle=");
+                        TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw);
+                        pw.println();
+            }
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean needSep, boolean dumpAll, String dumpPackage) {
+        if (mProcessesToGc.size() > 0) {
+            boolean printed = false;
+            long now = SystemClock.uptimeMillis();
+            for (int i=0; i<mProcessesToGc.size(); i++) {
+                ProcessRecord proc = mProcessesToGc.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(proc.info.packageName)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Processes that are waiting to GC:");
+                    printed = true;
+                }
+                pw.print("    Process "); pw.println(proc);
+                pw.print("      lowMem="); pw.print(proc.reportLowMemory);
+                        pw.print(", last gced=");
+                        pw.print(now-proc.lastRequestedGc);
+                        pw.print(" ms ago, last lowMem=");
+                        pw.print(now-proc.lastLowMemory);
+                        pw.println(" ms ago");
+
+            }
+        }
+        return needSep;
+    }
+
+    void printOomLevel(PrintWriter pw, String name, int adj) {
+        pw.print("    ");
+        if (adj >= 0) {
+            pw.print(' ');
+            if (adj < 10) pw.print(' ');
+        } else {
+            if (adj > -10) pw.print(' ');
+        }
+        pw.print(adj);
+        pw.print(": ");
+        pw.print(name);
+        pw.print(" (");
+        pw.print(mProcessList.getMemLevel(adj)/1024);
+        pw.println(" kB)");
+    }
+
+    boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll) {
+        boolean needSep = false;
+
+        if (mLruProcesses.size() > 0) {
+            if (needSep) pw.println();
+            needSep = true;
+            pw.println("  OOM levels:");
+            printOomLevel(pw, "SYSTEM_ADJ", ProcessList.SYSTEM_ADJ);
+            printOomLevel(pw, "PERSISTENT_PROC_ADJ", ProcessList.PERSISTENT_PROC_ADJ);
+            printOomLevel(pw, "FOREGROUND_APP_ADJ", ProcessList.FOREGROUND_APP_ADJ);
+            printOomLevel(pw, "VISIBLE_APP_ADJ", ProcessList.VISIBLE_APP_ADJ);
+            printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", ProcessList.PERCEPTIBLE_APP_ADJ);
+            printOomLevel(pw, "BACKUP_APP_ADJ", ProcessList.BACKUP_APP_ADJ);
+            printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", ProcessList.HEAVY_WEIGHT_APP_ADJ);
+            printOomLevel(pw, "SERVICE_ADJ", ProcessList.SERVICE_ADJ);
+            printOomLevel(pw, "HOME_APP_ADJ", ProcessList.HOME_APP_ADJ);
+            printOomLevel(pw, "PREVIOUS_APP_ADJ", ProcessList.PREVIOUS_APP_ADJ);
+            printOomLevel(pw, "SERVICE_B_ADJ", ProcessList.SERVICE_B_ADJ);
+            printOomLevel(pw, "CACHED_APP_MIN_ADJ", ProcessList.CACHED_APP_MIN_ADJ);
+            printOomLevel(pw, "CACHED_APP_MAX_ADJ", ProcessList.CACHED_APP_MAX_ADJ);
+
+            if (needSep) pw.println();
+            pw.print("  Process OOM control ("); pw.print(mLruProcesses.size());
+                    pw.print(" total, non-act at ");
+                    pw.print(mLruProcesses.size()-mLruProcessActivityStart);
+                    pw.print(", non-svc at ");
+                    pw.print(mLruProcesses.size()-mLruProcessServiceStart);
+                    pw.println("):");
+            dumpProcessOomList(pw, this, mLruProcesses, "    ", "Proc", "PERS", true, null);
+            needSep = true;
+        }
+
+        dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, null);
+
+        pw.println();
+        pw.println("  mHomeProcess: " + mHomeProcess);
+        pw.println("  mPreviousProcess: " + mPreviousProcess);
+        if (mHeavyWeightProcess != null) {
+            pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
+        }
+
+        return true;
+    }
+
+    /**
+     * There are three ways to call this:
+     *  - no provider specified: dump all the providers
+     *  - a flattened component name that matched an existing provider was specified as the
+     *    first arg: dump that one provider
+     *  - the first arg isn't the flattened component name of an existing provider:
+     *    dump all providers whose component contains the first arg as a substring
+     */
+    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll);
+    }
+
+    static class ItemMatcher {
+        ArrayList<ComponentName> components;
+        ArrayList<String> strings;
+        ArrayList<Integer> objects;
+        boolean all;
+        
+        ItemMatcher() {
+            all = true;
+        }
+
+        void build(String name) {
+            ComponentName componentName = ComponentName.unflattenFromString(name);
+            if (componentName != null) {
+                if (components == null) {
+                    components = new ArrayList<ComponentName>();
+                }
+                components.add(componentName);
+                all = false;
+            } else {
+                int objectId = 0;
+                // Not a '/' separated full component name; maybe an object ID?
+                try {
+                    objectId = Integer.parseInt(name, 16);
+                    if (objects == null) {
+                        objects = new ArrayList<Integer>();
+                    }
+                    objects.add(objectId);
+                    all = false;
+                } catch (RuntimeException e) {
+                    // Not an integer; just do string match.
+                    if (strings == null) {
+                        strings = new ArrayList<String>();
+                    }
+                    strings.add(name);
+                    all = false;
+                }
+            }
+        }
+
+        int build(String[] args, int opti) {
+            for (; opti<args.length; opti++) {
+                String name = args[opti];
+                if ("--".equals(name)) {
+                    return opti+1;
+                }
+                build(name);
+            }
+            return opti;
+        }
+
+        boolean match(Object object, ComponentName comp) {
+            if (all) {
+                return true;
+            }
+            if (components != null) {
+                for (int i=0; i<components.size(); i++) {
+                    if (components.get(i).equals(comp)) {
+                        return true;
+                    }
+                }
+            }
+            if (objects != null) {
+                for (int i=0; i<objects.size(); i++) {
+                    if (System.identityHashCode(object) == objects.get(i)) {
+                        return true;
+                    }
+                }
+            }
+            if (strings != null) {
+                String flat = comp.flattenToString();
+                for (int i=0; i<strings.size(); i++) {
+                    if (flat.contains(strings.get(i))) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * There are three things that cmd can be:
+     *  - a flattened component name that matches an existing activity
+     *  - the cmd arg isn't the flattened component name of an existing activity:
+     *    dump all activity whose component contains the cmd as a substring
+     *  - A hex number of the ActivityRecord object instance.
+     */
+    protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        ArrayList<ActivityRecord> activities;
+        
+        synchronized (this) {
+            activities = mStackSupervisor.getDumpActivitiesLocked(name);
+        }
+
+        if (activities.size() <= 0) {
+            return false;
+        }
+
+        String[] newArgs = new String[args.length - opti];
+        System.arraycopy(args, opti, newArgs, 0, args.length - opti);
+
+        TaskRecord lastTask = null;
+        boolean needSep = false;
+        for (int i=activities.size()-1; i>=0; i--) {
+            ActivityRecord r = activities.get(i);
+            if (needSep) {
+                pw.println();
+            }
+            needSep = true;
+            synchronized (this) {
+                if (lastTask != r.task) {
+                    lastTask = r.task;
+                    pw.print("TASK "); pw.print(lastTask.affinity);
+                            pw.print(" id="); pw.println(lastTask.taskId);
+                    if (dumpAll) {
+                        lastTask.dump(pw, "  ");
+                    }
+                }
+            }
+            dumpActivity("  ", fd, pw, activities.get(i), newArgs, dumpAll);
+        }
+        return true;
+    }
+
+    /**
+     * Invokes IApplicationThread.dumpActivity() on the thread of the specified activity if
+     * there is a thread associated with the activity.
+     */
+    private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
+            final ActivityRecord r, String[] args, boolean dumpAll) {
+        String innerPrefix = prefix + "  ";
+        synchronized (this) {
+            pw.print(prefix); pw.print("ACTIVITY "); pw.print(r.shortComponentName);
+                    pw.print(" "); pw.print(Integer.toHexString(System.identityHashCode(r)));
+                    pw.print(" pid=");
+                    if (r.app != null) pw.println(r.app.pid);
+                    else pw.println("(not running)");
+            if (dumpAll) {
+                r.dump(pw, innerPrefix);
+            }
+        }
+        if (r.app != null && r.app.thread != null) {
+            // flush anything that is already in the PrintWriter since the thread is going
+            // to write to the file descriptor directly
+            pw.flush();
+            try {
+                TransferPipe tp = new TransferPipe();
+                try {
+                    r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(),
+                            r.appToken, innerPrefix, args);
+                    tp.go(fd);
+                } finally {
+                    tp.kill();
+                }
+            } catch (IOException e) {
+                pw.println(innerPrefix + "Failure while dumping the activity: " + e);
+            } catch (RemoteException e) {
+                pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
+            }
+        }
+    }
+
+    void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean needSep = false;
+        boolean onlyHistory = false;
+        boolean printedAnything = false;
+
+        if ("history".equals(dumpPackage)) {
+            if (opti < args.length && "-s".equals(args[opti])) {
+                dumpAll = false;
+            }
+            onlyHistory = true;
+            dumpPackage = null;
+        }
+
+        pw.println("ACTIVITY MANAGER BROADCAST STATE (dumpsys activity broadcasts)");
+        if (!onlyHistory && dumpAll) {
+            if (mRegisteredReceivers.size() > 0) {
+                boolean printed = false;
+                Iterator it = mRegisteredReceivers.values().iterator();
+                while (it.hasNext()) {
+                    ReceiverList r = (ReceiverList)it.next();
+                    if (dumpPackage != null && (r.app == null ||
+                            !dumpPackage.equals(r.app.info.packageName))) {
+                        continue;
+                    }
+                    if (!printed) {
+                        pw.println("  Registered Receivers:");
+                        needSep = true;
+                        printed = true;
+                        printedAnything = true;
+                    }
+                    pw.print("  * "); pw.println(r);
+                    r.dump(pw, "    ");
+                }
+            }
+
+            if (mReceiverResolver.dump(pw, needSep ?
+                    "\n  Receiver Resolver Table:" : "  Receiver Resolver Table:",
+                    "    ", dumpPackage, false)) {
+                needSep = true;
+                printedAnything = true;
+            }
+        }
+
+        for (BroadcastQueue q : mBroadcastQueues) {
+            needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep);
+            printedAnything |= needSep;
+        }
+
+        needSep = true;
+        
+        if (!onlyHistory && mStickyBroadcasts != null && dumpPackage == null) {
+            for (int user=0; user<mStickyBroadcasts.size(); user++) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                printedAnything = true;
+                pw.print("  Sticky broadcasts for user ");
+                        pw.print(mStickyBroadcasts.keyAt(user)); pw.println(":");
+                StringBuilder sb = new StringBuilder(128);
+                for (Map.Entry<String, ArrayList<Intent>> ent
+                        : mStickyBroadcasts.valueAt(user).entrySet()) {
+                    pw.print("  * Sticky action "); pw.print(ent.getKey());
+                    if (dumpAll) {
+                        pw.println(":");
+                        ArrayList<Intent> intents = ent.getValue();
+                        final int N = intents.size();
+                        for (int i=0; i<N; i++) {
+                            sb.setLength(0);
+                            sb.append("    Intent: ");
+                            intents.get(i).toShortString(sb, false, true, false, false);
+                            pw.println(sb.toString());
+                            Bundle bundle = intents.get(i).getExtras();
+                            if (bundle != null) {
+                                pw.print("      ");
+                                pw.println(bundle.toString());
+                            }
+                        }
+                    } else {
+                        pw.println("");
+                    }
+                }
+            }
+        }
+        
+        if (!onlyHistory && dumpAll) {
+            pw.println();
+            for (BroadcastQueue queue : mBroadcastQueues) {
+                pw.println("  mBroadcastsScheduled [" + queue.mQueueName + "]="
+                        + queue.mBroadcastsScheduled);
+            }
+            pw.println("  mHandler:");
+            mHandler.dump(new PrintWriterPrinter(pw), "    ");
+            needSep = true;
+            printedAnything = true;
+        }
+        
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    void dumpProvidersLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean needSep;
+        boolean printedAnything = false;
+
+        ItemMatcher matcher = new ItemMatcher();
+        matcher.build(args, opti);
+
+        pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)");
+
+        needSep = mProviderMap.dumpProvidersLocked(pw, dumpAll, dumpPackage);
+        printedAnything |= needSep;
+
+        if (mLaunchingProviders.size() > 0) {
+            boolean printed = false;
+            for (int i=mLaunchingProviders.size()-1; i>=0; i--) {
+                ContentProviderRecord r = mLaunchingProviders.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(r.name.getPackageName())) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Launching content providers:");
+                    printed = true;
+                    printedAnything = true;
+                }
+                pw.print("  Launching #"); pw.print(i); pw.print(": ");
+                        pw.println(r);
+            }
+        }
+
+        if (mGrantedUriPermissions.size() > 0) {
+            boolean printed = false;
+            int dumpUid = -2;
+            if (dumpPackage != null) {
+                try {
+                    dumpUid = mContext.getPackageManager().getPackageUid(dumpPackage, 0);
+                } catch (NameNotFoundException e) {
+                    dumpUid = -1;
+                }
+            }
+            for (int i=0; i<mGrantedUriPermissions.size(); i++) {
+                int uid = mGrantedUriPermissions.keyAt(i);
+                if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) {
+                    continue;
+                }
+                ArrayMap<Uri, UriPermission> perms
+                        = mGrantedUriPermissions.valueAt(i);
+                if (!printed) {
+                    if (needSep) pw.println();
+                    needSep = true;
+                    pw.println("  Granted Uri Permissions:");
+                    printed = true;
+                    printedAnything = true;
+                }
+                pw.print("  * UID "); pw.print(uid);
+                        pw.println(" holds:");
+                for (UriPermission perm : perms.values()) {
+                    pw.print("    "); pw.println(perm);
+                    if (dumpAll) {
+                        perm.dump(pw, "      ");
+                    }
+                }
+            }
+        }
+
+        if (!printedAnything) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    void dumpPendingIntentsLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage) {
+        boolean printed = false;
+
+        pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)");
+
+        if (mIntentSenderRecords.size() > 0) {
+            Iterator<WeakReference<PendingIntentRecord>> it
+                    = mIntentSenderRecords.values().iterator();
+            while (it.hasNext()) {
+                WeakReference<PendingIntentRecord> ref = it.next();
+                PendingIntentRecord rec = ref != null ? ref.get(): null;
+                if (dumpPackage != null && (rec == null
+                        || !dumpPackage.equals(rec.key.packageName))) {
+                    continue;
+                }
+                printed = true;
+                if (rec != null) {
+                    pw.print("  * "); pw.println(rec);
+                    if (dumpAll) {
+                        rec.dump(pw, "    ");
+                    }
+                } else {
+                    pw.print("  * "); pw.println(ref);
+                }
+            }
+        }
+
+        if (!printed) {
+            pw.println("  (nothing)");
+        }
+    }
+
+    private static final int dumpProcessList(PrintWriter pw,
+            ActivityManagerService service, List list,
+            String prefix, String normalLabel, String persistentLabel,
+            String dumpPackage) {
+        int numPers = 0;
+        final int N = list.size()-1;
+        for (int i=N; i>=0; i--) {
+            ProcessRecord r = (ProcessRecord)list.get(i);
+            if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) {
+                continue;
+            }
+            pw.println(String.format("%s%s #%2d: %s",
+                    prefix, (r.persistent ? persistentLabel : normalLabel),
+                    i, r.toString()));
+            if (r.persistent) {
+                numPers++;
+            }
+        }
+        return numPers;
+    }
+
+    private static final boolean dumpProcessOomList(PrintWriter pw,
+            ActivityManagerService service, List<ProcessRecord> origList,
+            String prefix, String normalLabel, String persistentLabel,
+            boolean inclDetails, String dumpPackage) {
+
+        ArrayList<Pair<ProcessRecord, Integer>> list
+                = new ArrayList<Pair<ProcessRecord, Integer>>(origList.size());
+        for (int i=0; i<origList.size(); i++) {
+            ProcessRecord r = origList.get(i);
+            if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
+                continue;
+            }
+            list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i));
+        }
+
+        if (list.size() <= 0) {
+            return false;
+        }
+
+        Comparator<Pair<ProcessRecord, Integer>> comparator
+                = new Comparator<Pair<ProcessRecord, Integer>>() {
+            @Override
+            public int compare(Pair<ProcessRecord, Integer> object1,
+                    Pair<ProcessRecord, Integer> object2) {
+                if (object1.first.setAdj != object2.first.setAdj) {
+                    return object1.first.setAdj > object2.first.setAdj ? -1 : 1;
+                }
+                if (object1.second.intValue() != object2.second.intValue()) {
+                    return object1.second.intValue() > object2.second.intValue() ? -1 : 1;
+                }
+                return 0;
+            }
+        };
+
+        Collections.sort(list, comparator);
+
+        final long curRealtime = SystemClock.elapsedRealtime();
+        final long realtimeSince = curRealtime - service.mLastPowerCheckRealtime;
+        final long curUptime = SystemClock.uptimeMillis();
+        final long uptimeSince = curUptime - service.mLastPowerCheckUptime;
+
+        for (int i=list.size()-1; i>=0; i--) {
+            ProcessRecord r = list.get(i).first;
+            String oomAdj = ProcessList.makeOomAdjString(r.setAdj);
+            char schedGroup;
+            switch (r.setSchedGroup) {
+                case Process.THREAD_GROUP_BG_NONINTERACTIVE:
+                    schedGroup = 'B';
+                    break;
+                case Process.THREAD_GROUP_DEFAULT:
+                    schedGroup = 'F';
+                    break;
+                default:
+                    schedGroup = '?';
+                    break;
+            }
+            char foreground;
+            if (r.foregroundActivities) {
+                foreground = 'A';
+            } else if (r.foregroundServices) {
+                foreground = 'S';
+            } else {
+                foreground = ' ';
+            }
+            String procState = ProcessList.makeProcStateString(r.curProcState);
+            pw.print(prefix);
+            pw.print(r.persistent ? persistentLabel : normalLabel);
+            pw.print(" #");
+            int num = (origList.size()-1)-list.get(i).second;
+            if (num < 10) pw.print(' ');
+            pw.print(num);
+            pw.print(": ");
+            pw.print(oomAdj);
+            pw.print(' ');
+            pw.print(schedGroup);
+            pw.print('/');
+            pw.print(foreground);
+            pw.print('/');
+            pw.print(procState);
+            pw.print(" trm:");
+            if (r.trimMemoryLevel < 10) pw.print(' ');
+            pw.print(r.trimMemoryLevel);
+            pw.print(' ');
+            pw.print(r.toShortString());
+            pw.print(" (");
+            pw.print(r.adjType);
+            pw.println(')');
+            if (r.adjSource != null || r.adjTarget != null) {
+                pw.print(prefix);
+                pw.print("    ");
+                if (r.adjTarget instanceof ComponentName) {
+                    pw.print(((ComponentName)r.adjTarget).flattenToShortString());
+                } else if (r.adjTarget != null) {
+                    pw.print(r.adjTarget.toString());
+                } else {
+                    pw.print("{null}");
+                }
+                pw.print("<=");
+                if (r.adjSource instanceof ProcessRecord) {
+                    pw.print("Proc{");
+                    pw.print(((ProcessRecord)r.adjSource).toShortString());
+                    pw.println("}");
+                } else if (r.adjSource != null) {
+                    pw.println(r.adjSource.toString());
+                } else {
+                    pw.println("{null}");
+                }
+            }
+            if (inclDetails) {
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("oom: max="); pw.print(r.maxAdj);
+                pw.print(" curRaw="); pw.print(r.curRawAdj);
+                pw.print(" setRaw="); pw.print(r.setRawAdj);
+                pw.print(" cur="); pw.print(r.curAdj);
+                pw.print(" set="); pw.println(r.setAdj);
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("state: cur="); pw.print(ProcessList.makeProcStateString(r.curProcState));
+                pw.print(" set="); pw.print(ProcessList.makeProcStateString(r.setProcState));
+                pw.print(" lastPss="); pw.print(r.lastPss);
+                pw.print(" lastCachedPss="); pw.println(r.lastCachedPss);
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("keeping="); pw.print(r.keeping);
+                pw.print(" cached="); pw.print(r.cached);
+                pw.print(" empty="); pw.print(r.empty);
+                pw.print(" hasAboveClient="); pw.println(r.hasAboveClient);
+
+                if (!r.keeping) {
+                    if (r.lastWakeTime != 0) {
+                        long wtime;
+                        BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics();
+                        synchronized (stats) {
+                            wtime = stats.getProcessWakeTime(r.info.uid,
+                                    r.pid, curRealtime);
+                        }
+                        long timeUsed = wtime - r.lastWakeTime;
+                        pw.print(prefix);
+                        pw.print("    ");
+                        pw.print("keep awake over ");
+                        TimeUtils.formatDuration(realtimeSince, pw);
+                        pw.print(" used ");
+                        TimeUtils.formatDuration(timeUsed, pw);
+                        pw.print(" (");
+                        pw.print((timeUsed*100)/realtimeSince);
+                        pw.println("%)");
+                    }
+                    if (r.lastCpuTime != 0) {
+                        long timeUsed = r.curCpuTime - r.lastCpuTime;
+                        pw.print(prefix);
+                        pw.print("    ");
+                        pw.print("run cpu over ");
+                        TimeUtils.formatDuration(uptimeSince, pw);
+                        pw.print(" used ");
+                        TimeUtils.formatDuration(timeUsed, pw);
+                        pw.print(" (");
+                        pw.print((timeUsed*100)/uptimeSince);
+                        pw.println("%)");
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    ArrayList<ProcessRecord> collectProcesses(PrintWriter pw, int start, String[] args) {
+        ArrayList<ProcessRecord> procs;
+        synchronized (this) {
+            if (args != null && args.length > start
+                    && args[start].charAt(0) != '-') {
+                procs = new ArrayList<ProcessRecord>();
+                int pid = -1;
+                try {
+                    pid = Integer.parseInt(args[start]);
+                } catch (NumberFormatException e) {
+                }
+                for (int i=mLruProcesses.size()-1; i>=0; i--) {
+                    ProcessRecord proc = mLruProcesses.get(i);
+                    if (proc.pid == pid) {
+                        procs.add(proc);
+                    } else if (proc.processName.equals(args[start])) {
+                        procs.add(proc);
+                    }
+                }
+                if (procs.size() <= 0) {
+                    return null;
+                }
+            } else {
+                procs = new ArrayList<ProcessRecord>(mLruProcesses);
+            }
+        }
+        return procs;
+    }
+
+    final void dumpGraphicsHardwareUsage(FileDescriptor fd,
+            PrintWriter pw, String[] args) {
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args);
+        if (procs == null) {
+            pw.println("No process found for: " + args[0]);
+            return;
+        }
+
+        long uptime = SystemClock.uptimeMillis();
+        long realtime = SystemClock.elapsedRealtime();
+        pw.println("Applications Graphics Acceleration Info:");
+        pw.println("Uptime: " + uptime + " Realtime: " + realtime);
+        
+        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+            ProcessRecord r = procs.get(i);
+            if (r.thread != null) {
+                pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **");
+                pw.flush();
+                try {
+                    TransferPipe tp = new TransferPipe();
+                    try {
+                        r.thread.dumpGfxInfo(tp.getWriteFd().getFileDescriptor(), args);
+                        tp.go(fd);
+                    } finally {
+                        tp.kill();
+                    }
+                } catch (IOException e) {
+                    pw.println("Failure while dumping the app: " + r);
+                    pw.flush();
+                } catch (RemoteException e) {
+                    pw.println("Got a RemoteException while dumping the app " + r);
+                    pw.flush();
+                }
+            }
+        }
+    }
+
+    final void dumpDbInfo(FileDescriptor fd, PrintWriter pw, String[] args) {
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, 0, args);
+        if (procs == null) {
+            pw.println("No process found for: " + args[0]);
+            return;
+        }
+
+        pw.println("Applications Database Info:");
+
+        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+            ProcessRecord r = procs.get(i);
+            if (r.thread != null) {
+                pw.println("\n** Database info for pid " + r.pid + " [" + r.processName + "] **");
+                pw.flush();
+                try {
+                    TransferPipe tp = new TransferPipe();
+                    try {
+                        r.thread.dumpDbInfo(tp.getWriteFd().getFileDescriptor(), args);
+                        tp.go(fd);
+                    } finally {
+                        tp.kill();
+                    }
+                } catch (IOException e) {
+                    pw.println("Failure while dumping the app: " + r);
+                    pw.flush();
+                } catch (RemoteException e) {
+                    pw.println("Got a RemoteException while dumping the app " + r);
+                    pw.flush();
+                }
+            }
+        }
+    }
+
+    final static class MemItem {
+        final boolean isProc;
+        final String label;
+        final String shortLabel;
+        final long pss;
+        final int id;
+        final boolean hasActivities;
+        ArrayList<MemItem> subitems;
+
+        public MemItem(String _label, String _shortLabel, long _pss, int _id,
+                boolean _hasActivities) {
+            isProc = true;
+            label = _label;
+            shortLabel = _shortLabel;
+            pss = _pss;
+            id = _id;
+            hasActivities = _hasActivities;
+        }
+
+        public MemItem(String _label, String _shortLabel, long _pss, int _id) {
+            isProc = false;
+            label = _label;
+            shortLabel = _shortLabel;
+            pss = _pss;
+            id = _id;
+            hasActivities = false;
+        }
+    }
+
+    static final void dumpMemItems(PrintWriter pw, String prefix, String tag,
+            ArrayList<MemItem> items, boolean sort, boolean isCompact) {
+        if (sort && !isCompact) {
+            Collections.sort(items, new Comparator<MemItem>() {
+                @Override
+                public int compare(MemItem lhs, MemItem rhs) {
+                    if (lhs.pss < rhs.pss) {
+                        return 1;
+                    } else if (lhs.pss > rhs.pss) {
+                        return -1;
+                    }
+                    return 0;
+                }
+            });
+        }
+
+        for (int i=0; i<items.size(); i++) {
+            MemItem mi = items.get(i);
+            if (!isCompact) {
+                pw.print(prefix); pw.printf("%7d kB: ", mi.pss); pw.println(mi.label);
+            } else if (mi.isProc) {
+                pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel);
+                pw.print(","); pw.print(mi.id); pw.print(","); pw.print(mi.pss);
+                pw.println(mi.hasActivities ? ",a" : ",e");
+            } else {
+                pw.print(tag); pw.print(","); pw.print(mi.shortLabel); pw.print(",");
+                pw.println(mi.pss);
+            }
+            if (mi.subitems != null) {
+                dumpMemItems(pw, prefix + "           ", mi.shortLabel, mi.subitems,
+                        true, isCompact);
+            }
+        }
+    }
+
+    // These are in KB.
+    static final long[] DUMP_MEM_BUCKETS = new long[] {
+        5*1024, 7*1024, 10*1024, 15*1024, 20*1024, 30*1024, 40*1024, 80*1024,
+        120*1024, 160*1024, 200*1024,
+        250*1024, 300*1024, 350*1024, 400*1024, 500*1024, 600*1024, 800*1024,
+        1*1024*1024, 2*1024*1024, 5*1024*1024, 10*1024*1024, 20*1024*1024
+    };
+
+    static final void appendMemBucket(StringBuilder out, long memKB, String label,
+            boolean stackLike) {
+        int start = label.lastIndexOf('.');
+        if (start >= 0) start++;
+        else start = 0;
+        int end = label.length();
+        for (int i=0; i<DUMP_MEM_BUCKETS.length; i++) {
+            if (DUMP_MEM_BUCKETS[i] >= memKB) {
+                long bucket = DUMP_MEM_BUCKETS[i]/1024;
+                out.append(bucket);
+                out.append(stackLike ? "MB." : "MB ");
+                out.append(label, start, end);
+                return;
+            }
+        }
+        out.append(memKB/1024);
+        out.append(stackLike ? "MB." : "MB ");
+        out.append(label, start, end);
+    }
+
+    static final int[] DUMP_MEM_OOM_ADJ = new int[] {
+            ProcessList.NATIVE_ADJ,
+            ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ, ProcessList.FOREGROUND_APP_ADJ,
+            ProcessList.VISIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_APP_ADJ,
+            ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ,
+            ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ,
+            ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MAX_ADJ
+    };
+    static final String[] DUMP_MEM_OOM_LABEL = new String[] {
+            "Native",
+            "System", "Persistent", "Foreground",
+            "Visible", "Perceptible",
+            "Heavy Weight", "Backup",
+            "A Services", "Home",
+            "Previous", "B Services", "Cached"
+    };
+    static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] {
+            "native",
+            "sys", "pers", "fore",
+            "vis", "percept",
+            "heavy", "backup",
+            "servicea", "home",
+            "prev", "serviceb", "cached"
+    };
+
+    private final void dumpApplicationMemoryUsageHeader(PrintWriter pw, long uptime,
+            long realtime, boolean isCheckinRequest, boolean isCompact) {
+        if (isCheckinRequest || isCompact) {
+            // short checkin version
+            pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime);
+        } else {
+            pw.println("Applications Memory Usage (kB):");
+            pw.println("Uptime: " + uptime + " Realtime: " + realtime);
+        }
+    }
+
+    final void dumpApplicationMemoryUsage(FileDescriptor fd,
+            PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
+        boolean dumpDetails = false;
+        boolean dumpFullDetails = false;
+        boolean dumpDalvik = false;
+        boolean oomOnly = false;
+        boolean isCompact = false;
+        boolean localOnly = false;
+        
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                dumpDetails = true;
+                dumpFullDetails = true;
+                dumpDalvik = true;
+            } else if ("-d".equals(opt)) {
+                dumpDalvik = true;
+            } else if ("-c".equals(opt)) {
+                isCompact = true;
+            } else if ("--oom".equals(opt)) {
+                oomOnly = true;
+            } else if ("--local".equals(opt)) {
+                localOnly = true;
+            } else if ("-h".equals(opt)) {
+                pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
+                pw.println("  -a: include all available information for each process.");
+                pw.println("  -d: include dalvik details when dumping process details.");
+                pw.println("  -c: dump in a compact machine-parseable representation.");
+                pw.println("  --oom: only show processes organized by oom adj.");
+                pw.println("  --local: only collect details locally, don't call process.");
+                pw.println("If [process] is specified it can be the name or ");
+                pw.println("pid of a specific process to dump.");
+                return;
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+        
+        final boolean isCheckinRequest = scanArgs(args, "--checkin");
+        long uptime = SystemClock.uptimeMillis();
+        long realtime = SystemClock.elapsedRealtime();
+        final long[] tmpLong = new long[1];
+
+        ArrayList<ProcessRecord> procs = collectProcesses(pw, opti, args);
+        if (procs == null) {
+            // No Java processes.  Maybe they want to print a native process.
+            if (args != null && args.length > opti
+                    && args[opti].charAt(0) != '-') {
+                ArrayList<ProcessCpuTracker.Stats> nativeProcs
+                        = new ArrayList<ProcessCpuTracker.Stats>();
+                updateCpuStatsNow();
+                int findPid = -1;
+                try {
+                    findPid = Integer.parseInt(args[opti]);
+                } catch (NumberFormatException e) {
+                }
+                synchronized (mProcessCpuThread) {
+                    final int N = mProcessCpuTracker.countStats();
+                    for (int i=0; i<N; i++) {
+                        ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                        if (st.pid == findPid || (st.baseName != null
+                                && st.baseName.equals(args[opti]))) {
+                            nativeProcs.add(st);
+                        }
+                    }
+                }
+                if (nativeProcs.size() > 0) {
+                    dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest,
+                            isCompact);
+                    Debug.MemoryInfo mi = null;
+                    for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
+                        final ProcessCpuTracker.Stats r = nativeProcs.get(i);
+                        final int pid = r.pid;
+                        if (!isCheckinRequest && dumpDetails) {
+                            pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
+                        }
+                        if (mi == null) {
+                            mi = new Debug.MemoryInfo();
+                        }
+                        if (dumpDetails || (!brief && !oomOnly)) {
+                            Debug.getMemoryInfo(pid, mi);
+                        } else {
+                            mi.dalvikPss = (int)Debug.getPss(pid, tmpLong);
+                            mi.dalvikPrivateDirty = (int)tmpLong[0];
+                        }
+                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
+                                dumpDalvik, pid, r.baseName, 0, 0, 0, 0, 0, 0);
+                        if (isCheckinRequest) {
+                            pw.println();
+                        }
+                    }
+                    return;
+                }
+            }
+            pw.println("No process found for: " + args[opti]);
+            return;
+        }
+
+        if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest)) {
+            dumpDetails = true;
+        }
+
+        dumpApplicationMemoryUsageHeader(pw, uptime, realtime, isCheckinRequest, isCompact);
+
+        String[] innerArgs = new String[args.length-opti];
+        System.arraycopy(args, opti, innerArgs, 0, args.length-opti);
+
+        ArrayList<MemItem> procMems = new ArrayList<MemItem>();
+        final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>();
+        long nativePss=0, dalvikPss=0, otherPss=0;
+        long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS];
+
+        long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length];
+        ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[])
+                new ArrayList[DUMP_MEM_OOM_LABEL.length];
+
+        long totalPss = 0;
+        long cachedPss = 0;
+
+        Debug.MemoryInfo mi = null;
+        for (int i = procs.size() - 1 ; i >= 0 ; i--) {
+            final ProcessRecord r = procs.get(i);
+            final IApplicationThread thread;
+            final int pid;
+            final int oomAdj;
+            final boolean hasActivities;
+            synchronized (this) {
+                thread = r.thread;
+                pid = r.pid;
+                oomAdj = r.getSetAdjWithServices();
+                hasActivities = r.activities.size() > 0;
+            }
+            if (thread != null) {
+                if (!isCheckinRequest && dumpDetails) {
+                    pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
+                }
+                if (mi == null) {
+                    mi = new Debug.MemoryInfo();
+                }
+                if (dumpDetails || (!brief && !oomOnly)) {
+                    Debug.getMemoryInfo(pid, mi);
+                } else {
+                    mi.dalvikPss = (int)Debug.getPss(pid, tmpLong);
+                    mi.dalvikPrivateDirty = (int)tmpLong[0];
+                }
+                if (dumpDetails) {
+                    if (localOnly) {
+                        ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
+                                dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0);
+                        if (isCheckinRequest) {
+                            pw.println();
+                        }
+                    } else {
+                        try {
+                            pw.flush();
+                            thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
+                                    dumpDalvik, innerArgs);
+                        } catch (RemoteException e) {
+                            if (!isCheckinRequest) {
+                                pw.println("Got RemoteException!");
+                                pw.flush();
+                            }
+                        }
+                    }
+                }
+
+                final long myTotalPss = mi.getTotalPss();
+                final long myTotalUss = mi.getTotalUss();
+
+                synchronized (this) {
+                    if (r.thread != null && oomAdj == r.getSetAdjWithServices()) {
+                        // Record this for posterity if the process has been stable.
+                        r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList);
+                    }
+                }
+
+                if (!isCheckinRequest && mi != null) {
+                    totalPss += myTotalPss;
+                    MemItem pssItem = new MemItem(r.processName + " (pid " + pid +
+                            (hasActivities ? " / activities)" : ")"),
+                            r.processName, myTotalPss, pid, hasActivities);
+                    procMems.add(pssItem);
+                    procMemsMap.put(pid, pssItem);
+
+                    nativePss += mi.nativePss;
+                    dalvikPss += mi.dalvikPss;
+                    otherPss += mi.otherPss;
+                    for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                        long mem = mi.getOtherPss(j);
+                        miscPss[j] += mem;
+                        otherPss -= mem;
+                    }
+
+                    if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                        cachedPss += myTotalPss;
+                    }
+
+                    for (int oomIndex=0; oomIndex<oomPss.length; oomIndex++) {
+                        if (oomAdj <= DUMP_MEM_OOM_ADJ[oomIndex]
+                                || oomIndex == (oomPss.length-1)) {
+                            oomPss[oomIndex] += myTotalPss;
+                            if (oomProcs[oomIndex] == null) {
+                                oomProcs[oomIndex] = new ArrayList<MemItem>();
+                            }
+                            oomProcs[oomIndex].add(pssItem);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (!isCheckinRequest && procs.size() > 1) {
+            // If we are showing aggregations, also look for native processes to
+            // include so that our aggregations are more accurate.
+            updateCpuStatsNow();
+            synchronized (mProcessCpuThread) {
+                final int N = mProcessCpuTracker.countStats();
+                for (int i=0; i<N; i++) {
+                    ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
+                    if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
+                        if (mi == null) {
+                            mi = new Debug.MemoryInfo();
+                        }
+                        if (!brief && !oomOnly) {
+                            Debug.getMemoryInfo(st.pid, mi);
+                        } else {
+                            mi.nativePss = (int)Debug.getPss(st.pid, tmpLong);
+                            mi.nativePrivateDirty = (int)tmpLong[0];
+                        }
+
+                        final long myTotalPss = mi.getTotalPss();
+                        totalPss += myTotalPss;
+
+                        MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
+                                st.name, myTotalPss, st.pid, false);
+                        procMems.add(pssItem);
+
+                        nativePss += mi.nativePss;
+                        dalvikPss += mi.dalvikPss;
+                        otherPss += mi.otherPss;
+                        for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                            long mem = mi.getOtherPss(j);
+                            miscPss[j] += mem;
+                            otherPss -= mem;
+                        }
+                        oomPss[0] += myTotalPss;
+                        if (oomProcs[0] == null) {
+                            oomProcs[0] = new ArrayList<MemItem>();
+                        }
+                        oomProcs[0].add(pssItem);
+                    }
+                }
+            }
+
+            ArrayList<MemItem> catMems = new ArrayList<MemItem>();
+
+            catMems.add(new MemItem("Native", "Native", nativePss, -1));
+            catMems.add(new MemItem("Dalvik", "Dalvik", dalvikPss, -2));
+            catMems.add(new MemItem("Unknown", "Unknown", otherPss, -3));
+            for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
+                String label = Debug.MemoryInfo.getOtherLabel(j);
+                catMems.add(new MemItem(label, label, miscPss[j], j));
+            }
+
+            ArrayList<MemItem> oomMems = new ArrayList<MemItem>();
+            for (int j=0; j<oomPss.length; j++) {
+                if (oomPss[j] != 0) {
+                    String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j]
+                            : DUMP_MEM_OOM_LABEL[j];
+                    MemItem item = new MemItem(label, label, oomPss[j],
+                            DUMP_MEM_OOM_ADJ[j]);
+                    item.subitems = oomProcs[j];
+                    oomMems.add(item);
+                }
+            }
+
+            if (!brief && !oomOnly && !isCompact) {
+                pw.println();
+                pw.println("Total PSS by process:");
+                dumpMemItems(pw, "  ", "proc", procMems, true, isCompact);
+                pw.println();
+            }
+            if (!isCompact) {
+                pw.println("Total PSS by OOM adjustment:");
+            }
+            dumpMemItems(pw, "  ", "oom", oomMems, false, isCompact);
+            if (!brief && !oomOnly) {
+                PrintWriter out = categoryPw != null ? categoryPw : pw;
+                if (!isCompact) {
+                    out.println();
+                    out.println("Total PSS by category:");
+                }
+                dumpMemItems(out, "  ", "cat", catMems, true, isCompact);
+            }
+            if (!isCompact) {
+                pw.println();
+            }
+            MemInfoReader memInfo = new MemInfoReader();
+            memInfo.readMemInfo();
+            if (!brief) {
+                if (!isCompact) {
+                    pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
+                    pw.println(" kB");
+                    pw.print(" Free RAM: "); pw.print(cachedPss + memInfo.getCachedSizeKb()
+                            + memInfo.getFreeSizeKb()); pw.print(" kB (");
+                            pw.print(cachedPss); pw.print(" cached pss + ");
+                            pw.print(memInfo.getCachedSizeKb()); pw.print(" cached + ");
+                            pw.print(memInfo.getFreeSizeKb()); pw.println(" free)");
+                } else {
+                    pw.print("ram,"); pw.print(memInfo.getTotalSizeKb()); pw.print(",");
+                    pw.print(cachedPss + memInfo.getCachedSizeKb()
+                            + memInfo.getFreeSizeKb()); pw.print(",");
+                    pw.println(totalPss - cachedPss);
+                }
+            }
+            if (!isCompact) {
+                pw.print(" Used RAM: "); pw.print(totalPss - cachedPss
+                        + memInfo.getBuffersSizeKb() + memInfo.getShmemSizeKb()
+                        + memInfo.getSlabSizeKb()); pw.print(" kB (");
+                        pw.print(totalPss - cachedPss); pw.print(" used pss + ");
+                        pw.print(memInfo.getBuffersSizeKb()); pw.print(" buffers + ");
+                        pw.print(memInfo.getShmemSizeKb()); pw.print(" shmem + ");
+                        pw.print(memInfo.getSlabSizeKb()); pw.println(" slab)");
+                pw.print(" Lost RAM: "); pw.print(memInfo.getTotalSizeKb()
+                        - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+                        - memInfo.getBuffersSizeKb() - memInfo.getShmemSizeKb()
+                        - memInfo.getSlabSizeKb()); pw.println(" kB");
+            }
+            if (!brief) {
+                if (memInfo.getZramTotalSizeKb() != 0) {
+                    if (!isCompact) {
+                        pw.print("     ZRAM: "); pw.print(memInfo.getZramTotalSizeKb());
+                                pw.print(" kB physical used for ");
+                                pw.print(memInfo.getSwapTotalSizeKb()
+                                        - memInfo.getSwapFreeSizeKb());
+                                pw.print(" kB in swap (");
+                                pw.print(memInfo.getSwapTotalSizeKb());
+                                pw.println(" kB total swap)");
+                    } else {
+                        pw.print("zram,"); pw.print(memInfo.getZramTotalSizeKb()); pw.print(",");
+                                pw.print(memInfo.getSwapTotalSizeKb()); pw.print(",");
+                                pw.println(memInfo.getSwapFreeSizeKb());
+                    }
+                }
+                final int[] SINGLE_LONG_FORMAT = new int[] {
+                    Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG
+                };
+                long[] longOut = new long[1];
+                Process.readProcFile("/sys/kernel/mm/ksm/pages_shared",
+                        SINGLE_LONG_FORMAT, null, longOut, null);
+                long shared = longOut[0] * ProcessList.PAGE_SIZE / 1024;
+                longOut[0] = 0;
+                Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing",
+                        SINGLE_LONG_FORMAT, null, longOut, null);
+                long sharing = longOut[0] * ProcessList.PAGE_SIZE / 1024;
+                longOut[0] = 0;
+                Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared",
+                        SINGLE_LONG_FORMAT, null, longOut, null);
+                long unshared = longOut[0] * ProcessList.PAGE_SIZE / 1024;
+                longOut[0] = 0;
+                Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile",
+                        SINGLE_LONG_FORMAT, null, longOut, null);
+                long voltile = longOut[0] * ProcessList.PAGE_SIZE / 1024;
+                if (!isCompact) {
+                    if (sharing != 0 || shared != 0 || unshared != 0 || voltile != 0) {
+                        pw.print("      KSM: "); pw.print(sharing);
+                                pw.print(" kB saved from shared ");
+                                pw.print(shared); pw.println(" kB");
+                        pw.print("           "); pw.print(unshared); pw.print(" kB unshared; ");
+                                pw.print(voltile); pw.println(" kB volatile");
+                    }
+                    pw.print("   Tuning: ");
+                    pw.print(ActivityManager.staticGetMemoryClass());
+                    pw.print(" (large ");
+                    pw.print(ActivityManager.staticGetLargeMemoryClass());
+                    pw.print("), oom ");
+                    pw.print(mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024);
+                    pw.print(" kB");
+                    pw.print(", restore limit ");
+                    pw.print(mProcessList.getCachedRestoreThresholdKb());
+                    pw.print(" kB");
+                    if (ActivityManager.isLowRamDeviceStatic()) {
+                        pw.print(" (low-ram)");
+                    }
+                    if (ActivityManager.isHighEndGfx()) {
+                        pw.print(" (high-end-gfx)");
+                    }
+                    pw.println();
+                } else {
+                    pw.print("ksm,"); pw.print(sharing); pw.print(",");
+                    pw.print(shared); pw.print(","); pw.print(unshared); pw.print(",");
+                    pw.println(voltile);
+                    pw.print("tuning,");
+                    pw.print(ActivityManager.staticGetMemoryClass());
+                    pw.print(',');
+                    pw.print(ActivityManager.staticGetLargeMemoryClass());
+                    pw.print(',');
+                    pw.print(mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024);
+                    if (ActivityManager.isLowRamDeviceStatic()) {
+                        pw.print(",low-ram");
+                    }
+                    if (ActivityManager.isHighEndGfx()) {
+                        pw.print(",high-end-gfx");
+                    }
+                    pw.println();
+                }
+            }
+        }
+    }
+
+    /**
+     * Searches array of arguments for the specified string
+     * @param args array of argument strings
+     * @param value value to search for
+     * @return true if the value is contained in the array
+     */
+    private static boolean scanArgs(String[] args, String value) {
+        if (args != null) {
+            for (String arg : args) {
+                if (value.equals(arg)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private final boolean removeDyingProviderLocked(ProcessRecord proc,
+            ContentProviderRecord cpr, boolean always) {
+        final boolean inLaunching = mLaunchingProviders.contains(cpr);
+
+        if (!inLaunching || always) {
+            synchronized (cpr) {
+                cpr.launchingApp = null;
+                cpr.notifyAll();
+            }
+            mProviderMap.removeProviderByClass(cpr.name, UserHandle.getUserId(cpr.uid));
+            String names[] = cpr.info.authority.split(";");
+            for (int j = 0; j < names.length; j++) {
+                mProviderMap.removeProviderByName(names[j], UserHandle.getUserId(cpr.uid));
+            }
+        }
+
+        for (int i=0; i<cpr.connections.size(); i++) {
+            ContentProviderConnection conn = cpr.connections.get(i);
+            if (conn.waiting) {
+                // If this connection is waiting for the provider, then we don't
+                // need to mess with its process unless we are always removing
+                // or for some reason the provider is not currently launching.
+                if (inLaunching && !always) {
+                    continue;
+                }
+            }
+            ProcessRecord capp = conn.client;
+            conn.dead = true;
+            if (conn.stableCount > 0) {
+                if (!capp.persistent && capp.thread != null
+                        && capp.pid != 0
+                        && capp.pid != MY_PID) {
+                    killUnneededProcessLocked(capp, "depends on provider "
+                            + cpr.name.flattenToShortString()
+                            + " in dying proc " + (proc != null ? proc.processName : "??"));
+                }
+            } else if (capp.thread != null && conn.provider.provider != null) {
+                try {
+                    capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
+                } catch (RemoteException e) {
+                }
+                // In the protocol here, we don't expect the client to correctly
+                // clean up this connection, we'll just remove it.
+                cpr.connections.remove(i);
+                conn.client.conProviders.remove(conn);
+            }
+        }
+
+        if (inLaunching && always) {
+            mLaunchingProviders.remove(cpr);
+        }
+        return inLaunching;
+    }
+
+    /**
+     * Main code for cleaning up a process when it has gone away.  This is
+     * called both as a result of the process dying, or directly when stopping
+     * a process when running in single process mode.
+     */
+    private final void cleanUpApplicationRecordLocked(ProcessRecord app,
+            boolean restarting, boolean allowRestart, int index) {
+        if (index >= 0) {
+            removeLruProcessLocked(app);
+        }
+
+        mProcessesToGc.remove(app);
+        mPendingPssProcesses.remove(app);
+
+        // Dismiss any open dialogs.
+        if (app.crashDialog != null && !app.forceCrashReport) {
+            app.crashDialog.dismiss();
+            app.crashDialog = null;
+        }
+        if (app.anrDialog != null) {
+            app.anrDialog.dismiss();
+            app.anrDialog = null;
+        }
+        if (app.waitDialog != null) {
+            app.waitDialog.dismiss();
+            app.waitDialog = null;
+        }
+
+        app.crashing = false;
+        app.notResponding = false;
+
+        app.resetPackageList(mProcessStats);
+        app.unlinkDeathRecipient();
+        app.makeInactive(mProcessStats);
+        app.forcingToForeground = null;
+        app.foregroundServices = false;
+        app.foregroundActivities = false;
+        app.hasShownUi = false;
+        app.hasAboveClient = false;
+
+        mServices.killServicesLocked(app, allowRestart);
+
+        boolean restart = false;
+
+        // Remove published content providers.
+        for (int i=app.pubProviders.size()-1; i>=0; i--) {
+            ContentProviderRecord cpr = app.pubProviders.valueAt(i);
+            final boolean always = app.bad || !allowRestart;
+            if (removeDyingProviderLocked(app, cpr, always) || always) {
+                // We left the provider in the launching list, need to
+                // restart it.
+                restart = true;
+            }
+
+            cpr.provider = null;
+            cpr.proc = null;
+        }
+        app.pubProviders.clear();
+
+        // Take care of any launching providers waiting for this process.
+        if (checkAppInLaunchingProvidersLocked(app, false)) {
+            restart = true;
+        }
+
+        // Unregister from connected content providers.
+        if (!app.conProviders.isEmpty()) {
+            for (int i=0; i<app.conProviders.size(); i++) {
+                ContentProviderConnection conn = app.conProviders.get(i);
+                conn.provider.connections.remove(conn);
+            }
+            app.conProviders.clear();
+        }
+
+        // At this point there may be remaining entries in mLaunchingProviders
+        // where we were the only one waiting, so they are no longer of use.
+        // Look for these and clean up if found.
+        // XXX Commented out for now.  Trying to figure out a way to reproduce
+        // the actual situation to identify what is actually going on.
+        if (false) {
+            for (int i=0; i<mLaunchingProviders.size(); i++) {
+                ContentProviderRecord cpr = (ContentProviderRecord)
+                        mLaunchingProviders.get(i);
+                if (cpr.connections.size() <= 0 && !cpr.hasExternalProcessHandles()) {
+                    synchronized (cpr) {
+                        cpr.launchingApp = null;
+                        cpr.notifyAll();
+                    }
+                }
+            }
+        }
+
+        skipCurrentReceiverLocked(app);
+
+        // Unregister any receivers.
+        for (int i=app.receivers.size()-1; i>=0; i--) {
+            removeReceiverLocked(app.receivers.valueAt(i));
+        }
+        app.receivers.clear();
+
+        // If the app is undergoing backup, tell the backup manager about it
+        if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {
+            if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG, "App "
+                    + mBackupTarget.appInfo + " died during backup");
+            try {
+                IBackupManager bm = IBackupManager.Stub.asInterface(
+                        ServiceManager.getService(Context.BACKUP_SERVICE));
+                bm.agentDisconnected(app.info.packageName);
+            } catch (RemoteException e) {
+                // can't happen; backup manager is local
+            }
+        }
+
+        for (int i = mPendingProcessChanges.size()-1; i>=0; i--) {
+            ProcessChangeItem item = mPendingProcessChanges.get(i);
+            if (item.pid == app.pid) {
+                mPendingProcessChanges.remove(i);
+                mAvailProcessChanges.add(item);
+            }
+        }
+        mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
+
+        // If the caller is restarting this app, then leave it in its
+        // current lists and let the caller take care of it.
+        if (restarting) {
+            return;
+        }
+
+        if (!app.persistent || app.isolated) {
+            if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG,
+                    "Removing non-persistent process during cleanup: " + app);
+            mProcessNames.remove(app.processName, app.uid);
+            mIsolatedProcesses.remove(app.uid);
+            if (mHeavyWeightProcess == app) {
+                mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
+                        mHeavyWeightProcess.userId, 0));
+                mHeavyWeightProcess = null;
+            }
+        } else if (!app.removed) {
+            // This app is persistent, so we need to keep its record around.
+            // If it is not already on the pending app list, add it there
+            // and start a new process for it.
+            if (mPersistentStartingProcesses.indexOf(app) < 0) {
+                mPersistentStartingProcesses.add(app);
+                restart = true;
+            }
+        }
+        if ((DEBUG_PROCESSES || DEBUG_CLEANUP) && mProcessesOnHold.contains(app)) Slog.v(TAG,
+                "Clean-up removing on hold: " + app);
+        mProcessesOnHold.remove(app);
+
+        if (app == mHomeProcess) {
+            mHomeProcess = null;
+        }
+        if (app == mPreviousProcess) {
+            mPreviousProcess = null;
+        }
+
+        if (restart && !app.isolated) {
+            // We have components that still need to be running in the
+            // process, so re-launch it.
+            mProcessNames.put(app.processName, app.uid, app);
+            startProcessLocked(app, "restart", app.processName);
+        } else if (app.pid > 0 && app.pid != MY_PID) {
+            // Goodbye!
+            synchronized (mPidsSelfLocked) {
+                mPidsSelfLocked.remove(app.pid);
+                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+            }
+            app.setPid(0);
+        }
+    }
+
+    boolean checkAppInLaunchingProvidersLocked(ProcessRecord app, boolean alwaysBad) {
+        // Look through the content providers we are waiting to have launched,
+        // and if any run in this process then either schedule a restart of
+        // the process or kill the client waiting for it if this process has
+        // gone bad.
+        int NL = mLaunchingProviders.size();
+        boolean restart = false;
+        for (int i=0; i<NL; i++) {
+            ContentProviderRecord cpr = mLaunchingProviders.get(i);
+            if (cpr.launchingApp == app) {
+                if (!alwaysBad && !app.bad) {
+                    restart = true;
+                } else {
+                    removeDyingProviderLocked(app, cpr, true);
+                    // cpr should have been removed from mLaunchingProviders
+                    NL = mLaunchingProviders.size();
+                    i--;
+                }
+            }
+        }
+        return restart;
+    }
+    
+    // =========================================================
+    // SERVICES
+    // =========================================================
+
+    public List<ActivityManager.RunningServiceInfo> getServices(int maxNum,
+            int flags) {
+        enforceNotIsolatedCaller("getServices");
+        synchronized (this) {
+            return mServices.getRunningServiceInfoLocked(maxNum, flags);
+        }
+    }
+
+    public PendingIntent getRunningServiceControlPanel(ComponentName name) {
+        enforceNotIsolatedCaller("getRunningServiceControlPanel");
+        synchronized (this) {
+            return mServices.getRunningServiceControlPanelLocked(name);
+        }
+    }
+    
+    public ComponentName startService(IApplicationThread caller, Intent service,
+            String resolvedType, int userId) {
+        enforceNotIsolatedCaller("startService");
+        // Refuse possible leaked file descriptors
+        if (service != null && service.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        if (DEBUG_SERVICE)
+            Slog.v(TAG, "startService: " + service + " type=" + resolvedType);
+        synchronized(this) {
+            final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
+            final long origId = Binder.clearCallingIdentity();
+            ComponentName res = mServices.startServiceLocked(caller, service,
+                    resolvedType, callingPid, callingUid, userId);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    ComponentName startServiceInPackage(int uid,
+            Intent service, String resolvedType, int userId) {
+        synchronized(this) {
+            if (DEBUG_SERVICE)
+                Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType);
+            final long origId = Binder.clearCallingIdentity();
+            ComponentName res = mServices.startServiceLocked(null, service,
+                    resolvedType, -1, uid, userId);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    public int stopService(IApplicationThread caller, Intent service,
+            String resolvedType, int userId) {
+        enforceNotIsolatedCaller("stopService");
+        // Refuse possible leaked file descriptors
+        if (service != null && service.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            return mServices.stopServiceLocked(caller, service, resolvedType, userId);
+        }
+    }
+
+    public IBinder peekService(Intent service, String resolvedType) {
+        enforceNotIsolatedCaller("peekService");
+        // Refuse possible leaked file descriptors
+        if (service != null && service.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+        synchronized(this) {
+            return mServices.peekServiceLocked(service, resolvedType);
+        }
+    }
+    
+    public boolean stopServiceToken(ComponentName className, IBinder token,
+            int startId) {
+        synchronized(this) {
+            return mServices.stopServiceTokenLocked(className, token, startId);
+        }
+    }
+
+    public void setServiceForeground(ComponentName className, IBinder token,
+            int id, Notification notification, boolean removeNotification) {
+        synchronized(this) {
+            mServices.setServiceForegroundLocked(className, token, id, notification,
+                    removeNotification);
+        }
+    }
+
+    public int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll,
+            boolean requireFull, String name, String callerPackage) {
+        final int callingUserId = UserHandle.getUserId(callingUid);
+        if (callingUserId != userId) {
+            if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+                if ((requireFull || checkComponentPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED)
+                        && checkComponentPermission(
+                                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                                callingPid, callingUid, -1, true)
+                                != PackageManager.PERMISSION_GRANTED) {
+                    if (userId == UserHandle.USER_CURRENT_OR_SELF) {
+                        // In this case, they would like to just execute as their
+                        // owner user instead of failing.
+                        userId = callingUserId;
+                    } else {
+                        StringBuilder builder = new StringBuilder(128);
+                        builder.append("Permission Denial: ");
+                        builder.append(name);
+                        if (callerPackage != null) {
+                            builder.append(" from ");
+                            builder.append(callerPackage);
+                        }
+                        builder.append(" asks to run as user ");
+                        builder.append(userId);
+                        builder.append(" but is calling from user ");
+                        builder.append(UserHandle.getUserId(callingUid));
+                        builder.append("; this requires ");
+                        builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+                        if (!requireFull) {
+                            builder.append(" or ");
+                            builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+                        }
+                        String msg = builder.toString();
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    }
+                }
+            }
+            if (userId == UserHandle.USER_CURRENT
+                    || userId == UserHandle.USER_CURRENT_OR_SELF) {
+                // Note that we may be accessing this outside of a lock...
+                // shouldn't be a big deal, if this is being called outside
+                // of a locked context there is intrinsically a race with
+                // the value the caller will receive and someone else changing it.
+                userId = mCurrentUserId;
+            }
+            if (!allowAll && userId < 0) {
+                throw new IllegalArgumentException(
+                        "Call does not support special user #" + userId);
+            }
+        }
+        return userId;
+    }
+
+    boolean isSingleton(String componentProcessName, ApplicationInfo aInfo,
+            String className, int flags) {
+        boolean result = false;
+        if (UserHandle.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) {
+            if ((flags&ServiceInfo.FLAG_SINGLE_USER) != 0) {
+                if (ActivityManager.checkUidPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        aInfo.uid) != PackageManager.PERMISSION_GRANTED) {
+                    ComponentName comp = new ComponentName(aInfo.packageName, className);
+                    String msg = "Permission Denial: Component " + comp.flattenToShortString()
+                            + " requests FLAG_SINGLE_USER, but app does not hold "
+                            + android.Manifest.permission.INTERACT_ACROSS_USERS;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                }
+                result = true;
+            }
+        } else if (componentProcessName == aInfo.packageName) {
+            result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0;
+        } else if ("system".equals(componentProcessName)) {
+            result = true;
+        }
+        if (DEBUG_MU) {
+            Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo
+                    + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result);
+        }
+        return result;
+    }
+
+    public int bindService(IApplicationThread caller, IBinder token,
+            Intent service, String resolvedType,
+            IServiceConnection connection, int flags, int userId) {
+        enforceNotIsolatedCaller("bindService");
+        // Refuse possible leaked file descriptors
+        if (service != null && service.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            return mServices.bindServiceLocked(caller, token, service, resolvedType,
+                    connection, flags, userId);
+        }
+    }
+
+    public boolean unbindService(IServiceConnection connection) {
+        synchronized (this) {
+            return mServices.unbindServiceLocked(connection);
+        }
+    }
+
+    public void publishService(IBinder token, Intent intent, IBinder service) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            if (!(token instanceof ServiceRecord)) {
+                throw new IllegalArgumentException("Invalid service token");
+            }
+            mServices.publishServiceLocked((ServiceRecord)token, intent, service);
+        }
+    }
+
+    public void unbindFinished(IBinder token, Intent intent, boolean doRebind) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            mServices.unbindFinishedLocked((ServiceRecord)token, intent, doRebind);
+        }
+    }
+
+    public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
+        synchronized(this) {
+            if (!(token instanceof ServiceRecord)) {
+                throw new IllegalArgumentException("Invalid service token");
+            }
+            mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res);
+        }
+    }
+    
+    // =========================================================
+    // BACKUP AND RESTORE
+    // =========================================================
+    
+    // Cause the target app to be launched if necessary and its backup agent
+    // instantiated.  The backup agent will invoke backupAgentCreated() on the
+    // activity manager to announce its creation.
+    public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
+        if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
+        enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent");
+
+        synchronized(this) {
+            // !!! TODO: currently no check here that we're already bound
+            BatteryStatsImpl.Uid.Pkg.Serv ss = null;
+            BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+            synchronized (stats) {
+                ss = stats.getServiceStatsLocked(app.uid, app.packageName, app.name);
+            }
+
+            // Backup agent is now in use, its package can't be stopped.
+            try {
+                AppGlobals.getPackageManager().setPackageStoppedState(
+                        app.packageName, false, UserHandle.getUserId(app.uid));
+            } catch (RemoteException e) {
+            } catch (IllegalArgumentException e) {
+                Slog.w(TAG, "Failed trying to unstop package "
+                        + app.packageName + ": " + e);
+            }
+
+            BackupRecord r = new BackupRecord(ss, app, backupMode);
+            ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL)
+                    ? new ComponentName(app.packageName, app.backupAgentName)
+                    : new ComponentName("android", "FullBackupAgent");
+            // startProcessLocked() returns existing proc's record if it's already running
+            ProcessRecord proc = startProcessLocked(app.processName, app,
+                    false, 0, "backup", hostingName, false, false, false);
+            if (proc == null) {
+                Slog.e(TAG, "Unable to start backup agent process " + r);
+                return false;
+            }
+
+            r.app = proc;
+            mBackupTarget = r;
+            mBackupAppName = app.packageName;
+
+            // Try not to kill the process during backup
+            updateOomAdjLocked(proc);
+
+            // If the process is already attached, schedule the creation of the backup agent now.
+            // If it is not yet live, this will be done when it attaches to the framework.
+            if (proc.thread != null) {
+                if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc);
+                try {
+                    proc.thread.scheduleCreateBackupAgent(app,
+                            compatibilityInfoForPackageLocked(app), backupMode);
+                } catch (RemoteException e) {
+                    // Will time out on the backup manager side
+                }
+            } else {
+                if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach");
+            }
+            // Invariants: at this point, the target app process exists and the application
+            // is either already running or in the process of coming up.  mBackupTarget and
+            // mBackupAppName describe the app, so that when it binds back to the AM we
+            // know that it's scheduled for a backup-agent operation.
+        }
+        
+        return true;
+    }
+
+    @Override
+    public void clearPendingBackup() {
+        if (DEBUG_BACKUP) Slog.v(TAG, "clearPendingBackup");
+        enforceCallingPermission("android.permission.BACKUP", "clearPendingBackup");
+
+        synchronized (this) {
+            mBackupTarget = null;
+            mBackupAppName = null;
+        }
+    }
+
+    // A backup agent has just come up                    
+    public void backupAgentCreated(String agentPackageName, IBinder agent) {
+        if (DEBUG_BACKUP) Slog.v(TAG, "backupAgentCreated: " + agentPackageName
+                + " = " + agent);
+
+        synchronized(this) {
+            if (!agentPackageName.equals(mBackupAppName)) {
+                Slog.e(TAG, "Backup agent created for " + agentPackageName + " but not requested!");
+                return;
+            }
+        }
+
+        long oldIdent = Binder.clearCallingIdentity();
+        try {
+            IBackupManager bm = IBackupManager.Stub.asInterface(
+                    ServiceManager.getService(Context.BACKUP_SERVICE));
+            bm.agentConnected(agentPackageName, agent);
+        } catch (RemoteException e) {
+            // can't happen; the backup manager service is local
+        } catch (Exception e) {
+            Slog.w(TAG, "Exception trying to deliver BackupAgent binding: ");
+            e.printStackTrace();
+        } finally {
+            Binder.restoreCallingIdentity(oldIdent);
+        }
+    }
+
+    // done with this agent
+    public void unbindBackupAgent(ApplicationInfo appInfo) {
+        if (DEBUG_BACKUP) Slog.v(TAG, "unbindBackupAgent: " + appInfo);
+        if (appInfo == null) {
+            Slog.w(TAG, "unbind backup agent for null app");
+            return;
+        }
+
+        synchronized(this) {
+            try {
+                if (mBackupAppName == null) {
+                    Slog.w(TAG, "Unbinding backup agent with no active backup");
+                    return;
+                }
+
+                if (!mBackupAppName.equals(appInfo.packageName)) {
+                    Slog.e(TAG, "Unbind of " + appInfo + " but is not the current backup target");
+                    return;
+                }
+
+                // Not backing this app up any more; reset its OOM adjustment
+                final ProcessRecord proc = mBackupTarget.app;
+                updateOomAdjLocked(proc);
+
+                // If the app crashed during backup, 'thread' will be null here
+                if (proc.thread != null) {
+                    try {
+                        proc.thread.scheduleDestroyBackupAgent(appInfo,
+                                compatibilityInfoForPackageLocked(appInfo));
+                    } catch (Exception e) {
+                        Slog.e(TAG, "Exception when unbinding backup agent:");
+                        e.printStackTrace();
+                    }
+                }
+            } finally {
+                mBackupTarget = null;
+                mBackupAppName = null;
+            }
+        }
+    }
+    // =========================================================
+    // BROADCASTS
+    // =========================================================
+
+    private final List getStickiesLocked(String action, IntentFilter filter,
+            List cur, int userId) {
+        final ContentResolver resolver = mContext.getContentResolver();
+        ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
+        if (stickies == null) {
+            return cur;
+        }
+        final ArrayList<Intent> list = stickies.get(action);
+        if (list == null) {
+            return cur;
+        }
+        int N = list.size();
+        for (int i=0; i<N; i++) {
+            Intent intent = list.get(i);
+            if (filter.match(resolver, intent, true, TAG) >= 0) {
+                if (cur == null) {
+                    cur = new ArrayList<Intent>();
+                }
+                cur.add(intent);
+            }
+        }
+        return cur;
+    }
+
+    boolean isPendingBroadcastProcessLocked(int pid) {
+        return mFgBroadcastQueue.isPendingBroadcastProcessLocked(pid)
+                || mBgBroadcastQueue.isPendingBroadcastProcessLocked(pid);
+    }
+
+    void skipPendingBroadcastLocked(int pid) {
+            Slog.w(TAG, "Unattached app died before broadcast acknowledged, skipping");
+            for (BroadcastQueue queue : mBroadcastQueues) {
+                queue.skipPendingBroadcastLocked(pid);
+            }
+    }
+
+    // The app just attached; send any pending broadcasts that it should receive
+    boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+        boolean didSomething = false;
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            didSomething |= queue.sendPendingBroadcastsLocked(app);
+        }
+        return didSomething;
+    }
+
+    public Intent registerReceiver(IApplicationThread caller, String callerPackage,
+            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
+        enforceNotIsolatedCaller("registerReceiver");
+        int callingUid;
+        int callingPid;
+        synchronized(this) {
+            ProcessRecord callerApp = null;
+            if (caller != null) {
+                callerApp = getRecordForAppLocked(caller);
+                if (callerApp == null) {
+                    throw new SecurityException(
+                            "Unable to find app for caller " + caller
+                            + " (pid=" + Binder.getCallingPid()
+                            + ") when registering receiver " + receiver);
+                }
+                if (callerApp.info.uid != Process.SYSTEM_UID &&
+                        !callerApp.pkgList.containsKey(callerPackage) &&
+                        !"android".equals(callerPackage)) {
+                    throw new SecurityException("Given caller package " + callerPackage
+                            + " is not running in process " + callerApp);
+                }
+                callingUid = callerApp.info.uid;
+                callingPid = callerApp.pid;
+            } else {
+                callerPackage = null;
+                callingUid = Binder.getCallingUid();
+                callingPid = Binder.getCallingPid();
+            }
+
+            userId = this.handleIncomingUser(callingPid, callingUid, userId,
+                    true, true, "registerReceiver", callerPackage);
+
+            List allSticky = null;
+
+            // Look for any matching sticky broadcasts...
+            Iterator actions = filter.actionsIterator();
+            if (actions != null) {
+                while (actions.hasNext()) {
+                    String action = (String)actions.next();
+                    allSticky = getStickiesLocked(action, filter, allSticky,
+                            UserHandle.USER_ALL);
+                    allSticky = getStickiesLocked(action, filter, allSticky,
+                            UserHandle.getUserId(callingUid));
+                }
+            } else {
+                allSticky = getStickiesLocked(null, filter, allSticky,
+                        UserHandle.USER_ALL);
+                allSticky = getStickiesLocked(null, filter, allSticky,
+                        UserHandle.getUserId(callingUid));
+            }
+
+            // The first sticky in the list is returned directly back to
+            // the client.
+            Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;
+
+            if (DEBUG_BROADCAST) Slog.v(TAG, "Register receiver " + filter
+                    + ": " + sticky);
+
+            if (receiver == null) {
+                return sticky;
+            }
+
+            ReceiverList rl
+                = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
+            if (rl == null) {
+                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
+                        userId, receiver);
+                if (rl.app != null) {
+                    rl.app.receivers.add(rl);
+                } else {
+                    try {
+                        receiver.asBinder().linkToDeath(rl, 0);
+                    } catch (RemoteException e) {
+                        return sticky;
+                    }
+                    rl.linkedToDeath = true;
+                }
+                mRegisteredReceivers.put(receiver.asBinder(), rl);
+            } else if (rl.uid != callingUid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for uid " + callingUid
+                        + " was previously registered for uid " + rl.uid);
+            } else if (rl.pid != callingPid) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for pid " + callingPid
+                        + " was previously registered for pid " + rl.pid);
+            } else if (rl.userId != userId) {
+                throw new IllegalArgumentException(
+                        "Receiver requested to register for user " + userId
+                        + " was previously registered for user " + rl.userId);
+            }
+            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
+                    permission, callingUid, userId);
+            rl.add(bf);
+            if (!bf.debugCheck()) {
+                Slog.w(TAG, "==> For Dynamic broadast");
+            }
+            mReceiverResolver.addFilter(bf);
+
+            // Enqueue broadcasts for all existing stickies that match
+            // this filter.
+            if (allSticky != null) {
+                ArrayList receivers = new ArrayList();
+                receivers.add(bf);
+
+                int N = allSticky.size();
+                for (int i=0; i<N; i++) {
+                    Intent intent = (Intent)allSticky.get(i);
+                    BroadcastQueue queue = broadcastQueueForIntent(intent);
+                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
+                            null, -1, -1, null, null, AppOpsManager.OP_NONE, receivers, null, 0,
+                            null, null, false, true, true, -1);
+                    queue.enqueueParallelBroadcastLocked(r);
+                    queue.scheduleBroadcastsLocked();
+                }
+            }
+
+            return sticky;
+        }
+    }
+
+    public void unregisterReceiver(IIntentReceiver receiver) {
+        if (DEBUG_BROADCAST) Slog.v(TAG, "Unregister receiver: " + receiver);
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            boolean doTrim = false;
+
+            synchronized(this) {
+                ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
+                if (rl != null) {
+                    if (rl.curBroadcast != null) {
+                        BroadcastRecord r = rl.curBroadcast;
+                        final boolean doNext = finishReceiverLocked(
+                                receiver.asBinder(), r.resultCode, r.resultData,
+                                r.resultExtras, r.resultAbort);
+                        if (doNext) {
+                            doTrim = true;
+                            r.queue.processNextBroadcast(false);
+                        }
+                    }
+
+                    if (rl.app != null) {
+                        rl.app.receivers.remove(rl);
+                    }
+                    removeReceiverLocked(rl);
+                    if (rl.linkedToDeath) {
+                        rl.linkedToDeath = false;
+                        rl.receiver.asBinder().unlinkToDeath(rl, 0);
+                    }
+                }
+            }
+
+            // If we actually concluded any broadcasts, we might now be able
+            // to trim the recipients' apps from our working set
+            if (doTrim) {
+                trimApplications();
+                return;
+            }
+
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void removeReceiverLocked(ReceiverList rl) {
+        mRegisteredReceivers.remove(rl.receiver.asBinder());
+        int N = rl.size();
+        for (int i=0; i<N; i++) {
+            mReceiverResolver.removeFilter(rl.get(i));
+        }
+    }
+    
+    private final void sendPackageBroadcastLocked(int cmd, String[] packages, int userId) {
+        for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+            ProcessRecord r = mLruProcesses.get(i);
+            if (r.thread != null && (userId == UserHandle.USER_ALL || r.userId == userId)) {
+                try {
+                    r.thread.dispatchPackageBroadcast(cmd, packages);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
+            int[] users) {
+        List<ResolveInfo> receivers = null;
+        try {
+            HashSet<ComponentName> singleUserReceivers = null;
+            boolean scannedFirstReceivers = false;
+            for (int user : users) {
+                List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
+                        .queryIntentReceivers(intent, resolvedType, STOCK_PM_FLAGS, user);
+                if (user != 0 && newReceivers != null) {
+                    // If this is not the primary user, we need to check for
+                    // any receivers that should be filtered out.
+                    for (int i=0; i<newReceivers.size(); i++) {
+                        ResolveInfo ri = newReceivers.get(i);
+                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_PRIMARY_USER_ONLY) != 0) {
+                            newReceivers.remove(i);
+                            i--;
+                        }
+                    }
+                }
+                if (newReceivers != null && newReceivers.size() == 0) {
+                    newReceivers = null;
+                }
+                if (receivers == null) {
+                    receivers = newReceivers;
+                } else if (newReceivers != null) {
+                    // We need to concatenate the additional receivers
+                    // found with what we have do far.  This would be easy,
+                    // but we also need to de-dup any receivers that are
+                    // singleUser.
+                    if (!scannedFirstReceivers) {
+                        // Collect any single user receivers we had already retrieved.
+                        scannedFirstReceivers = true;
+                        for (int i=0; i<receivers.size(); i++) {
+                            ResolveInfo ri = receivers.get(i);
+                            if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                                ComponentName cn = new ComponentName(
+                                        ri.activityInfo.packageName, ri.activityInfo.name);
+                                if (singleUserReceivers == null) {
+                                    singleUserReceivers = new HashSet<ComponentName>();
+                                }
+                                singleUserReceivers.add(cn);
+                            }
+                        }
+                    }
+                    // Add the new results to the existing results, tracking
+                    // and de-dupping single user receivers.
+                    for (int i=0; i<newReceivers.size(); i++) {
+                        ResolveInfo ri = newReceivers.get(i);
+                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                            ComponentName cn = new ComponentName(
+                                    ri.activityInfo.packageName, ri.activityInfo.name);
+                            if (singleUserReceivers == null) {
+                                singleUserReceivers = new HashSet<ComponentName>();
+                            }
+                            if (!singleUserReceivers.contains(cn)) {
+                                singleUserReceivers.add(cn);
+                                receivers.add(ri);
+                            }
+                        } else {
+                            receivers.add(ri);
+                        }
+                    }
+                }
+            }
+        } catch (RemoteException ex) {
+            // pm is in same process, this will never happen.
+        }
+        return receivers;
+    }
+
+    private final int broadcastIntentLocked(ProcessRecord callerApp,
+            String callerPackage, Intent intent, String resolvedType,
+            IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle map, String requiredPermission, int appOp,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int userId) {
+        intent = new Intent(intent);
+
+        // By default broadcasts do not go to stopped apps.
+        intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
+
+        if (DEBUG_BROADCAST_LIGHT) Slog.v(
+            TAG, (sticky ? "Broadcast sticky: ": "Broadcast: ") + intent
+            + " ordered=" + ordered + " userid=" + userId);
+        if ((resultTo != null) && !ordered) {
+            Slog.w(TAG, "Broadcast " + intent + " not ordered but result callback requested!");
+        }
+
+        userId = handleIncomingUser(callingPid, callingUid, userId,
+                true, false, "broadcast", callerPackage);
+
+        // Make sure that the user who is receiving this broadcast is started.
+        // If not, we will just skip it.
+        if (userId != UserHandle.USER_ALL && mStartedUsers.get(userId) == null) {
+            if (callingUid != Process.SYSTEM_UID || (intent.getFlags()
+                    & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
+                Slog.w(TAG, "Skipping broadcast of " + intent
+                        + ": user " + userId + " is stopped");
+                return ActivityManager.BROADCAST_SUCCESS;
+            }
+        }
+
+        /*
+         * Prevent non-system code (defined here to be non-persistent
+         * processes) from sending protected broadcasts.
+         */
+        int callingAppId = UserHandle.getAppId(callingUid);
+        if (callingAppId == Process.SYSTEM_UID || callingAppId == Process.PHONE_UID
+            || callingAppId == Process.SHELL_UID || callingAppId == Process.BLUETOOTH_UID ||
+            callingUid == 0) {
+            // Always okay.
+        } else if (callerApp == null || !callerApp.persistent) {
+            try {
+                if (AppGlobals.getPackageManager().isProtectedBroadcast(
+                        intent.getAction())) {
+                    String msg = "Permission Denial: not allowed to send broadcast "
+                            + intent.getAction() + " from pid="
+                            + callingPid + ", uid=" + callingUid;
+                    Slog.w(TAG, msg);
+                    throw new SecurityException(msg);
+                } else if (AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(intent.getAction())) {
+                    // Special case for compatibility: we don't want apps to send this,
+                    // but historically it has not been protected and apps may be using it
+                    // to poke their own app widget.  So, instead of making it protected,
+                    // just limit it to the caller.
+                    if (callerApp == null) {
+                        String msg = "Permission Denial: not allowed to send broadcast "
+                                + intent.getAction() + " from unknown caller.";
+                        Slog.w(TAG, msg);
+                        throw new SecurityException(msg);
+                    } else if (intent.getComponent() != null) {
+                        // They are good enough to send to an explicit component...  verify
+                        // it is being sent to the calling app.
+                        if (!intent.getComponent().getPackageName().equals(
+                                callerApp.info.packageName)) {
+                            String msg = "Permission Denial: not allowed to send broadcast "
+                                    + intent.getAction() + " to "
+                                    + intent.getComponent().getPackageName() + " from "
+                                    + callerApp.info.packageName;
+                            Slog.w(TAG, msg);
+                            throw new SecurityException(msg);
+                        }
+                    } else {
+                        // Limit broadcast to their own package.
+                        intent.setPackage(callerApp.info.packageName);
+                    }
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Remote exception", e);
+                return ActivityManager.BROADCAST_SUCCESS;
+            }
+        }
+
+        // Handle special intents: if this broadcast is from the package
+        // manager about a package being removed, we need to remove all of
+        // its activities from the history stack.
+        final boolean uidRemoved = Intent.ACTION_UID_REMOVED.equals(
+                intent.getAction());
+        if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())
+                || Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())
+                || Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())
+                || uidRemoved) {
+            if (checkComponentPermission(
+                    android.Manifest.permission.BROADCAST_PACKAGE_REMOVED,
+                    callingPid, callingUid, -1, true)
+                    == PackageManager.PERMISSION_GRANTED) {
+                if (uidRemoved) {
+                    final Bundle intentExtras = intent.getExtras();
+                    final int uid = intentExtras != null
+                            ? intentExtras.getInt(Intent.EXTRA_UID) : -1;
+                    if (uid >= 0) {
+                        BatteryStatsImpl bs = mBatteryStatsService.getActiveStatistics();
+                        synchronized (bs) {
+                            bs.removeUidStatsLocked(uid);
+                        }
+                        mAppOpsService.uidRemoved(uid);
+                    }
+                } else {
+                    // If resources are unavailable just force stop all
+                    // those packages and flush the attribute cache as well.
+                    if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(intent.getAction())) {
+                        String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                        if (list != null && (list.length > 0)) {
+                            for (String pkg : list) {
+                                forceStopPackageLocked(pkg, -1, false, true, true, false, userId,
+                                        "storage unmount");
+                            }
+                            sendPackageBroadcastLocked(
+                                    IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId);
+                        }
+                    } else {
+                        Uri data = intent.getData();
+                        String ssp;
+                        if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
+                            boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals(
+                                    intent.getAction());
+                            if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) {
+                                forceStopPackageLocked(ssp, UserHandle.getAppId(
+                                        intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true,
+                                        false, userId, removed ? "pkg removed" : "pkg changed");
+                            }
+                            if (removed) {
+                                sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED,
+                                        new String[] {ssp}, userId);
+                                if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                                    mAppOpsService.packageRemoved(
+                                            intent.getIntExtra(Intent.EXTRA_UID, -1), ssp);
+
+                                    // Remove all permissions granted from/to this package
+                                    removeUriPermissionsForPackageLocked(ssp, userId, true);
+                                }
+                            }
+                        }
+                    }
+                }
+            } else {
+                String msg = "Permission Denial: " + intent.getAction()
+                        + " broadcast from " + callerPackage + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")"
+                        + " requires "
+                        + android.Manifest.permission.BROADCAST_PACKAGE_REMOVED;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+
+        // Special case for adding a package: by default turn on compatibility
+        // mode.
+        } else if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
+            Uri data = intent.getData();
+            String ssp;
+            if (data != null && (ssp=data.getSchemeSpecificPart()) != null) {
+                mCompatModePackages.handlePackageAddedLocked(ssp,
+                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false));
+            }
+        }
+
+        /*
+         * If this is the time zone changed action, queue up a message that will reset the timezone
+         * of all currently running processes. This message will get queued up before the broadcast
+         * happens.
+         */
+        if (intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
+            mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
+        }
+
+        if (intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) {
+            mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG);
+        }
+
+        if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) {
+            ProxyProperties proxy = intent.getParcelableExtra("proxy");
+            mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy));
+        }
+
+        // Add to the sticky list if requested.
+        if (sticky) {
+            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
+                    callingPid, callingUid)
+                    != PackageManager.PERMISSION_GRANTED) {
+                String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
+                        + callingPid + ", uid=" + callingUid
+                        + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            if (requiredPermission != null) {
+                Slog.w(TAG, "Can't broadcast sticky intent " + intent
+                        + " and enforce permission " + requiredPermission);
+                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+            }
+            if (intent.getComponent() != null) {
+                throw new SecurityException(
+                        "Sticky broadcasts can't target a specific component");
+            }
+            // We use userId directly here, since the "all" target is maintained
+            // as a separate set of sticky broadcasts.
+            if (userId != UserHandle.USER_ALL) {
+                // But first, if this is not a broadcast to all users, then
+                // make sure it doesn't conflict with an existing broadcast to
+                // all users.
+                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(
+                        UserHandle.USER_ALL);
+                if (stickies != null) {
+                    ArrayList<Intent> list = stickies.get(intent.getAction());
+                    if (list != null) {
+                        int N = list.size();
+                        int i;
+                        for (i=0; i<N; i++) {
+                            if (intent.filterEquals(list.get(i))) {
+                                throw new IllegalArgumentException(
+                                        "Sticky broadcast " + intent + " for user "
+                                        + userId + " conflicts with existing global broadcast");
+                            }
+                        }
+                    }
+                }
+            }
+            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
+            if (stickies == null) {
+                stickies = new ArrayMap<String, ArrayList<Intent>>();
+                mStickyBroadcasts.put(userId, stickies);
+            }
+            ArrayList<Intent> list = stickies.get(intent.getAction());
+            if (list == null) {
+                list = new ArrayList<Intent>();
+                stickies.put(intent.getAction(), list);
+            }
+            int N = list.size();
+            int i;
+            for (i=0; i<N; i++) {
+                if (intent.filterEquals(list.get(i))) {
+                    // This sticky already exists, replace it.
+                    list.set(i, new Intent(intent));
+                    break;
+                }
+            }
+            if (i >= N) {
+                list.add(new Intent(intent));
+            }
+        }
+
+        int[] users;
+        if (userId == UserHandle.USER_ALL) {
+            // Caller wants broadcast to go to all started users.
+            users = mStartedUserArray;
+        } else {
+            // Caller wants broadcast to go to one specific user.
+            users = new int[] {userId};
+        }
+
+        // Figure out who all will receive this broadcast.
+        List receivers = null;
+        List<BroadcastFilter> registeredReceivers = null;
+        // Need to resolve the intent to interested receivers...
+        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+                 == 0) {
+            receivers = collectReceiverComponents(intent, resolvedType, users);
+        }
+        if (intent.getComponent() == null) {
+            registeredReceivers = mReceiverResolver.queryIntent(intent,
+                    resolvedType, false, userId);
+        }
+
+        final boolean replacePending =
+                (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
+        
+        if (DEBUG_BROADCAST) Slog.v(TAG, "Enqueing broadcast: " + intent.getAction()
+                + " replacePending=" + replacePending);
+        
+        int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
+        if (!ordered && NR > 0) {
+            // If we are not serializing this broadcast, then send the
+            // registered receivers separately so they don't wait for the
+            // components to be launched.
+            final BroadcastQueue queue = broadcastQueueForIntent(intent);
+            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
+                    callerPackage, callingPid, callingUid, resolvedType, requiredPermission,
+                    appOp, registeredReceivers, resultTo, resultCode, resultData, map,
+                    ordered, sticky, false, userId);
+            if (DEBUG_BROADCAST) Slog.v(
+                    TAG, "Enqueueing parallel broadcast " + r);
+            final boolean replaced = replacePending && queue.replaceParallelBroadcastLocked(r);
+            if (!replaced) {
+                queue.enqueueParallelBroadcastLocked(r);
+                queue.scheduleBroadcastsLocked();
+            }
+            registeredReceivers = null;
+            NR = 0;
+        }
+
+        // Merge into one list.
+        int ir = 0;
+        if (receivers != null) {
+            // A special case for PACKAGE_ADDED: do not allow the package
+            // being added to see this broadcast.  This prevents them from
+            // using this as a back door to get run as soon as they are
+            // installed.  Maybe in the future we want to have a special install
+            // broadcast or such for apps, but we'd like to deliberately make
+            // this decision.
+            String skipPackages[] = null;
+            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
+                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
+                Uri data = intent.getData();
+                if (data != null) {
+                    String pkgName = data.getSchemeSpecificPart();
+                    if (pkgName != null) {
+                        skipPackages = new String[] { pkgName };
+                    }
+                }
+            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
+                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+            }
+            if (skipPackages != null && (skipPackages.length > 0)) {
+                for (String skipPackage : skipPackages) {
+                    if (skipPackage != null) {
+                        int NT = receivers.size();
+                        for (int it=0; it<NT; it++) {
+                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
+                            if (curt.activityInfo.packageName.equals(skipPackage)) {
+                                receivers.remove(it);
+                                it--;
+                                NT--;
+                            }
+                        }
+                    }
+                }
+            }
+
+            int NT = receivers != null ? receivers.size() : 0;
+            int it = 0;
+            ResolveInfo curt = null;
+            BroadcastFilter curr = null;
+            while (it < NT && ir < NR) {
+                if (curt == null) {
+                    curt = (ResolveInfo)receivers.get(it);
+                }
+                if (curr == null) {
+                    curr = registeredReceivers.get(ir);
+                }
+                if (curr.getPriority() >= curt.priority) {
+                    // Insert this broadcast record into the final list.
+                    receivers.add(it, curr);
+                    ir++;
+                    curr = null;
+                    it++;
+                    NT++;
+                } else {
+                    // Skip to the next ResolveInfo in the final list.
+                    it++;
+                    curt = null;
+                }
+            }
+        }
+        while (ir < NR) {
+            if (receivers == null) {
+                receivers = new ArrayList();
+            }
+            receivers.add(registeredReceivers.get(ir));
+            ir++;
+        }
+
+        if ((receivers != null && receivers.size() > 0)
+                || resultTo != null) {
+            BroadcastQueue queue = broadcastQueueForIntent(intent);
+            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
+                    callerPackage, callingPid, callingUid, resolvedType,
+                    requiredPermission, appOp, receivers, resultTo, resultCode,
+                    resultData, map, ordered, sticky, false, userId);
+            if (DEBUG_BROADCAST) Slog.v(
+                    TAG, "Enqueueing ordered broadcast " + r
+                    + ": prev had " + queue.mOrderedBroadcasts.size());
+            if (DEBUG_BROADCAST) {
+                int seq = r.intent.getIntExtra("seq", -1);
+                Slog.i(TAG, "Enqueueing broadcast " + r.intent.getAction() + " seq=" + seq);
+            }
+            boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r); 
+            if (!replaced) {
+                queue.enqueueOrderedBroadcastLocked(r);
+                queue.scheduleBroadcastsLocked();
+            }
+        }
+
+        return ActivityManager.BROADCAST_SUCCESS;
+    }
+
+    final Intent verifyBroadcastLocked(Intent intent) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        int flags = intent.getFlags();
+
+        if (!mProcessesReady) {
+            // if the caller really truly claims to know what they're doing, go
+            // ahead and allow the broadcast without launching any receivers
+            if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) {
+                intent = new Intent(intent);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            } else if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
+                Slog.e(TAG, "Attempt to launch receivers of broadcast intent " + intent
+                        + " before boot completion");
+                throw new IllegalStateException("Cannot broadcast before boot completed");
+            }
+        }
+
+        if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+            throw new IllegalArgumentException(
+                    "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+        }
+
+        return intent;
+    }
+
+    public final int broadcastIntent(IApplicationThread caller,
+            Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle map,
+            String requiredPermission, int appOp, boolean serialized, boolean sticky, int userId) {
+        enforceNotIsolatedCaller("broadcastIntent");
+        synchronized(this) {
+            intent = verifyBroadcastLocked(intent);
+            
+            final ProcessRecord callerApp = getRecordForAppLocked(caller);
+            final int callingPid = Binder.getCallingPid();
+            final int callingUid = Binder.getCallingUid();
+            final long origId = Binder.clearCallingIdentity();
+            int res = broadcastIntentLocked(callerApp,
+                    callerApp != null ? callerApp.info.packageName : null,
+                    intent, resolvedType, resultTo,
+                    resultCode, resultData, map, requiredPermission, appOp, serialized, sticky,
+                    callingPid, callingUid, userId);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    int broadcastIntentInPackage(String packageName, int uid,
+            Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle map,
+            String requiredPermission, boolean serialized, boolean sticky, int userId) {
+        synchronized(this) {
+            intent = verifyBroadcastLocked(intent);
+
+            final long origId = Binder.clearCallingIdentity();
+            int res = broadcastIntentLocked(null, packageName, intent, resolvedType,
+                    resultTo, resultCode, resultData, map, requiredPermission,
+                    AppOpsManager.OP_NONE, serialized, sticky, -1, uid, userId);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
+    public final void unbroadcastIntent(IApplicationThread caller, Intent intent, int userId) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors() == true) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        userId = handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, true, false, "removeStickyBroadcast", null);
+
+        synchronized(this) {
+            if (checkCallingPermission(android.Manifest.permission.BROADCAST_STICKY)
+                    != PackageManager.PERMISSION_GRANTED) {
+                String msg = "Permission Denial: unbroadcastIntent() from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid()
+                        + " requires " + android.Manifest.permission.BROADCAST_STICKY;
+                Slog.w(TAG, msg);
+                throw new SecurityException(msg);
+            }
+            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
+            if (stickies != null) {
+                ArrayList<Intent> list = stickies.get(intent.getAction());
+                if (list != null) {
+                    int N = list.size();
+                    int i;
+                    for (i=0; i<N; i++) {
+                        if (intent.filterEquals(list.get(i))) {
+                            list.remove(i);
+                            break;
+                        }
+                    }
+                    if (list.size() <= 0) {
+                        stickies.remove(intent.getAction());
+                    }
+                }
+                if (stickies.size() <= 0) {
+                    mStickyBroadcasts.remove(userId);
+                }
+            }
+        }
+    }
+
+    private final boolean finishReceiverLocked(IBinder receiver, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort) {
+        final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver);
+        if (r == null) {
+            Slog.w(TAG, "finishReceiver called but not found on queue");
+            return false;
+        }
+
+        return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, false);
+    }
+
+    void backgroundServicesFinishedLocked(int userId) {
+        for (BroadcastQueue queue : mBroadcastQueues) {
+            queue.backgroundServicesFinishedLocked(userId);
+        }
+    }
+
+    public void finishReceiver(IBinder who, int resultCode, String resultData,
+            Bundle resultExtras, boolean resultAbort) {
+        if (DEBUG_BROADCAST) Slog.v(TAG, "Finish receiver: " + who);
+
+        // Refuse possible leaked file descriptors
+        if (resultExtras != null && resultExtras.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            boolean doNext = false;
+            BroadcastRecord r;
+
+            synchronized(this) {
+                r = broadcastRecordForReceiverLocked(who);
+                if (r != null) {
+                    doNext = r.queue.finishReceiverLocked(r, resultCode,
+                        resultData, resultExtras, resultAbort, true);
+                }
+            }
+
+            if (doNext) {
+                r.queue.processNextBroadcast(false);
+            }
+            trimApplications();
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+    
+    // =========================================================
+    // INSTRUMENTATION
+    // =========================================================
+
+    public boolean startInstrumentation(ComponentName className,
+            String profileFile, int flags, Bundle arguments,
+            IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection,
+            int userId) {
+        enforceNotIsolatedCaller("startInstrumentation");
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, true, "startInstrumentation", null);
+        // Refuse possible leaked file descriptors
+        if (arguments != null && arguments.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Bundle");
+        }
+
+        synchronized(this) {
+            InstrumentationInfo ii = null;
+            ApplicationInfo ai = null;
+            try {
+                ii = mContext.getPackageManager().getInstrumentationInfo(
+                    className, STOCK_PM_FLAGS);
+                ai = AppGlobals.getPackageManager().getApplicationInfo(
+                        ii.targetPackage, STOCK_PM_FLAGS, userId);
+            } catch (PackageManager.NameNotFoundException e) {
+            } catch (RemoteException e) {
+            }
+            if (ii == null) {
+                reportStartInstrumentationFailure(watcher, className,
+                        "Unable to find instrumentation info for: " + className);
+                return false;
+            }
+            if (ai == null) {
+                reportStartInstrumentationFailure(watcher, className,
+                        "Unable to find instrumentation target package: " + ii.targetPackage);
+                return false;
+            }
+
+            int match = mContext.getPackageManager().checkSignatures(
+                    ii.targetPackage, ii.packageName);
+            if (match < 0 && match != PackageManager.SIGNATURE_FIRST_NOT_SIGNED) {
+                String msg = "Permission Denial: starting instrumentation "
+                        + className + " from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingPid()
+                        + " not allowed because package " + ii.packageName
+                        + " does not have a signature matching the target "
+                        + ii.targetPackage;
+                reportStartInstrumentationFailure(watcher, className, msg);
+                throw new SecurityException(msg);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            // Instrumentation can kill and relaunch even persistent processes
+            forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId,
+                    "start instr");
+            ProcessRecord app = addAppLocked(ai, false);
+            app.instrumentationClass = className;
+            app.instrumentationInfo = ai;
+            app.instrumentationProfileFile = profileFile;
+            app.instrumentationArguments = arguments;
+            app.instrumentationWatcher = watcher;
+            app.instrumentationUiAutomationConnection = uiAutomationConnection;
+            app.instrumentationResultClass = className;
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return true;
+    }
+    
+    /**
+     * Report errors that occur while attempting to start Instrumentation.  Always writes the 
+     * error to the logs, but if somebody is watching, send the report there too.  This enables
+     * the "am" command to report errors with more information.
+     * 
+     * @param watcher The IInstrumentationWatcher.  Null if there isn't one.
+     * @param cn The component name of the instrumentation.
+     * @param report The error report.
+     */
+    private void reportStartInstrumentationFailure(IInstrumentationWatcher watcher, 
+            ComponentName cn, String report) {
+        Slog.w(TAG, report);
+        try {
+            if (watcher != null) {
+                Bundle results = new Bundle();
+                results.putString(Instrumentation.REPORT_KEY_IDENTIFIER, "ActivityManagerService");
+                results.putString("Error", report);
+                watcher.instrumentationStatus(cn, -1, results);
+            }
+        } catch (RemoteException e) {
+            Slog.w(TAG, e);
+        }
+    }
+
+    void finishInstrumentationLocked(ProcessRecord app, int resultCode, Bundle results) {
+        if (app.instrumentationWatcher != null) {
+            try {
+                // NOTE:  IInstrumentationWatcher *must* be oneway here
+                app.instrumentationWatcher.instrumentationFinished(
+                    app.instrumentationClass,
+                    resultCode,
+                    results);
+            } catch (RemoteException e) {
+            }
+        }
+        if (app.instrumentationUiAutomationConnection != null) {
+            try {
+                app.instrumentationUiAutomationConnection.shutdown();
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+            // Only a UiAutomation can set this flag and now that
+            // it is finished we make sure it is reset to its default.
+            mUserIsMonkey = false;
+        }
+        app.instrumentationWatcher = null;
+        app.instrumentationUiAutomationConnection = null;
+        app.instrumentationClass = null;
+        app.instrumentationInfo = null;
+        app.instrumentationProfileFile = null;
+        app.instrumentationArguments = null;
+
+        forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId,
+                "finished inst");
+    }
+
+    public void finishInstrumentation(IApplicationThread target,
+            int resultCode, Bundle results) {
+        int userId = UserHandle.getCallingUserId();
+        // Refuse possible leaked file descriptors
+        if (results != null && results.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+
+        synchronized(this) {
+            ProcessRecord app = getRecordForAppLocked(target);
+            if (app == null) {
+                Slog.w(TAG, "finishInstrumentation: no app for " + target);
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            finishInstrumentationLocked(app, resultCode, results);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    // =========================================================
+    // CONFIGURATION
+    // =========================================================
+    
+    public ConfigurationInfo getDeviceConfigurationInfo() {
+        ConfigurationInfo config = new ConfigurationInfo();
+        synchronized (this) {
+            config.reqTouchScreen = mConfiguration.touchscreen;
+            config.reqKeyboardType = mConfiguration.keyboard;
+            config.reqNavigation = mConfiguration.navigation;
+            if (mConfiguration.navigation == Configuration.NAVIGATION_DPAD
+                    || mConfiguration.navigation == Configuration.NAVIGATION_TRACKBALL) {
+                config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_FIVE_WAY_NAV;
+            }
+            if (mConfiguration.keyboard != Configuration.KEYBOARD_UNDEFINED
+                    && mConfiguration.keyboard != Configuration.KEYBOARD_NOKEYS) {
+                config.reqInputFeatures |= ConfigurationInfo.INPUT_FEATURE_HARD_KEYBOARD;
+            }
+            config.reqGlEsVersion = GL_ES_VERSION;
+        }
+        return config;
+    }
+
+    ActivityStack getFocusedStack() {
+        return mStackSupervisor.getFocusedStack();
+    }
+
+    public Configuration getConfiguration() {
+        Configuration ci;
+        synchronized(this) {
+            ci = new Configuration(mConfiguration);
+        }
+        return ci;
+    }
+
+    public void updatePersistentConfiguration(Configuration values) {
+        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+                "updateConfiguration()");
+        enforceCallingPermission(android.Manifest.permission.WRITE_SETTINGS,
+                "updateConfiguration()");
+        if (values == null) {
+            throw new NullPointerException("Configuration must not be null");
+        }
+
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            updateConfigurationLocked(values, null, true, false);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public void updateConfiguration(Configuration values) {
+        enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+                "updateConfiguration()");
+
+        synchronized(this) {
+            if (values == null && mWindowManager != null) {
+                // sentinel: fetch the current configuration from the window manager
+                values = mWindowManager.computeNewConfiguration();
+            }
+
+            if (mWindowManager != null) {
+                mProcessList.applyDisplaySize(mWindowManager);
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            if (values != null) {
+                Settings.System.clearConfiguration(values);
+            }
+            updateConfigurationLocked(values, null, false, false);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * Do either or both things: (1) change the current configuration, and (2)
+     * make sure the given activity is running with the (now) current
+     * configuration.  Returns true if the activity has been left running, or
+     * false if <var>starting</var> is being destroyed to match the new
+     * configuration.
+     * @param persistent TODO
+     */
+    boolean updateConfigurationLocked(Configuration values,
+            ActivityRecord starting, boolean persistent, boolean initLocale) {
+        int changes = 0;
+
+        if (values != null) {
+            Configuration newConfig = new Configuration(mConfiguration);
+            changes = newConfig.updateFrom(values);
+            if (changes != 0) {
+                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
+                    Slog.i(TAG, "Updating configuration to: " + values);
+                }
+                
+                EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes);
+
+                if (values.locale != null && !initLocale) {
+                    saveLocaleLocked(values.locale, 
+                                     !values.locale.equals(mConfiguration.locale),
+                                     values.userSetLocale);
+                }
+
+                mConfigurationSeq++;
+                if (mConfigurationSeq <= 0) {
+                    mConfigurationSeq = 1;
+                }
+                newConfig.seq = mConfigurationSeq;
+                mConfiguration = newConfig;
+                Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig);
+
+                final Configuration configCopy = new Configuration(mConfiguration);
+                
+                // TODO: If our config changes, should we auto dismiss any currently
+                // showing dialogs?
+                mShowDialogs = shouldShowDialogs(newConfig);
+
+                AttributeCache ac = AttributeCache.instance();
+                if (ac != null) {
+                    ac.updateConfiguration(configCopy);
+                }
+
+                // Make sure all resources in our process are updated
+                // right now, so that anyone who is going to retrieve
+                // resource values after we return will be sure to get
+                // the new ones.  This is especially important during
+                // boot, where the first config change needs to guarantee
+                // all resources have that config before following boot
+                // code is executed.
+                mSystemThread.applyConfigurationToResources(configCopy);
+
+                if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
+                    Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG);
+                    msg.obj = new Configuration(configCopy);
+                    mHandler.sendMessage(msg);
+                }
+        
+                for (int i=mLruProcesses.size()-1; i>=0; i--) {
+                    ProcessRecord app = mLruProcesses.get(i);
+                    try {
+                        if (app.thread != null) {
+                            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+                                    + app.processName + " new config " + mConfiguration);
+                            app.thread.scheduleConfigurationChanged(configCopy);
+                        }
+                    } catch (Exception e) {
+                    }
+                }
+                Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_REPLACE_PENDING
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                broadcastIntentLocked(null, null, intent, null, null, 0, null, null,
+                        null, AppOpsManager.OP_NONE, false, false, MY_PID,
+                        Process.SYSTEM_UID, UserHandle.USER_ALL);
+                if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {
+                    intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                    broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+                }
+            }
+        }
+
+        boolean kept = true;
+        final ActivityStack mainStack = mStackSupervisor.getFocusedStack();
+        // mainStack is null during startup.
+        if (mainStack != null) {
+            if (changes != 0 && starting == null) {
+                // If the configuration changed, and the caller is not already
+                // in the process of starting an activity, then find the top
+                // activity to check if its configuration needs to change.
+                starting = mainStack.topRunningActivityLocked(null);
+            }
+
+            if (starting != null) {
+                kept = mainStack.ensureActivityConfigurationLocked(starting, changes);
+                // And we need to make sure at this point that all other activities
+                // are made visible with the correct configuration.
+                mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes);
+            }
+        }
+
+        if (values != null && mWindowManager != null) {
+            mWindowManager.setNewConfiguration(mConfiguration);
+        }
+
+        return kept;
+    }
+
+    /**
+     * Decide based on the configuration whether we should shouw the ANR,
+     * crash, etc dialogs.  The idea is that if there is no affordnace to
+     * press the on-screen buttons, we shouldn't show the dialog.
+     *
+     * A thought: SystemUI might also want to get told about this, the Power
+     * dialog / global actions also might want different behaviors.
+     */
+    private static final boolean shouldShowDialogs(Configuration config) {
+        return !(config.keyboard == Configuration.KEYBOARD_NOKEYS
+                && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH);
+    }
+
+    /**
+     * Save the locale.  You must be inside a synchronized (this) block.
+     */
+    private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) {
+        if(isDiff) {
+            SystemProperties.set("user.language", l.getLanguage());
+            SystemProperties.set("user.region", l.getCountry());
+        } 
+
+        if(isPersist) {
+            SystemProperties.set("persist.sys.language", l.getLanguage());
+            SystemProperties.set("persist.sys.country", l.getCountry());
+            SystemProperties.set("persist.sys.localevar", l.getVariant());
+        }
+    }
+
+    @Override
+    public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) {
+        ActivityRecord srec = ActivityRecord.forToken(token);
+        return srec != null && srec.task.affinity != null &&
+                srec.task.affinity.equals(destAffinity);
+    }
+
+    public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+            Intent resultData) {
+
+        synchronized (this) {
+            final ActivityStack stack = ActivityRecord.getStackLocked(token);
+            if (stack != null) {
+                return stack.navigateUpToLocked(token, destIntent, resultCode, resultData);
+            }
+            return false;
+        }
+    }
+
+    public int getLaunchedFromUid(IBinder activityToken) {
+        ActivityRecord srec = ActivityRecord.forToken(activityToken);
+        if (srec == null) {
+            return -1;
+        }
+        return srec.launchedFromUid;
+    }
+
+    public String getLaunchedFromPackage(IBinder activityToken) {
+        ActivityRecord srec = ActivityRecord.forToken(activityToken);
+        if (srec == null) {
+            return null;
+        }
+        return srec.launchedFromPackage;
+    }
+
+    // =========================================================
+    // LIFETIME MANAGEMENT
+    // =========================================================
+
+    // Returns which broadcast queue the app is the current [or imminent] receiver
+    // on, or 'null' if the app is not an active broadcast recipient.
+    private BroadcastQueue isReceivingBroadcast(ProcessRecord app) {
+        BroadcastRecord r = app.curReceiver;
+        if (r != null) {
+            return r.queue;
+        }
+
+        // It's not the current receiver, but it might be starting up to become one
+        synchronized (this) {
+            for (BroadcastQueue queue : mBroadcastQueues) {
+                r = queue.mPendingBroadcast;
+                if (r != null && r.curApp == app) {
+                    // found it; report which queue it's in
+                    return queue;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
+            boolean doingAll, long now) {
+        if (mAdjSeq == app.adjSeq) {
+            // This adjustment has already been computed.
+            return app.curRawAdj;
+        }
+
+        if (app.thread == null) {
+            app.adjSeq = mAdjSeq;
+            app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+            app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+            return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ);
+        }
+
+        app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
+        app.adjSource = null;
+        app.adjTarget = null;
+        app.empty = false;
+        app.cached = false;
+
+        final int activitiesSize = app.activities.size();
+
+        if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+            // The max adjustment doesn't allow this app to be anything
+            // below foreground, so it is not worth doing work for it.
+            app.adjType = "fixed";
+            app.adjSeq = mAdjSeq;
+            app.curRawAdj = app.maxAdj;
+            app.foregroundActivities = false;
+            app.keeping = true;
+            app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
+            app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;
+            // System process can do UI, and when they do we want to have
+            // them trim their memory after the user leaves the UI.  To
+            // facilitate this, here we need to determine whether or not it
+            // is currently showing UI.
+            app.systemNoUi = true;
+            if (app == TOP_APP) {
+                app.systemNoUi = false;
+            } else if (activitiesSize > 0) {
+                for (int j = 0; j < activitiesSize; j++) {
+                    final ActivityRecord r = app.activities.get(j);
+                    if (r.visible) {
+                        app.systemNoUi = false;
+                    }
+                }
+            }
+            if (!app.systemNoUi) {
+                app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI;
+            }
+            return (app.curAdj=app.maxAdj);
+        }
+
+        app.keeping = false;
+        app.systemNoUi = false;
+
+        // Determine the importance of the process, starting with most
+        // important to least, and assign an appropriate OOM adjustment.
+        int adj;
+        int schedGroup;
+        int procState;
+        boolean foregroundActivities = false;
+        boolean interesting = false;
+        BroadcastQueue queue;
+        if (app == TOP_APP) {
+            // The last app on the list is the foreground app.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
+            app.adjType = "top-activity";
+            foregroundActivities = true;
+            interesting = true;
+            procState = ActivityManager.PROCESS_STATE_TOP;
+        } else if (app.instrumentationClass != null) {
+            // Don't want to kill running instrumentation.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = Process.THREAD_GROUP_DEFAULT;
+            app.adjType = "instrumentation";
+            interesting = true;
+            procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+        } else if ((queue = isReceivingBroadcast(app)) != null) {
+            // An app that is currently receiving a broadcast also
+            // counts as being in the foreground for OOM killer purposes.
+            // It's placed in a sched group based on the nature of the
+            // broadcast as reflected by which queue it's active in.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = (queue == mFgBroadcastQueue)
+                    ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE;
+            app.adjType = "broadcast";
+            procState = ActivityManager.PROCESS_STATE_RECEIVER;
+        } else if (app.executingServices.size() > 0) {
+            // An app that is currently executing a service callback also
+            // counts as being in the foreground.
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = app.execServicesFg ?
+                    Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE;
+            app.adjType = "exec-service";
+            procState = ActivityManager.PROCESS_STATE_SERVICE;
+            //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+        } else {
+            // As far as we know the process is empty.  We may change our mind later.
+            schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+            // At this point we don't actually know the adjustment.  Use the cached adj
+            // value that the caller wants us to.
+            adj = cachedAdj;
+            procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+            app.cached = true;
+            app.empty = true;
+            app.adjType = "cch-empty";
+        }
+
+        // Examine all activities if not already foreground.
+        if (!foregroundActivities && activitiesSize > 0) {
+            for (int j = 0; j < activitiesSize; j++) {
+                final ActivityRecord r = app.activities.get(j);
+                if (r.app != app) {
+                    Slog.w(TAG, "Wtf, activity " + r + " in proc activity list not using proc "
+                            + app + "?!?");
+                    continue;
+                }
+                if (r.visible) {
+                    // App has a visible activity; only upgrade adjustment.
+                    if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                        adj = ProcessList.VISIBLE_APP_ADJ;
+                        app.adjType = "visible";
+                    }
+                    if (procState > ActivityManager.PROCESS_STATE_TOP) {
+                        procState = ActivityManager.PROCESS_STATE_TOP;
+                    }
+                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                    app.cached = false;
+                    app.empty = false;
+                    foregroundActivities = true;
+                    break;
+                } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {
+                    if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                        app.adjType = "pausing";
+                    }
+                    if (procState > ActivityManager.PROCESS_STATE_TOP) {
+                        procState = ActivityManager.PROCESS_STATE_TOP;
+                    }
+                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                    app.cached = false;
+                    app.empty = false;
+                    foregroundActivities = true;
+                } else if (r.state == ActivityState.STOPPING) {
+                    if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                        app.adjType = "stopping";
+                    }
+                    // For the process state, we will at this point consider the
+                    // process to be cached.  It will be cached either as an activity
+                    // or empty depending on whether the activity is finishing.  We do
+                    // this so that we can treat the process as cached for purposes of
+                    // memory trimming (determing current memory level, trim command to
+                    // send to process) since there can be an arbitrary number of stopping
+                    // processes and they should soon all go into the cached state.
+                    if (!r.finishing) {
+                        if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
+                            procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+                        }
+                    }
+                    app.cached = false;
+                    app.empty = false;
+                    foregroundActivities = true;
+                } else {
+                    if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
+                        procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+                        app.adjType = "cch-act";
+                    }
+                }
+            }
+        }
+
+        if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+            if (app.foregroundServices) {
+                // The user is aware of this app, so make it visible.
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                app.cached = false;
+                app.adjType = "fg-service";
+                schedGroup = Process.THREAD_GROUP_DEFAULT;
+            } else if (app.forcingToForeground != null) {
+                // The user is aware of this app, so make it visible.
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                app.cached = false;
+                app.adjType = "force-fg";
+                app.adjSource = app.forcingToForeground;
+                schedGroup = Process.THREAD_GROUP_DEFAULT;
+            }
+        }
+
+        if (app.foregroundServices) {
+            interesting = true;
+        }
+
+        if (app == mHeavyWeightProcess) {
+            if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                // We don't want to kill the current heavy-weight process.
+                adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
+                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+                app.cached = false;
+                app.adjType = "heavy";
+            }
+            if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+            }
+        }
+
+        if (app == mHomeProcess) {
+            if (adj > ProcessList.HOME_APP_ADJ) {
+                // This process is hosting what we currently consider to be the
+                // home app, so we don't want to let it go into the background.
+                adj = ProcessList.HOME_APP_ADJ;
+                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+                app.cached = false;
+                app.adjType = "home";
+            }
+            if (procState > ActivityManager.PROCESS_STATE_HOME) {
+                procState = ActivityManager.PROCESS_STATE_HOME;
+            }
+        }
+
+        if (app == mPreviousProcess && app.activities.size() > 0) {
+            if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+                // This was the previous process that showed UI to the user.
+                // We want to try to keep it around more aggressively, to give
+                // a good experience around switching between two apps.
+                adj = ProcessList.PREVIOUS_APP_ADJ;
+                schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
+                app.cached = false;
+                app.adjType = "previous";
+            }
+            if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {
+                procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+            }
+        }
+
+        if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
+                + " reason=" + app.adjType);
+
+        // By default, we use the computed adjustment.  It may be changed if
+        // there are applications dependent on our services or providers, but
+        // this gives us a baseline and makes sure we don't get into an
+        // infinite recursion.
+        app.adjSeq = mAdjSeq;
+        app.curRawAdj = adj;
+        app.hasStartedServices = false;
+
+        if (mBackupTarget != null && app == mBackupTarget.app) {
+            // If possible we want to avoid killing apps while they're being backed up
+            if (adj > ProcessList.BACKUP_APP_ADJ) {
+                if (DEBUG_BACKUP) Slog.v(TAG, "oom BACKUP_APP_ADJ for " + app);
+                adj = ProcessList.BACKUP_APP_ADJ;
+                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+                }
+                app.adjType = "backup";
+                app.cached = false;
+            }
+            if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
+                procState = ActivityManager.PROCESS_STATE_BACKUP;
+            }
+        }
+
+        boolean mayBeTop = false;
+
+        for (int is = app.services.size()-1;
+                is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                        || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
+                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                is--) {
+            ServiceRecord s = app.services.valueAt(is);
+            if (s.startRequested) {
+                app.hasStartedServices = true;
+                if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
+                    procState = ActivityManager.PROCESS_STATE_SERVICE;
+                }
+                if (app.hasShownUi && app != mHomeProcess) {
+                    // If this process has shown some UI, let it immediately
+                    // go to the LRU list because it may be pretty heavy with
+                    // UI stuff.  We'll tag it with a label just to help
+                    // debug and understand what is going on.
+                    if (adj > ProcessList.SERVICE_ADJ) {
+                        app.adjType = "cch-started-ui-services";
+                    }
+                } else {
+                    if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) {
+                        // This service has seen some activity within
+                        // recent memory, so we will keep its process ahead
+                        // of the background processes.
+                        if (adj > ProcessList.SERVICE_ADJ) {
+                            adj = ProcessList.SERVICE_ADJ;
+                            app.adjType = "started-services";
+                            app.cached = false;
+                        }
+                    }
+                    // If we have let the service slide into the background
+                    // state, still have some text describing what it is doing
+                    // even though the service no longer has an impact.
+                    if (adj > ProcessList.SERVICE_ADJ) {
+                        app.adjType = "cch-started-services";
+                    }
+                }
+                // Don't kill this process because it is doing work; it
+                // has said it is doing work.
+                app.keeping = true;
+            }
+            for (int conni = s.connections.size()-1;
+                    conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                            || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
+                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                    conni--) {
+                ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
+                for (int i = 0;
+                        i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
+                                || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
+                                || procState > ActivityManager.PROCESS_STATE_TOP);
+                        i++) {
+                    // XXX should compute this based on the max of
+                    // all connected clients.
+                    ConnectionRecord cr = clist.get(i);
+                    if (cr.binding.client == app) {
+                        // Binding to ourself is not interesting.
+                        continue;
+                    }
+                    if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
+                        ProcessRecord client = cr.binding.client;
+                        int clientAdj = computeOomAdjLocked(client, cachedAdj,
+                                TOP_APP, doingAll, now);
+                        int clientProcState = client.curProcState;
+                        if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
+                            // If the other app is cached for any reason, for purposes here
+                            // we are going to consider it empty.  The specific cached state
+                            // doesn't propagate except under certain conditions.
+                            clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                        }
+                        String adjType = null;
+                        if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+                            // Not doing bind OOM management, so treat
+                            // this guy more like a started service.
+                            if (app.hasShownUi && app != mHomeProcess) {
+                                // If this process has shown some UI, let it immediately
+                                // go to the LRU list because it may be pretty heavy with
+                                // UI stuff.  We'll tag it with a label just to help
+                                // debug and understand what is going on.
+                                if (adj > clientAdj) {
+                                    adjType = "cch-bound-ui-services";
+                                }
+                                app.cached = false;
+                                clientAdj = adj;
+                                clientProcState = procState;
+                            } else {
+                                if (now >= (s.lastActivity
+                                        + ActiveServices.MAX_SERVICE_INACTIVITY)) {
+                                    // This service has not seen activity within
+                                    // recent memory, so allow it to drop to the
+                                    // LRU list if there is no other reason to keep
+                                    // it around.  We'll also tag it with a label just
+                                    // to help debug and undertand what is going on.
+                                    if (adj > clientAdj) {
+                                        adjType = "cch-bound-services";
+                                    }
+                                    clientAdj = adj;
+                                }
+                            }
+                        }
+                        if (adj > clientAdj) {
+                            // If this process has recently shown UI, and
+                            // the process that is binding to it is less
+                            // important than being visible, then we don't
+                            // care about the binding as much as we care
+                            // about letting this process get into the LRU
+                            // list to be killed and restarted if needed for
+                            // memory.
+                            if (app.hasShownUi && app != mHomeProcess
+                                    && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                adjType = "cch-bound-ui-services";
+                            } else {
+                                if ((cr.flags&(Context.BIND_ABOVE_CLIENT
+                                        |Context.BIND_IMPORTANT)) != 0) {
+                                    adj = clientAdj;
+                                } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
+                                        && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
+                                        && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                                    adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+                                } else if (clientAdj > ProcessList.VISIBLE_APP_ADJ) {
+                                    adj = clientAdj;
+                                } else {
+                                    if (adj > ProcessList.VISIBLE_APP_ADJ) {
+                                        adj = ProcessList.VISIBLE_APP_ADJ;
+                                    }
+                                }
+                                if (!client.cached) {
+                                    app.cached = false;
+                                }
+                                if (client.keeping) {
+                                    app.keeping = true;
+                                }
+                                adjType = "service";
+                            }
+                        }
+                        if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+                            if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                                schedGroup = Process.THREAD_GROUP_DEFAULT;
+                            }
+                            if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                                if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                                    // Special handling of clients who are in the top state.
+                                    // We *may* want to consider this process to be in the
+                                    // top state as well, but only if there is not another
+                                    // reason for it to be running.  Being on the top is a
+                                    // special state, meaning you are specifically running
+                                    // for the current top app.  If the process is already
+                                    // running in the background for some other reason, it
+                                    // is more important to continue considering it to be
+                                    // in the background state.
+                                    mayBeTop = true;
+                                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                                } else {
+                                    // Special handling for above-top states (persistent
+                                    // processes).  These should not bring the current process
+                                    // into the top state, since they are not on top.  Instead
+                                    // give them the best state after that.
+                                    clientProcState =
+                                            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                                }
+                            }
+                        } else {
+                            if (clientProcState <
+                                    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+                                clientProcState =
+                                        ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+                            }
+                        }
+                        if (procState > clientProcState) {
+                            procState = clientProcState;
+                        }
+                        if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                                && (cr.flags&Context.BIND_SHOWING_UI) != 0) {
+                            app.pendingUiClean = true;
+                        }
+                        if (adjType != null) {
+                            app.adjType = adjType;
+                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE;
+                            app.adjSource = cr.binding.client;
+                            app.adjSourceOom = clientAdj;
+                            app.adjTarget = s.name;
+                        }
+                    }
+                    final ActivityRecord a = cr.activity;
+                    if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+                        if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ &&
+                                (a.visible || a.state == ActivityState.RESUMED
+                                 || a.state == ActivityState.PAUSING)) {
+                            adj = ProcessList.FOREGROUND_APP_ADJ;
+                            if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+                                schedGroup = Process.THREAD_GROUP_DEFAULT;
+                            }
+                            app.cached = false;
+                            app.adjType = "service";
+                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                                    .REASON_SERVICE_IN_USE;
+                            app.adjSource = a;
+                            app.adjSourceOom = adj;
+                            app.adjTarget = s.name;
+                        }
+                    }
+                }
+            }
+        }
+
+        for (int provi = app.pubProviders.size()-1;
+                provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                        || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
+                        || procState > ActivityManager.PROCESS_STATE_TOP);
+                provi--) {
+            ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
+            for (int i = cpr.connections.size()-1;
+                    i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                            || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE
+                            || procState > ActivityManager.PROCESS_STATE_TOP);
+                    i--) {
+                ContentProviderConnection conn = cpr.connections.get(i);
+                ProcessRecord client = conn.client;
+                if (client == app) {
+                    // Being our own client is not interesting.
+                    continue;
+                }
+                int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
+                int clientProcState = client.curProcState;
+                if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {
+                    // If the other app is cached for any reason, for purposes here
+                    // we are going to consider it empty.
+                    clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                }
+                if (adj > clientAdj) {
+                    if (app.hasShownUi && app != mHomeProcess
+                            && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                        app.adjType = "cch-ui-provider";
+                    } else {
+                        adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
+                                ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+                        app.adjType = "provider";
+                    }
+                    app.cached &= client.cached;
+                    app.keeping |= client.keeping;
+                    app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                            .REASON_PROVIDER_IN_USE;
+                    app.adjSource = client;
+                    app.adjSourceOom = clientAdj;
+                    app.adjTarget = cpr.name;
+                }
+                if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+                    if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+                        // Special handling of clients who are in the top state.
+                        // We *may* want to consider this process to be in the
+                        // top state as well, but only if there is not another
+                        // reason for it to be running.  Being on the top is a
+                        // special state, meaning you are specifically running
+                        // for the current top app.  If the process is already
+                        // running in the background for some other reason, it
+                        // is more important to continue considering it to be
+                        // in the background state.
+                        mayBeTop = true;
+                        clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+                    } else {
+                        // Special handling for above-top states (persistent
+                        // processes).  These should not bring the current process
+                        // into the top state, since they are not on top.  Instead
+                        // give them the best state after that.
+                        clientProcState =
+                                ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    }
+                }
+                if (procState > clientProcState) {
+                    procState = clientProcState;
+                }
+                if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                }
+            }
+            // If the provider has external (non-framework) process
+            // dependencies, ensure that its adjustment is at least
+            // FOREGROUND_APP_ADJ.
+            if (cpr.hasExternalProcessHandles()) {
+                if (adj > ProcessList.FOREGROUND_APP_ADJ) {
+                    adj = ProcessList.FOREGROUND_APP_ADJ;
+                    schedGroup = Process.THREAD_GROUP_DEFAULT;
+                    app.cached = false;
+                    app.keeping = true;
+                    app.adjType = "provider";
+                    app.adjTarget = cpr.name;
+                }
+                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                }
+            }
+        }
+
+        if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
+            // A client of one of our services or providers is in the top state.  We
+            // *may* want to be in the top state, but not if we are already running in
+            // the background for some other reason.  For the decision here, we are going
+            // to pick out a few specific states that we want to remain in when a client
+            // is top (states that tend to be longer-term) and otherwise allow it to go
+            // to the top state.
+            switch (procState) {
+                case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+                case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+                case ActivityManager.PROCESS_STATE_SERVICE:
+                    // These all are longer-term states, so pull them up to the top
+                    // of the background states, but not all the way to the top state.
+                    procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+                    break;
+                default:
+                    // Otherwise, top is a better choice, so take it.
+                    procState = ActivityManager.PROCESS_STATE_TOP;
+                    break;
+            }
+        }
+
+        if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY && app.hasClientActivities) {
+            // This is a cached process, but with client activities.  Mark it so.
+            procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+            app.adjType = "cch-client-act";
+        }
+
+        if (adj == ProcessList.SERVICE_ADJ) {
+            if (doingAll) {
+                app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
+                mNewNumServiceProcs++;
+                //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
+                if (!app.serviceb) {
+                    // This service isn't far enough down on the LRU list to
+                    // normally be a B service, but if we are low on RAM and it
+                    // is large we want to force it down since we would prefer to
+                    // keep launcher over it.
+                    if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
+                            && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
+                        app.serviceHighRam = true;
+                        app.serviceb = true;
+                        //Slog.i(TAG, "ADJ " + app + " high ram!");
+                    } else {
+                        mNewNumAServiceProcs++;
+                        //Slog.i(TAG, "ADJ " + app + " not high ram!");
+                    }
+                } else {
+                    app.serviceHighRam = false;
+                }
+            }
+            if (app.serviceb) {
+                adj = ProcessList.SERVICE_B_ADJ;
+            }
+        }
+
+        app.curRawAdj = adj;
+        
+        //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
+        //      " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
+        if (adj > app.maxAdj) {
+            adj = app.maxAdj;
+            if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                schedGroup = Process.THREAD_GROUP_DEFAULT;
+            }
+        }
+        if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
+            app.keeping = true;
+        }
+
+        // Do final modification to adj.  Everything we do between here and applying
+        // the final setAdj must be done in this function, because we will also use
+        // it when computing the final cached adj later.  Note that we don't need to
+        // worry about this for max adj above, since max adj will always be used to
+        // keep it out of the cached vaues.
+        adj = app.modifyRawOomAdj(adj);
+
+        app.curProcState = procState;
+
+        int importance = app.memImportance;
+        if (importance == 0 || adj != app.curAdj || schedGroup != app.curSchedGroup) {
+            app.curAdj = adj;
+            app.curSchedGroup = schedGroup;
+            if (!interesting) {
+                // For this reporting, if there is not something explicitly
+                // interesting in this process then we will push it to the
+                // background importance.
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+            } else if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+            } else if (adj >= ProcessList.SERVICE_B_ADJ) {
+                importance =  ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+            } else if (adj >= ProcessList.HOME_APP_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
+            } else if (adj >= ProcessList.SERVICE_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
+            } else if (adj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE;
+            } else if (adj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE;
+            } else if (adj >= ProcessList.VISIBLE_APP_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+            } else if (adj >= ProcessList.FOREGROUND_APP_ADJ) {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+            } else {
+                importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERSISTENT;
+            }
+        }
+
+        int changes = importance != app.memImportance ? ProcessChangeItem.CHANGE_IMPORTANCE : 0;
+        if (foregroundActivities != app.foregroundActivities) {
+            changes |= ProcessChangeItem.CHANGE_ACTIVITIES;
+        }
+        if (changes != 0) {
+            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Changes in " + app + ": " + changes);
+            app.memImportance = importance;
+            app.foregroundActivities = foregroundActivities;
+            int i = mPendingProcessChanges.size()-1;
+            ProcessChangeItem item = null;
+            while (i >= 0) {
+                item = mPendingProcessChanges.get(i);
+                if (item.pid == app.pid) {
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Re-using existing item: " + item);
+                    break;
+                }
+                i--;
+            }
+            if (i < 0) {
+                // No existing item in pending changes; need a new one.
+                final int NA = mAvailProcessChanges.size();
+                if (NA > 0) {
+                    item = mAvailProcessChanges.remove(NA-1);
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Retreiving available item: " + item);
+                } else {
+                    item = new ProcessChangeItem();
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Allocating new item: " + item);
+                }
+                item.changes = 0;
+                item.pid = app.pid;
+                item.uid = app.info.uid;
+                if (mPendingProcessChanges.size() == 0) {
+                    if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG,
+                            "*** Enqueueing dispatch processes changed!");
+                    mHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED).sendToTarget();
+                }
+                mPendingProcessChanges.add(item);
+            }
+            item.changes |= changes;
+            item.importance = importance;
+            item.foregroundActivities = foregroundActivities;
+            if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "Item "
+                    + Integer.toHexString(System.identityHashCode(item))
+                    + " " + app.toShortString() + ": changes=" + item.changes
+                    + " importance=" + item.importance
+                    + " foreground=" + item.foregroundActivities
+                    + " type=" + app.adjType + " source=" + app.adjSource
+                    + " target=" + app.adjTarget);
+        }
+
+        return app.curRawAdj;
+    }
+
+    /**
+     * Schedule PSS collection of a process.
+     */
+    void requestPssLocked(ProcessRecord proc, int procState) {
+        if (mPendingPssProcesses.contains(proc)) {
+            return;
+        }
+        if (mPendingPssProcesses.size() == 0) {
+            mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+        }
+        if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of: " + proc);
+        proc.pssProcState = procState;
+        mPendingPssProcesses.add(proc);
+    }
+
+    /**
+     * Schedule PSS collection of all processes.
+     */
+    void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) {
+        if (!always) {
+            if (now < (mLastFullPssTime +
+                    (memLowered ? FULL_PSS_LOWERED_INTERVAL : FULL_PSS_MIN_INTERVAL))) {
+                return;
+            }
+        }
+        if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs!  memLowered=" + memLowered);
+        mLastFullPssTime = now;
+        mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
+        mPendingPssProcesses.clear();
+        for (int i=mLruProcesses.size()-1; i>=0; i--) {
+            ProcessRecord app = mLruProcesses.get(i);
+            if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
+                app.pssProcState = app.setProcState;
+                app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
+                        mSleeping, now);
+                mPendingPssProcesses.add(app);
+            }
+        }
+        mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+    }
+
+    /**
+     * Ask a given process to GC right now.
+     */
+    final void performAppGcLocked(ProcessRecord app) {
+        try {
+            app.lastRequestedGc = SystemClock.uptimeMillis();
+            if (app.thread != null) {
+                if (app.reportLowMemory) {
+                    app.reportLowMemory = false;
+                    app.thread.scheduleLowMemory();
+                } else {
+                    app.thread.processInBackground();
+                }
+            }
+        } catch (Exception e) {
+            // whatever.
+        }
+    }
+    
+    /**
+     * Returns true if things are idle enough to perform GCs.
+     */
+    private final boolean canGcNowLocked() {
+        boolean processingBroadcasts = false;
+        for (BroadcastQueue q : mBroadcastQueues) {
+            if (q.mParallelBroadcasts.size() != 0 || q.mOrderedBroadcasts.size() != 0) {
+                processingBroadcasts = true;
+            }
+        }
+        return !processingBroadcasts
+                && (mSleeping || mStackSupervisor.allResumedActivitiesIdle());
+    }
+    
+    /**
+     * Perform GCs on all processes that are waiting for it, but only
+     * if things are idle.
+     */
+    final void performAppGcsLocked() {
+        final int N = mProcessesToGc.size();
+        if (N <= 0) {
+            return;
+        }
+        if (canGcNowLocked()) {
+            while (mProcessesToGc.size() > 0) {
+                ProcessRecord proc = mProcessesToGc.remove(0);
+                if (proc.curRawAdj > ProcessList.PERCEPTIBLE_APP_ADJ || proc.reportLowMemory) {
+                    if ((proc.lastRequestedGc+GC_MIN_INTERVAL)
+                            <= SystemClock.uptimeMillis()) {
+                        // To avoid spamming the system, we will GC processes one
+                        // at a time, waiting a few seconds between each.
+                        performAppGcLocked(proc);
+                        scheduleAppGcsLocked();
+                        return;
+                    } else {
+                        // It hasn't been long enough since we last GCed this
+                        // process...  put it in the list to wait for its time.
+                        addProcessToGcListLocked(proc);
+                        break;
+                    }
+                }
+            }
+            
+            scheduleAppGcsLocked();
+        }
+    }
+    
+    /**
+     * If all looks good, perform GCs on all processes waiting for them.
+     */
+    final void performAppGcsIfAppropriateLocked() {
+        if (canGcNowLocked()) {
+            performAppGcsLocked();
+            return;
+        }
+        // Still not idle, wait some more.
+        scheduleAppGcsLocked();
+    }
+
+    /**
+     * Schedule the execution of all pending app GCs.
+     */
+    final void scheduleAppGcsLocked() {
+        mHandler.removeMessages(GC_BACKGROUND_PROCESSES_MSG);
+        
+        if (mProcessesToGc.size() > 0) {
+            // Schedule a GC for the time to the next process.
+            ProcessRecord proc = mProcessesToGc.get(0);
+            Message msg = mHandler.obtainMessage(GC_BACKGROUND_PROCESSES_MSG);
+            
+            long when = proc.lastRequestedGc + GC_MIN_INTERVAL;
+            long now = SystemClock.uptimeMillis();
+            if (when < (now+GC_TIMEOUT)) {
+                when = now + GC_TIMEOUT;
+            }
+            mHandler.sendMessageAtTime(msg, when);
+        }
+    }
+    
+    /**
+     * Add a process to the array of processes waiting to be GCed.  Keeps the
+     * list in sorted order by the last GC time.  The process can't already be
+     * on the list.
+     */
+    final void addProcessToGcListLocked(ProcessRecord proc) {
+        boolean added = false;
+        for (int i=mProcessesToGc.size()-1; i>=0; i--) {
+            if (mProcessesToGc.get(i).lastRequestedGc <
+                    proc.lastRequestedGc) {
+                added = true;
+                mProcessesToGc.add(i+1, proc);
+                break;
+            }
+        }
+        if (!added) {
+            mProcessesToGc.add(0, proc);
+        }
+    }
+    
+    /**
+     * Set up to ask a process to GC itself.  This will either do it
+     * immediately, or put it on the list of processes to gc the next
+     * time things are idle.
+     */
+    final void scheduleAppGcLocked(ProcessRecord app) {
+        long now = SystemClock.uptimeMillis();
+        if ((app.lastRequestedGc+GC_MIN_INTERVAL) > now) {
+            return;
+        }
+        if (!mProcessesToGc.contains(app)) {
+            addProcessToGcListLocked(app);
+            scheduleAppGcsLocked();
+        }
+    }
+
+    final void checkExcessivePowerUsageLocked(boolean doKills) {
+        updateCpuStatsNow();
+
+        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+        boolean doWakeKills = doKills;
+        boolean doCpuKills = doKills;
+        if (mLastPowerCheckRealtime == 0) {
+            doWakeKills = false;
+        }
+        if (mLastPowerCheckUptime == 0) {
+            doCpuKills = false;
+        }
+        if (stats.isScreenOn()) {
+            doWakeKills = false;
+        }
+        final long curRealtime = SystemClock.elapsedRealtime();
+        final long realtimeSince = curRealtime - mLastPowerCheckRealtime;
+        final long curUptime = SystemClock.uptimeMillis();
+        final long uptimeSince = curUptime - mLastPowerCheckUptime;
+        mLastPowerCheckRealtime = curRealtime;
+        mLastPowerCheckUptime = curUptime;
+        if (realtimeSince < WAKE_LOCK_MIN_CHECK_DURATION) {
+            doWakeKills = false;
+        }
+        if (uptimeSince < CPU_MIN_CHECK_DURATION) {
+            doCpuKills = false;
+        }
+        int i = mLruProcesses.size();
+        while (i > 0) {
+            i--;
+            ProcessRecord app = mLruProcesses.get(i);
+            if (!app.keeping) {
+                long wtime;
+                synchronized (stats) {
+                    wtime = stats.getProcessWakeTime(app.info.uid,
+                            app.pid, curRealtime);
+                }
+                long wtimeUsed = wtime - app.lastWakeTime;
+                long cputimeUsed = app.curCpuTime - app.lastCpuTime;
+                if (DEBUG_POWER) {
+                    StringBuilder sb = new StringBuilder(128);
+                    sb.append("Wake for ");
+                    app.toShortString(sb);
+                    sb.append(": over ");
+                    TimeUtils.formatDuration(realtimeSince, sb);
+                    sb.append(" used ");
+                    TimeUtils.formatDuration(wtimeUsed, sb);
+                    sb.append(" (");
+                    sb.append((wtimeUsed*100)/realtimeSince);
+                    sb.append("%)");
+                    Slog.i(TAG, sb.toString());
+                    sb.setLength(0);
+                    sb.append("CPU for ");
+                    app.toShortString(sb);
+                    sb.append(": over ");
+                    TimeUtils.formatDuration(uptimeSince, sb);
+                    sb.append(" used ");
+                    TimeUtils.formatDuration(cputimeUsed, sb);
+                    sb.append(" (");
+                    sb.append((cputimeUsed*100)/uptimeSince);
+                    sb.append("%)");
+                    Slog.i(TAG, sb.toString());
+                }
+                // If a process has held a wake lock for more
+                // than 50% of the time during this period,
+                // that sounds bad.  Kill!
+                if (doWakeKills && realtimeSince > 0
+                        && ((wtimeUsed*100)/realtimeSince) >= 50) {
+                    synchronized (stats) {
+                        stats.reportExcessiveWakeLocked(app.info.uid, app.processName,
+                                realtimeSince, wtimeUsed);
+                    }
+                    killUnneededProcessLocked(app, "excessive wake held " + wtimeUsed
+                            + " during " + realtimeSince);
+                    app.baseProcessTracker.reportExcessiveWake(app.pkgList);
+                } else if (doCpuKills && uptimeSince > 0
+                        && ((cputimeUsed*100)/uptimeSince) >= 50) {
+                    synchronized (stats) {
+                        stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
+                                uptimeSince, cputimeUsed);
+                    }
+                    killUnneededProcessLocked(app, "excessive cpu " + cputimeUsed
+                            + " during " + uptimeSince);
+                    app.baseProcessTracker.reportExcessiveCpu(app.pkgList);
+                } else {
+                    app.lastWakeTime = wtime;
+                    app.lastCpuTime = app.curCpuTime;
+                }
+            }
+        }
+    }
+
+    private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping,
+            ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
+        boolean success = true;
+
+        if (app.curRawAdj != app.setRawAdj) {
+            if (wasKeeping && !app.keeping) {
+                // This app is no longer something we want to keep.  Note
+                // its current wake lock time to later know to kill it if
+                // it is not behaving well.
+                BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
+                synchronized (stats) {
+                    app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
+                            app.pid, SystemClock.elapsedRealtime());
+                }
+                app.lastCpuTime = app.curCpuTime;
+            }
+
+            app.setRawAdj = app.curRawAdj;
+        }
+
+        if (app.curAdj != app.setAdj) {
+            if (Process.setOomAdj(app.pid, app.curAdj)) {
+                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(
+                    TAG, "Set " + app.pid + " " + app.processName +
+                    " adj " + app.curAdj + ": " + app.adjType);
+                app.setAdj = app.curAdj;
+            } else {
+                success = false;
+                Slog.w(TAG, "Failed setting oom adj of " + app + " to " + app.curAdj);
+            }
+        }
+        if (app.setSchedGroup != app.curSchedGroup) {
+            app.setSchedGroup = app.curSchedGroup;
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                    "Setting process group of " + app.processName
+                    + " to " + app.curSchedGroup);
+            if (app.waitingToKill != null &&
+                    app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+                killUnneededProcessLocked(app, app.waitingToKill);
+                success = false;
+            } else {
+                if (true) {
+                    long oldId = Binder.clearCallingIdentity();
+                    try {
+                        Process.setProcessGroup(app.pid, app.curSchedGroup);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Failed setting process group of " + app.pid
+                                + " to " + app.curSchedGroup);
+                        e.printStackTrace();
+                    } finally {
+                        Binder.restoreCallingIdentity(oldId);
+                    }
+                } else {
+                    if (app.thread != null) {
+                        try {
+                            app.thread.setSchedulingGroup(app.curSchedGroup);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+                Process.setSwappiness(app.pid,
+                        app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE);
+            }
+        }
+        if (app.repProcState != app.curProcState) {
+            app.repProcState = app.curProcState;
+            if (!reportingProcessState && app.thread != null) {
+                try {
+                    if (false) {
+                        //RuntimeException h = new RuntimeException("here");
+                        Slog.i(TAG, "Sending new process state " + app.repProcState
+                                + " to " + app /*, h*/);
+                    }
+                    app.thread.setProcessState(app.repProcState);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+        if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
+                app.setProcState)) {
+            app.lastStateTime = now;
+            app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
+                    mSleeping, now);
+            if (DEBUG_PSS) Slog.d(TAG, "Process state change from "
+                    + ProcessList.makeProcStateString(app.setProcState) + " to "
+                    + ProcessList.makeProcStateString(app.curProcState) + " next pss in "
+                    + (app.nextPssTime-now) + ": " + app);
+        } else {
+            if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
+                    && now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) {
+                requestPssLocked(app, app.setProcState);
+                app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false,
+                        mSleeping, now);
+            } else if (false && DEBUG_PSS) {
+                Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now));
+            }
+        }
+        if (app.setProcState != app.curProcState) {
+            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                    "Proc state change of " + app.processName
+                    + " to " + app.curProcState);
+            app.setProcState = app.curProcState;
+            if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+                app.notCachedSinceIdle = false;
+            }
+            if (!doingAll) {
+                setProcessTrackerState(app, mProcessStats.getMemFactorLocked(), now);
+            } else {
+                app.procStateChanged = true;
+            }
+        }
+        return success;
+    }
+
+    private final void setProcessTrackerState(ProcessRecord proc, int memFactor, long now) {
+        if (proc.thread != null && proc.baseProcessTracker != null) {
+            proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList);
+        }
+    }
+
+    private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
+            ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) {
+        if (app.thread == null) {
+            return false;
+        }
+
+        final boolean wasKeeping = app.keeping;
+
+        computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
+
+        return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll,
+                reportingProcessState, now);
+    }
+
+    private final ActivityRecord resumedAppLocked() {
+        return mStackSupervisor.resumedAppLocked();
+    }
+
+    final boolean updateOomAdjLocked(ProcessRecord app) {
+        return updateOomAdjLocked(app, false);
+    }
+
+    final boolean updateOomAdjLocked(ProcessRecord app, boolean doingProcessState) {
+        final ActivityRecord TOP_ACT = resumedAppLocked();
+        final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
+        final boolean wasCached = app.cached;
+
+        mAdjSeq++;
+
+        // This is the desired cached adjusment we want to tell it to use.
+        // If our app is currently cached, we know it, and that is it.  Otherwise,
+        // we don't know it yet, and it needs to now be cached we will then
+        // need to do a complete oom adj.
+        final int cachedAdj = app.curRawAdj >= ProcessList.CACHED_APP_MIN_ADJ
+                ? app.curRawAdj : ProcessList.UNKNOWN_ADJ;
+        boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, doingProcessState,
+                SystemClock.uptimeMillis());
+        if (wasCached != app.cached || app.curRawAdj == ProcessList.UNKNOWN_ADJ) {
+            // Changed to/from cached state, so apps after it in the LRU
+            // list may also be changed.
+            updateOomAdjLocked();
+        }
+        return success;
+    }
+
+    final void updateOomAdjLocked() {
+        final ActivityRecord TOP_ACT = resumedAppLocked();
+        final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
+        final long now = SystemClock.uptimeMillis();
+        final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
+        final int N = mLruProcesses.size();
+
+        if (false) {
+            RuntimeException e = new RuntimeException();
+            e.fillInStackTrace();
+            Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
+        }
+
+        mAdjSeq++;
+        mNewNumServiceProcs = 0;
+        mNewNumAServiceProcs = 0;
+
+        final int emptyProcessLimit;
+        final int cachedProcessLimit;
+        if (mProcessLimit <= 0) {
+            emptyProcessLimit = cachedProcessLimit = 0;
+        } else if (mProcessLimit == 1) {
+            emptyProcessLimit = 1;
+            cachedProcessLimit = 0;
+        } else {
+            emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);
+            cachedProcessLimit = mProcessLimit - emptyProcessLimit;
+        }
+
+        // Let's determine how many processes we have running vs.
+        // how many slots we have for background processes; we may want
+        // to put multiple processes in a slot of there are enough of
+        // them.
+        int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
+                - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;
+        int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
+        if (numEmptyProcs > cachedProcessLimit) {
+            // If there are more empty processes than our limit on cached
+            // processes, then use the cached process limit for the factor.
+            // This ensures that the really old empty processes get pushed
+            // down to the bottom, so if we are running low on memory we will
+            // have a better chance at keeping around more cached processes
+            // instead of a gazillion empty processes.
+            numEmptyProcs = cachedProcessLimit;
+        }
+        int emptyFactor = numEmptyProcs/numSlots;
+        if (emptyFactor < 1) emptyFactor = 1;
+        int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots;
+        if (cachedFactor < 1) cachedFactor = 1;
+        int stepCached = 0;
+        int stepEmpty = 0;
+        int numCached = 0;
+        int numEmpty = 0;
+        int numTrimming = 0;
+
+        mNumNonCachedProcs = 0;
+        mNumCachedHiddenProcs = 0;
+
+        // First update the OOM adjustment for each of the
+        // application processes based on their current state.
+        int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
+        int nextCachedAdj = curCachedAdj+1;
+        int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;
+        int nextEmptyAdj = curEmptyAdj+2;
+        for (int i=N-1; i>=0; i--) {
+            ProcessRecord app = mLruProcesses.get(i);
+            if (!app.killedByAm && app.thread != null) {
+                app.procStateChanged = false;
+                final boolean wasKeeping = app.keeping;
+                computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
+
+                // If we haven't yet assigned the final cached adj
+                // to the process, do that now.
+                if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
+                    switch (app.curProcState) {
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+                        case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                            // This process is a cached process holding activities...
+                            // assign it the next cached value for that type, and then
+                            // step that cached level.
+                            app.curRawAdj = curCachedAdj;
+                            app.curAdj = app.modifyRawOomAdj(curCachedAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG, "Assigning activity LRU #" + i
+                                    + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+                                    + ")");
+                            if (curCachedAdj != nextCachedAdj) {
+                                stepCached++;
+                                if (stepCached >= cachedFactor) {
+                                    stepCached = 0;
+                                    curCachedAdj = nextCachedAdj;
+                                    nextCachedAdj += 2;
+                                    if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+                                        nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                    }
+                                }
+                            }
+                            break;
+                        default:
+                            // For everything else, assign next empty cached process
+                            // level and bump that up.  Note that this means that
+                            // long-running services that have dropped down to the
+                            // cached level will be treated as empty (since their process
+                            // state is still as a service), which is what we want.
+                            app.curRawAdj = curEmptyAdj;
+                            app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+                            if (DEBUG_LRU && false) Slog.d(TAG, "Assigning empty LRU #" + i
+                                    + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+                                    + ")");
+                            if (curEmptyAdj != nextEmptyAdj) {
+                                stepEmpty++;
+                                if (stepEmpty >= emptyFactor) {
+                                    stepEmpty = 0;
+                                    curEmptyAdj = nextEmptyAdj;
+                                    nextEmptyAdj += 2;
+                                    if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+                                        nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
+                                    }
+                                }
+                            }
+                            break;
+                    }
+                }
+
+                applyOomAdjLocked(app, wasKeeping, TOP_APP, true, false, now);
+
+                // Count the number of process types.
+                switch (app.curProcState) {
+                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+                    case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        mNumCachedHiddenProcs++;
+                        numCached++;
+                        if (numCached > cachedProcessLimit) {
+                            killUnneededProcessLocked(app, "cached #" + numCached);
+                        }
+                        break;
+                    case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+                        if (numEmpty > ProcessList.TRIM_EMPTY_APPS
+                                && app.lastActivityTime < oldTime) {
+                            killUnneededProcessLocked(app, "empty for "
+                                    + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
+                                    / 1000) + "s");
+                        } else {
+                            numEmpty++;
+                            if (numEmpty > emptyProcessLimit) {
+                                killUnneededProcessLocked(app, "empty #" + numEmpty);
+                            }
+                        }
+                        break;
+                    default:
+                        mNumNonCachedProcs++;
+                        break;
+                }
+
+                if (app.isolated && app.services.size() <= 0) {
+                    // If this is an isolated process, and there are no
+                    // services running in it, then the process is no longer
+                    // needed.  We agressively kill these because we can by
+                    // definition not re-use the same process again, and it is
+                    // good to avoid having whatever code was running in them
+                    // left sitting around after no longer needed.
+                    killUnneededProcessLocked(app, "isolated not needed");
+                }
+
+                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
+                        && !app.killedByAm) {
+                    numTrimming++;
+                }
+            }
+        }
+
+        mNumServiceProcs = mNewNumServiceProcs;
+
+        // Now determine the memory trimming level of background processes.
+        // Unfortunately we need to start at the back of the list to do this
+        // properly.  We only do this if the number of background apps we
+        // are managing to keep around is less than half the maximum we desire;
+        // if we are keeping a good number around, we'll let them use whatever
+        // memory they want.
+        final int numCachedAndEmpty = numCached + numEmpty;
+        int memFactor;
+        if (numCached <= ProcessList.TRIM_CACHED_APPS
+                && numEmpty <= ProcessList.TRIM_EMPTY_APPS) {
+            if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
+                memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
+            } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
+                memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
+            } else {
+                memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
+            }
+        } else {
+            memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+        }
+        // We always allow the memory level to go up (better).  We only allow it to go
+        // down if we are in a state where that is allowed, *and* the total number of processes
+        // has gone down since last time.
+        if (DEBUG_OOM_ADJ) Slog.d(TAG, "oom: memFactor=" + memFactor + " last=" + mLastMemoryLevel
+                + " allowLow=" + mAllowLowerMemLevel + " numProcs=" + mLruProcesses.size()
+                + " last=" + mLastNumProcesses);
+        if (memFactor > mLastMemoryLevel) {
+            if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) {
+                memFactor = mLastMemoryLevel;
+                if (DEBUG_OOM_ADJ) Slog.d(TAG, "Keeping last mem factor!");
+            }
+        }
+        mLastMemoryLevel = memFactor;
+        mLastNumProcesses = mLruProcesses.size();
+        boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now);
+        final int trackerMemFactor = mProcessStats.getMemFactorLocked();
+        if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {
+            if (mLowRamStartTime == 0) {
+                mLowRamStartTime = now;
+            }
+            int step = 0;
+            int fgTrimLevel;
+            switch (memFactor) {
+                case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
+                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
+                    break;
+                case ProcessStats.ADJ_MEM_FACTOR_LOW:
+                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
+                    break;
+                default:
+                    fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;
+                    break;
+            }
+            int factor = numTrimming/3;
+            int minFactor = 2;
+            if (mHomeProcess != null) minFactor++;
+            if (mPreviousProcess != null) minFactor++;
+            if (factor < minFactor) factor = minFactor;
+            int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
+            for (int i=N-1; i>=0; i--) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (allChanged || app.procStateChanged) {
+                    setProcessTrackerState(app, trackerMemFactor, now);
+                    app.procStateChanged = false;
+                }
+                if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
+                        && !app.killedByAm) {
+                    if (app.trimMemoryLevel < curLevel && app.thread != null) {
+                        try {
+                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                                    "Trimming memory of " + app.processName
+                                    + " to " + curLevel);
+                            app.thread.scheduleTrimMemory(curLevel);
+                        } catch (RemoteException e) {
+                        }
+                        if (false) {
+                            // For now we won't do this; our memory trimming seems
+                            // to be good enough at this point that destroying
+                            // activities causes more harm than good.
+                            if (curLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE
+                                    && app != mHomeProcess && app != mPreviousProcess) {
+                                // Need to do this on its own message because the stack may not
+                                // be in a consistent state at this point.
+                                // For these apps we will also finish their activities
+                                // to help them free memory.
+                                mStackSupervisor.scheduleDestroyAllActivities(app, "trim");
+                            }
+                        }
+                    }
+                    app.trimMemoryLevel = curLevel;
+                    step++;
+                    if (step >= factor) {
+                        step = 0;
+                        switch (curLevel) {
+                            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+                                curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;
+                                break;
+                            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+                                curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+                                break;
+                        }
+                    }
+                } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND
+                            && app.thread != null) {
+                        try {
+                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                                    "Trimming memory of heavy-weight " + app.processName
+                                    + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
+                            app.thread.scheduleTrimMemory(
+                                    ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
+                } else {
+                    if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                            || app.systemNoUi) && app.pendingUiClean) {
+                        // If this application is now in the background and it
+                        // had done UI, then give it the special trim level to
+                        // have it free UI resources.
+                        final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;
+                        if (app.trimMemoryLevel < level && app.thread != null) {
+                            try {
+                                if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                                        "Trimming memory of bg-ui " + app.processName
+                                        + " to " + level);
+                                app.thread.scheduleTrimMemory(level);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                        app.pendingUiClean = false;
+                    }
+                    if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {
+                        try {
+                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                                    "Trimming memory of fg " + app.processName
+                                    + " to " + fgTrimLevel);
+                            app.thread.scheduleTrimMemory(fgTrimLevel);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    app.trimMemoryLevel = fgTrimLevel;
+                }
+            }
+        } else {
+            if (mLowRamStartTime != 0) {
+                mLowRamTimeSinceLastIdle += now - mLowRamStartTime;
+                mLowRamStartTime = 0;
+            }
+            for (int i=N-1; i>=0; i--) {
+                ProcessRecord app = mLruProcesses.get(i);
+                if (allChanged || app.procStateChanged) {
+                    setProcessTrackerState(app, trackerMemFactor, now);
+                    app.procStateChanged = false;
+                }
+                if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+                        || app.systemNoUi) && app.pendingUiClean) {
+                    if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
+                            && app.thread != null) {
+                        try {
+                            if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
+                                    "Trimming memory of ui hidden " + app.processName
+                                    + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+                            app.thread.scheduleTrimMemory(
+                                    ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                    app.pendingUiClean = false;
+                }
+                app.trimMemoryLevel = 0;
+            }
+        }
+
+        if (mAlwaysFinishActivities) {
+            // Need to do this on its own message because the stack may not
+            // be in a consistent state at this point.
+            mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");
+        }
+
+        if (allChanged) {
+            requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());
+        }
+
+        if (mProcessStats.shouldWriteNowLocked(now)) {
+            mHandler.post(new Runnable() {
+                @Override public void run() {
+                    synchronized (ActivityManagerService.this) {
+                        mProcessStats.writeStateAsyncLocked();
+                    }
+                }
+            });
+        }
+
+        if (DEBUG_OOM_ADJ) {
+            Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms");
+        }
+    }
+
+    final void trimApplications() {
+        synchronized (this) {
+            int i;
+
+            // First remove any unused application processes whose package
+            // has been removed.
+            for (i=mRemovedProcesses.size()-1; i>=0; i--) {
+                final ProcessRecord app = mRemovedProcesses.get(i);
+                if (app.activities.size() == 0
+                        && app.curReceiver == null && app.services.size() == 0) {
+                    Slog.i(
+                        TAG, "Exiting empty application process "
+                        + app.processName + " ("
+                        + (app.thread != null ? app.thread.asBinder() : null)
+                        + ")\n");
+                    if (app.pid > 0 && app.pid != MY_PID) {
+                        EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
+                                app.processName, app.setAdj, "empty");
+                        app.killedByAm = true;
+                        Process.killProcessQuiet(app.pid);
+                    } else {
+                        try {
+                            app.thread.scheduleExit();
+                        } catch (Exception e) {
+                            // Ignore exceptions.
+                        }
+                    }
+                    cleanUpApplicationRecordLocked(app, false, true, -1);
+                    mRemovedProcesses.remove(i);
+                    
+                    if (app.persistent) {
+                        if (app.persistent) {
+                            addAppLocked(app.info, false);
+                        }
+                    }
+                }
+            }
+
+            // Now update the oom adj for all processes.
+            updateOomAdjLocked();
+        }
+    }
+
+    /** This method sends the specified signal to each of the persistent apps */
+    public void signalPersistentProcesses(int sig) throws RemoteException {
+        if (sig != Process.SIGNAL_USR1) {
+            throw new SecurityException("Only SIGNAL_USR1 is allowed");
+        }
+
+        synchronized (this) {
+            if (checkCallingPermission(android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires permission "
+                        + android.Manifest.permission.SIGNAL_PERSISTENT_PROCESSES);
+            }
+
+            for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                ProcessRecord r = mLruProcesses.get(i);
+                if (r.thread != null && r.persistent) {
+                    Process.sendSignal(r.pid, sig);
+                }
+            }
+        }
+    }
+
+    private void stopProfilerLocked(ProcessRecord proc, String path, int profileType) {
+        if (proc == null || proc == mProfileProc) {
+            proc = mProfileProc;
+            path = mProfileFile;
+            profileType = mProfileType;
+            clearProfilerLocked();
+        }
+        if (proc == null) {
+            return;
+        }
+        try {
+            proc.thread.profilerControl(false, path, null, profileType);
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        }
+    }
+
+    private void clearProfilerLocked() {
+        if (mProfileFd != null) {
+            try {
+                mProfileFd.close();
+            } catch (IOException e) {
+            }
+        }
+        mProfileApp = null;
+        mProfileProc = null;
+        mProfileFile = null;
+        mProfileType = 0;
+        mAutoStopProfiler = false;
+    }
+
+    public boolean profileControl(String process, int userId, boolean start,
+            String path, ParcelFileDescriptor fd, int profileType) throws RemoteException {
+
+        try {
+            synchronized (this) {
+                // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+                // its own permission.
+                if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Requires permission "
+                            + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+                }
+
+                if (start && fd == null) {
+                    throw new IllegalArgumentException("null fd");
+                }
+
+                ProcessRecord proc = null;
+                if (process != null) {
+                    proc = findProcessLocked(process, userId, "profileControl");
+                }
+
+                if (start && (proc == null || proc.thread == null)) {
+                    throw new IllegalArgumentException("Unknown process: " + process);
+                }
+
+                if (start) {
+                    stopProfilerLocked(null, null, 0);
+                    setProfileApp(proc.info, proc.processName, path, fd, false);
+                    mProfileProc = proc;
+                    mProfileType = profileType;
+                    try {
+                        fd = fd.dup();
+                    } catch (IOException e) {
+                        fd = null;
+                    }
+                    proc.thread.profilerControl(start, path, fd, profileType);
+                    fd = null;
+                    mProfileFd = null;
+                } else {
+                    stopProfilerLocked(proc, path, profileType);
+                    if (fd != null) {
+                        try {
+                            fd.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+
+                return true;
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        } finally {
+            if (fd != null) {
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private ProcessRecord findProcessLocked(String process, int userId, String callName) {
+        userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, true, true, callName, null);
+        ProcessRecord proc = null;
+        try {
+            int pid = Integer.parseInt(process);
+            synchronized (mPidsSelfLocked) {
+                proc = mPidsSelfLocked.get(pid);
+            }
+        } catch (NumberFormatException e) {
+        }
+
+        if (proc == null) {
+            ArrayMap<String, SparseArray<ProcessRecord>> all
+                    = mProcessNames.getMap();
+            SparseArray<ProcessRecord> procs = all.get(process);
+            if (procs != null && procs.size() > 0) {
+                proc = procs.valueAt(0);
+                if (userId != UserHandle.USER_ALL && proc.userId != userId) {
+                    for (int i=1; i<procs.size(); i++) {
+                        ProcessRecord thisProc = procs.valueAt(i);
+                        if (thisProc.userId == userId) {
+                            proc = thisProc;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return proc;
+    }
+
+    public boolean dumpHeap(String process, int userId, boolean managed,
+            String path, ParcelFileDescriptor fd) throws RemoteException {
+
+        try {
+            synchronized (this) {
+                // note: hijacking SET_ACTIVITY_WATCHER, but should be changed to
+                // its own permission (same as profileControl).
+                if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Requires permission "
+                            + android.Manifest.permission.SET_ACTIVITY_WATCHER);
+                }
+
+                if (fd == null) {
+                    throw new IllegalArgumentException("null fd");
+                }
+
+                ProcessRecord proc = findProcessLocked(process, userId, "dumpHeap");
+                if (proc == null || proc.thread == null) {
+                    throw new IllegalArgumentException("Unknown process: " + process);
+                }
+
+                boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+                if (!isDebuggable) {
+                    if ((proc.info.flags&ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+                        throw new SecurityException("Process not debuggable: " + proc);
+                    }
+                }
+
+                proc.thread.dumpHeap(managed, path, fd);
+                fd = null;
+                return true;
+            }
+        } catch (RemoteException e) {
+            throw new IllegalStateException("Process disappeared");
+        } finally {
+            if (fd != null) {
+                try {
+                    fd.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    /** In this method we try to acquire our lock to make sure that we have not deadlocked */
+    public void monitor() {
+        synchronized (this) { }
+    }
+
+    void onCoreSettingsChange(Bundle settings) {
+        for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
+            ProcessRecord processRecord = mLruProcesses.get(i);
+            try {
+                if (processRecord.thread != null) {
+                    processRecord.thread.setCoreSettings(settings);
+                }
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+    }
+
+    // Multi-user methods
+
+    @Override
+    public boolean switchUser(final int userId) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final int oldUserId = mCurrentUserId;
+                if (oldUserId == userId) {
+                    return true;
+                }
+
+                final UserInfo userInfo = getUserManagerLocked().getUserInfo(userId);
+                if (userInfo == null) {
+                    Slog.w(TAG, "No user info for user #" + userId);
+                    return false;
+                }
+
+                mWindowManager.startFreezingScreen(R.anim.screen_user_exit,
+                        R.anim.screen_user_enter);
+
+                boolean needStart = false;
+
+                // If the user we are switching to is not currently started, then
+                // we need to start it now.
+                if (mStartedUsers.get(userId) == null) {
+                    mStartedUsers.put(userId, new UserStartedState(new UserHandle(userId), false));
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                mCurrentUserId = userId;
+                final Integer userIdInt = Integer.valueOf(userId);
+                mUserLru.remove(userIdInt);
+                mUserLru.add(userIdInt);
+
+                mWindowManager.setCurrentUser(userId);
+
+                // Once the internal notion of the active user has switched, we lock the device
+                // with the option to show the user switcher on the keyguard.
+                mWindowManager.lockNow(null);
+
+                final UserStartedState uss = mStartedUsers.get(userId);
+
+                // Make sure user is in the started state.  If it is currently
+                // stopping, we need to knock that off.
+                if (uss.mState == UserStartedState.STATE_STOPPING) {
+                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                    // so we can just fairly silently bring the user back from
+                    // the almost-dead.
+                    uss.mState = UserStartedState.STATE_RUNNING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                } else if (uss.mState == UserStartedState.STATE_SHUTDOWN) {
+                    // This means ACTION_SHUTDOWN has been sent, so we will
+                    // need to treat this as a new boot of the user.
+                    uss.mState = UserStartedState.STATE_BOOTING;
+                    updateStartedUserArrayLocked();
+                    needStart = true;
+                }
+
+                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                        oldUserId, userId, uss));
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
+                if (needStart) {
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                            | Intent.FLAG_RECEIVER_FOREGROUND);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    broadcastIntentLocked(null, null, intent,
+                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                            false, false, MY_PID, Process.SYSTEM_UID, userId);
+                }
+
+                if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                    if (userId != 0) {
+                        Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+                        broadcastIntentLocked(null, null, intent, null,
+                                new IIntentReceiver.Stub() {
+                                    public void performReceive(Intent intent, int resultCode,
+                                            String data, Bundle extras, boolean ordered,
+                                            boolean sticky, int sendingUser) {
+                                        userInitialized(uss, userId);
+                                    }
+                                }, 0, null, null, null, AppOpsManager.OP_NONE,
+                                true, false, MY_PID, Process.SYSTEM_UID,
+                                userId);
+                        uss.initializing = true;
+                    } else {
+                        getUserManagerLocked().makeInitialized(userInfo.id);
+                    }
+                }
+
+                boolean homeInFront = mStackSupervisor.switchUserLocked(userId, uss);
+                if (homeInFront) {
+                    startHomeActivityLocked(userId);
+                } else {
+                    mStackSupervisor.resumeTopActivitiesLocked();
+                }
+
+                EventLogTags.writeAmSwitchUser(userId);
+                getUserManagerLocked().userForeground(userId);
+                sendUserSwitchBroadcastsLocked(oldUserId, userId);
+                if (needStart) {
+                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                    broadcastIntentLocked(null, null, intent,
+                            null, new IIntentReceiver.Stub() {
+                                @Override
+                                public void performReceive(Intent intent, int resultCode, String data,
+                                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                                        throws RemoteException {
+                                }
+                            }, 0, null, null,
+                            android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                            true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return true;
+    }
+
+    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Intent intent;
+            if (oldUserId >= 0) {
+                intent = new Intent(Intent.ACTION_USER_BACKGROUND);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, oldUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        false, false, MY_PID, Process.SYSTEM_UID, oldUserId);
+            }
+            if (newUserId >= 0) {
+                intent = new Intent(Intent.ACTION_USER_FOREGROUND);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        false, false, MY_PID, Process.SYSTEM_UID, newUserId);
+                intent = new Intent(Intent.ACTION_USER_SWITCHED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        android.Manifest.permission.MANAGE_USERS, AppOpsManager.OP_NONE,
+                        false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    void dispatchUserSwitch(final UserStartedState uss, final int oldUserId,
+            final int newUserId) {
+        final int N = mUserSwitchObservers.beginBroadcast();
+        if (N > 0) {
+            final IRemoteCallback callback = new IRemoteCallback.Stub() {
+                int mCount = 0;
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    synchronized (ActivityManagerService.this) {
+                        if (mCurUserSwitchCallback == this) {
+                            mCount++;
+                            if (mCount == N) {
+                                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                            }
+                        }
+                    }
+                }
+            };
+            synchronized (this) {
+                uss.switching = true;
+                mCurUserSwitchCallback = callback;
+            }
+            for (int i=0; i<N; i++) {
+                try {
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(
+                            newUserId, callback);
+                } catch (RemoteException e) {
+                }
+            }
+        } else {
+            synchronized (this) {
+                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            }
+        }
+        mUserSwitchObservers.finishBroadcast();
+    }
+
+    void timeoutUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
+        synchronized (this) {
+            Slog.w(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
+            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+        }
+    }
+
+    void sendContinueUserSwitchLocked(UserStartedState uss, int oldUserId, int newUserId) {
+        mCurUserSwitchCallback = null;
+        mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
+                oldUserId, newUserId, uss));
+    }
+
+    void userInitialized(UserStartedState uss, int newUserId) {
+        completeSwitchAndInitalize(uss, newUserId, true, false);
+    }
+
+    void continueUserSwitch(UserStartedState uss, int oldUserId, int newUserId) {
+        completeSwitchAndInitalize(uss, newUserId, false, true);
+    }
+
+    void completeSwitchAndInitalize(UserStartedState uss, int newUserId,
+            boolean clearInitializing, boolean clearSwitching) {
+        boolean unfrozen = false;
+        synchronized (this) {
+            if (clearInitializing) {
+                uss.initializing = false;
+                getUserManagerLocked().makeInitialized(uss.mHandle.getIdentifier());
+            }
+            if (clearSwitching) {
+                uss.switching = false;
+            }
+            if (!uss.switching && !uss.initializing) {
+                mWindowManager.stopFreezingScreen();
+                unfrozen = true;
+            }
+        }
+        if (unfrozen) {
+            final int N = mUserSwitchObservers.beginBroadcast();
+            for (int i=0; i<N; i++) {
+                try {
+                    mUserSwitchObservers.getBroadcastItem(i).onUserSwitchComplete(newUserId);
+                } catch (RemoteException e) {
+                }
+            }
+            mUserSwitchObservers.finishBroadcast();
+        }
+    }
+
+    void finishUserSwitch(UserStartedState uss) {
+        synchronized (this) {
+            if (uss.mState == UserStartedState.STATE_BOOTING
+                    && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) {
+                uss.mState = UserStartedState.STATE_RUNNING;
+                final int userId = uss.mHandle.getIdentifier();
+                Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT);
+                broadcastIntentLocked(null, null, intent,
+                        null, null, 0, null, null,
+                        android.Manifest.permission.RECEIVE_BOOT_COMPLETED, AppOpsManager.OP_NONE,
+                        true, false, MY_PID, Process.SYSTEM_UID, userId);
+            }
+            int num = mUserLru.size();
+            int i = 0;
+            while (num > MAX_RUNNING_USERS && i < mUserLru.size()) {
+                Integer oldUserId = mUserLru.get(i);
+                UserStartedState oldUss = mStartedUsers.get(oldUserId);
+                if (oldUss == null) {
+                    // Shouldn't happen, but be sane if it does.
+                    mUserLru.remove(i);
+                    num--;
+                    continue;
+                }
+                if (oldUss.mState == UserStartedState.STATE_STOPPING
+                        || oldUss.mState == UserStartedState.STATE_SHUTDOWN) {
+                    // This user is already stopping, doesn't count.
+                    num--;
+                    i++;
+                    continue;
+                }
+                if (oldUserId == UserHandle.USER_OWNER || oldUserId == mCurrentUserId) {
+                    // Owner and current can't be stopped, but count as running.
+                    i++;
+                    continue;
+                }
+                // This is a user to be stopped.
+                stopUserLocked(oldUserId, null);
+                num--;
+                i++;
+            }
+        }
+    }
+
+    @Override
+    public int stopUser(final int userId, final IStopUserCallback callback) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: switchUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        if (userId <= 0) {
+            throw new IllegalArgumentException("Can't stop primary user " + userId);
+        }
+        synchronized (this) {
+            return stopUserLocked(userId, callback);
+        }
+    }
+
+    private int stopUserLocked(final int userId, final IStopUserCallback callback) {
+        if (mCurrentUserId == userId) {
+            return ActivityManager.USER_OP_IS_CURRENT;
+        }
+
+        final UserStartedState uss = mStartedUsers.get(userId);
+        if (uss == null) {
+            // User is not started, nothing to do...  but we do need to
+            // callback if requested.
+            if (callback != null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            callback.userStopped(userId);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                });
+            }
+            return ActivityManager.USER_OP_SUCCESS;
+        }
+
+        if (callback != null) {
+            uss.mStopCallbacks.add(callback);
+        }
+
+        if (uss.mState != UserStartedState.STATE_STOPPING
+                && uss.mState != UserStartedState.STATE_SHUTDOWN) {
+            uss.mState = UserStartedState.STATE_STOPPING;
+            updateStartedUserArrayLocked();
+
+            long ident = Binder.clearCallingIdentity();
+            try {
+                // We are going to broadcast ACTION_USER_STOPPING and then
+                // once that is done send a final ACTION_SHUTDOWN and then
+                // stop the user.
+                final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING);
+                stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true);
+                final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
+                // This is the result receiver for the final shutdown broadcast.
+                final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        finishUserStop(uss);
+                    }
+                };
+                // This is the result receiver for the initial stopping broadcast.
+                final IIntentReceiver stoppingReceiver = new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        // On to the next.
+                        synchronized (ActivityManagerService.this) {
+                            if (uss.mState != UserStartedState.STATE_STOPPING) {
+                                // Whoops, we are being started back up.  Abort, abort!
+                                return;
+                            }
+                            uss.mState = UserStartedState.STATE_SHUTDOWN;
+                        }
+                        broadcastIntentLocked(null, null, shutdownIntent,
+                                null, shutdownReceiver, 0, null, null, null, AppOpsManager.OP_NONE,
+                                true, false, MY_PID, Process.SYSTEM_UID, userId);
+                    }
+                };
+                // Kick things off.
+                broadcastIntentLocked(null, null, stoppingIntent,
+                        null, stoppingReceiver, 0, null, null,
+                        android.Manifest.permission.INTERACT_ACROSS_USERS, AppOpsManager.OP_NONE,
+                        true, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        return ActivityManager.USER_OP_SUCCESS;
+    }
+
+    void finishUserStop(UserStartedState uss) {
+        final int userId = uss.mHandle.getIdentifier();
+        boolean stopped;
+        ArrayList<IStopUserCallback> callbacks;
+        synchronized (this) {
+            callbacks = new ArrayList<IStopUserCallback>(uss.mStopCallbacks);
+            if (mStartedUsers.get(userId) != uss) {
+                stopped = false;
+            } else if (uss.mState != UserStartedState.STATE_SHUTDOWN) {
+                stopped = false;
+            } else {
+                stopped = true;
+                // User can no longer run.
+                mStartedUsers.remove(userId);
+                mUserLru.remove(Integer.valueOf(userId));
+                updateStartedUserArrayLocked();
+
+                // Clean up all state and processes associated with the user.
+                // Kill all the processes for the user.
+                forceStopUserLocked(userId, "finish user");
+            }
+        }
+
+        for (int i=0; i<callbacks.size(); i++) {
+            try {
+                if (stopped) callbacks.get(i).userStopped(userId);
+                else callbacks.get(i).userStopAborted(userId);
+            } catch (RemoteException e) {
+            }
+        }
+
+        mStackSupervisor.removeUserLocked(userId);
+    }
+
+    @Override
+    public UserInfo getCurrentUser() {
+        if ((checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) && (
+                checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED)) {
+            String msg = "Permission Denial: getCurrentUser() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (this) {
+            return getUserManagerLocked().getUserInfo(mCurrentUserId);
+        }
+    }
+
+    int getCurrentUserIdLocked() {
+        return mCurrentUserId;
+    }
+
+    @Override
+    public boolean isUserRunning(int userId, boolean orStopped) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: isUserRunning() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (this) {
+            return isUserRunningLocked(userId, orStopped);
+        }
+    }
+
+    boolean isUserRunningLocked(int userId, boolean orStopped) {
+        UserStartedState state = mStartedUsers.get(userId);
+        if (state == null) {
+            return false;
+        }
+        if (orStopped) {
+            return true;
+        }
+        return state.mState != UserStartedState.STATE_STOPPING
+                && state.mState != UserStartedState.STATE_SHUTDOWN;
+    }
+
+    @Override
+    public int[] getRunningUserIds() {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: isUserRunning() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (this) {
+            return mStartedUserArray;
+        }
+    }
+
+    private void updateStartedUserArrayLocked() {
+        int num = 0;
+        for (int i=0; i<mStartedUsers.size();  i++) {
+            UserStartedState uss = mStartedUsers.valueAt(i);
+            // This list does not include stopping users.
+            if (uss.mState != UserStartedState.STATE_STOPPING
+                    && uss.mState != UserStartedState.STATE_SHUTDOWN) {
+                num++;
+            }
+        }
+        mStartedUserArray = new int[num];
+        num = 0;
+        for (int i=0; i<mStartedUsers.size();  i++) {
+            UserStartedState uss = mStartedUsers.valueAt(i);
+            if (uss.mState != UserStartedState.STATE_STOPPING
+                    && uss.mState != UserStartedState.STATE_SHUTDOWN) {
+                mStartedUserArray[num] = mStartedUsers.keyAt(i);
+                num++;
+            }
+        }
+    }
+
+    @Override
+    public void registerUserSwitchObserver(IUserSwitchObserver observer) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: registerUserSwitchObserver() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        mUserSwitchObservers.register(observer);
+    }
+
+    @Override
+    public void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
+        mUserSwitchObservers.unregister(observer);
+    }
+
+    private boolean userExists(int userId) {
+        if (userId == 0) {
+            return true;
+        }
+        UserManagerService ums = getUserManagerLocked();
+        return ums != null ? (ums.getUserInfo(userId) != null) : false;
+    }
+
+    int[] getUsersLocked() {
+        UserManagerService ums = getUserManagerLocked();
+        return ums != null ? ums.getUserIds() : new int[] { 0 };
+    }
+
+    UserManagerService getUserManagerLocked() {
+        if (mUserManager == null) {
+            IBinder b = ServiceManager.getService(Context.USER_SERVICE);
+            mUserManager = (UserManagerService)IUserManager.Stub.asInterface(b);
+        }
+        return mUserManager;
+    }
+
+    private int applyUserId(int uid, int userId) {
+        return UserHandle.getUid(userId, uid);
+    }
+
+    ApplicationInfo getAppInfoForUser(ApplicationInfo info, int userId) {
+        if (info == null) return null;
+        ApplicationInfo newInfo = new ApplicationInfo(info);
+        newInfo.uid = applyUserId(info.uid, userId);
+        newInfo.dataDir = USER_DATA_DIR + userId + "/"
+                + info.packageName;
+        return newInfo;
+    }
+
+    ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
+        if (aInfo == null
+                || (userId < 1 && aInfo.applicationInfo.uid < UserHandle.PER_USER_RANGE)) {
+            return aInfo;
+        }
+
+        ActivityInfo info = new ActivityInfo(aInfo);
+        info.applicationInfo = getAppInfoForUser(info.applicationInfo, userId);
+        return info;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
new file mode 100644
index 0000000..38b01df
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.os.Trace;
+import com.android.internal.R.styleable;
+import com.android.internal.app.ResolverActivity;
+import com.android.server.AttributeCache;
+import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+
+import android.app.ActivityOptions;
+import android.app.ResultInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.IApplicationToken;
+import android.view.WindowManager;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+/**
+ * An entry in the history stack, representing an activity.
+ */
+final class ActivityRecord {
+    static final String TAG = ActivityManagerService.TAG;
+    static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE;
+    final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recent";
+
+    final ActivityManagerService service; // owner
+    final IApplicationToken.Stub appToken; // window manager token
+    final ActivityInfo info; // all about me
+    final int launchedFromUid; // always the uid who started the activity.
+    final String launchedFromPackage; // always the package who started the activity.
+    final int userId;          // Which user is this running for?
+    final Intent intent;    // the original intent that generated us
+    final ComponentName realActivity;  // the intent component, or target of an alias.
+    final String shortComponentName; // the short component name of the intent
+    final String resolvedType; // as per original caller;
+    final String packageName; // the package implementing intent's component
+    final String processName; // process where this component wants to run
+    final String taskAffinity; // as per ActivityInfo.taskAffinity
+    final boolean stateNotNeeded; // As per ActivityInfo.flags
+    boolean fullscreen; // covers the full screen?
+    final boolean noDisplay;  // activity is not displayed?
+    final boolean componentSpecified;  // did caller specifiy an explicit component?
+
+    static final int APPLICATION_ACTIVITY_TYPE = 0;
+    static final int HOME_ACTIVITY_TYPE = 1;
+    static final int RECENTS_ACTIVITY_TYPE = 2;
+    int mActivityType;
+
+    final String baseDir;   // where activity source (resources etc) located
+    final String resDir;   // where public activity source (public resources etc) located
+    final String dataDir;   // where activity data should go
+    CharSequence nonLocalizedLabel;  // the label information from the package mgr.
+    int labelRes;           // the label information from the package mgr.
+    int icon;               // resource identifier of activity's icon.
+    int logo;               // resource identifier of activity's logo.
+    int theme;              // resource identifier of activity's theme.
+    int realTheme;          // actual theme resource we will use, never 0.
+    int windowFlags;        // custom window flags for preview window.
+    TaskRecord task;        // the task this is in.
+    ThumbnailHolder thumbHolder; // where our thumbnails should go.
+    long displayStartTime;  // when we started launching this activity
+    long fullyDrawnStartTime; // when we started launching this activity
+    long startTime;         // last time this activity was started
+    long lastVisibleTime;   // last time this activity became visible
+    long cpuTimeAtResume;   // the cpu time of host process at the time of resuming activity
+    long pauseTime;         // last time we started pausing the activity
+    long launchTickTime;    // base time for launch tick messages
+    Configuration configuration; // configuration activity was last running in
+    CompatibilityInfo compat;// last used compatibility mode
+    ActivityRecord resultTo; // who started this entry, so will get our reply
+    final String resultWho; // additional identifier for use by resultTo.
+    final int requestCode;  // code given by requester (resultTo)
+    ArrayList<ResultInfo> results; // pending ActivityResult objs we have received
+    HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act
+    ArrayList<Intent> newIntents; // any pending new intents for single-top mode
+    ActivityOptions pendingOptions; // most recently given options
+    HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold
+    UriPermissionOwner uriPermissions; // current special URI access perms.
+    ProcessRecord app;      // if non-null, hosting application
+    ActivityState state;    // current state we are in
+    Bundle  icicle;         // last saved activity state
+    boolean frontOfTask;    // is this the root activity of its task?
+    boolean launchFailed;   // set if a launched failed, to abort on 2nd try
+    boolean haveState;      // have we gotten the last activity state?
+    boolean stopped;        // is activity pause finished?
+    boolean delayedResume;  // not yet resumed because of stopped app switches?
+    boolean finishing;      // activity in pending finish list?
+    boolean configDestroy;  // need to destroy due to config change?
+    int configChangeFlags;  // which config values have changed
+    boolean keysPaused;     // has key dispatching been paused for it?
+    int launchMode;         // the launch mode activity attribute.
+    boolean visible;        // does this activity's window need to be shown?
+    boolean sleeping;       // have we told the activity to sleep?
+    boolean waitingVisible; // true if waiting for a new act to become vis
+    boolean nowVisible;     // is this activity's window visible?
+    boolean thumbnailNeeded;// has someone requested a thumbnail?
+    boolean idle;           // has the activity gone idle?
+    boolean hasBeenLaunched;// has this activity ever been launched?
+    boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
+    boolean immersive;      // immersive mode (don't interrupt if possible)
+    boolean forceNewConfig; // force re-create with new config next time
+    int launchCount;        // count of launches since last state
+    long lastLaunchTime;    // time of last lauch of this activity
+    ArrayList<ActivityStack> mChildContainers = new ArrayList<ActivityStack>();
+
+    String stringName;      // for caching of toString().
+
+    private boolean inHistory;  // are we in the history stack?
+    final ActivityStackSupervisor mStackSupervisor;
+
+    void dump(PrintWriter pw, String prefix) {
+        final long now = SystemClock.uptimeMillis();
+        pw.print(prefix); pw.print("packageName="); pw.print(packageName);
+                pw.print(" processName="); pw.println(processName);
+        pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid);
+                pw.print(" launchedFromPackage="); pw.print(launchedFromPackage);
+                pw.print(" userId="); pw.println(userId);
+        pw.print(prefix); pw.print("app="); pw.println(app);
+        pw.print(prefix); pw.println(intent.toInsecureStringWithClip());
+        pw.print(prefix); pw.print("frontOfTask="); pw.print(frontOfTask);
+                pw.print(" task="); pw.println(task);
+        pw.print(prefix); pw.print("taskAffinity="); pw.println(taskAffinity);
+        pw.print(prefix); pw.print("realActivity=");
+                pw.println(realActivity.flattenToShortString());
+        pw.print(prefix); pw.print("baseDir="); pw.println(baseDir);
+        if (!resDir.equals(baseDir)) {
+            pw.print(prefix); pw.print("resDir="); pw.println(resDir);
+        }
+        pw.print(prefix); pw.print("dataDir="); pw.println(dataDir);
+        pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded);
+                pw.print(" componentSpecified="); pw.print(componentSpecified);
+                pw.print(" mActivityType="); pw.println(mActivityType);
+        pw.print(prefix); pw.print("compat="); pw.print(compat);
+                pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes));
+                pw.print(" icon=0x"); pw.print(Integer.toHexString(icon));
+                pw.print(" theme=0x"); pw.println(Integer.toHexString(theme));
+        pw.print(prefix); pw.print("config="); pw.println(configuration);
+        if (resultTo != null || resultWho != null) {
+            pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
+                    pw.print(" resultWho="); pw.print(resultWho);
+                    pw.print(" resultCode="); pw.println(requestCode);
+        }
+        if (results != null) {
+            pw.print(prefix); pw.print("results="); pw.println(results);
+        }
+        if (pendingResults != null && pendingResults.size() > 0) {
+            pw.print(prefix); pw.println("Pending Results:");
+            for (WeakReference<PendingIntentRecord> wpir : pendingResults) {
+                PendingIntentRecord pir = wpir != null ? wpir.get() : null;
+                pw.print(prefix); pw.print("  - ");
+                if (pir == null) {
+                    pw.println("null");
+                } else {
+                    pw.println(pir);
+                    pir.dump(pw, prefix + "    ");
+                }
+            }
+        }
+        if (newIntents != null && newIntents.size() > 0) {
+            pw.print(prefix); pw.println("Pending New Intents:");
+            for (int i=0; i<newIntents.size(); i++) {
+                Intent intent = newIntents.get(i);
+                pw.print(prefix); pw.print("  - ");
+                if (intent == null) {
+                    pw.println("null");
+                } else {
+                    pw.println(intent.toShortString(false, true, false, true));
+                }
+            }
+        }
+        if (pendingOptions != null) {
+            pw.print(prefix); pw.print("pendingOptions="); pw.println(pendingOptions);
+        }
+        if (uriPermissions != null) {
+            if (uriPermissions.readUriPermissions != null) {
+                pw.print(prefix); pw.print("readUriPermissions=");
+                        pw.println(uriPermissions.readUriPermissions);
+            }
+            if (uriPermissions.writeUriPermissions != null) {
+                pw.print(prefix); pw.print("writeUriPermissions=");
+                        pw.println(uriPermissions.writeUriPermissions);
+            }
+        }
+        pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
+                pw.print(" launchCount="); pw.print(launchCount);
+                pw.print(" lastLaunchTime=");
+                if (lastLaunchTime == 0) pw.print("0");
+                else TimeUtils.formatDuration(lastLaunchTime, now, pw);
+                pw.println();
+        pw.print(prefix); pw.print("haveState="); pw.print(haveState);
+                pw.print(" icicle="); pw.println(icicle);
+        pw.print(prefix); pw.print("state="); pw.print(state);
+                pw.print(" stopped="); pw.print(stopped);
+                pw.print(" delayedResume="); pw.print(delayedResume);
+                pw.print(" finishing="); pw.println(finishing);
+        pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
+                pw.print(" inHistory="); pw.print(inHistory);
+                pw.print(" visible="); pw.print(visible);
+                pw.print(" sleeping="); pw.print(sleeping);
+                pw.print(" idle="); pw.println(idle);
+        pw.print(prefix); pw.print("fullscreen="); pw.print(fullscreen);
+                pw.print(" noDisplay="); pw.print(noDisplay);
+                pw.print(" immersive="); pw.print(immersive);
+                pw.print(" launchMode="); pw.println(launchMode);
+        pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
+                pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded);
+                pw.print(" forceNewConfig="); pw.println(forceNewConfig);
+        pw.print(prefix); pw.print("mActivityType=");
+                pw.println(activityTypeToString(mActivityType));
+        pw.print(prefix); pw.print("thumbHolder: ");
+                pw.print(Integer.toHexString(System.identityHashCode(thumbHolder)));
+                if (thumbHolder != null) {
+                    pw.print(" bm="); pw.print(thumbHolder.lastThumbnail);
+                    pw.print(" desc="); pw.print(thumbHolder.lastDescription);
+                }
+                pw.println();
+        if (displayStartTime != 0 || startTime != 0) {
+            pw.print(prefix); pw.print("displayStartTime=");
+                    if (displayStartTime == 0) pw.print("0");
+                    else TimeUtils.formatDuration(displayStartTime, now, pw);
+                    pw.print(" startTime=");
+                    if (startTime == 0) pw.print("0");
+                    else TimeUtils.formatDuration(startTime, now, pw);
+                    pw.println();
+        }
+        if (lastVisibleTime != 0 || waitingVisible || nowVisible) {
+            pw.print(prefix); pw.print("waitingVisible="); pw.print(waitingVisible);
+                    pw.print(" nowVisible="); pw.print(nowVisible);
+                    pw.print(" lastVisibleTime=");
+                    if (lastVisibleTime == 0) pw.print("0");
+                    else TimeUtils.formatDuration(lastVisibleTime, now, pw);
+                    pw.println();
+        }
+        if (configDestroy || configChangeFlags != 0) {
+            pw.print(prefix); pw.print("configDestroy="); pw.print(configDestroy);
+                    pw.print(" configChangeFlags=");
+                    pw.println(Integer.toHexString(configChangeFlags));
+        }
+        if (connections != null) {
+            pw.print(prefix); pw.print("connections="); pw.println(connections);
+        }
+    }
+
+    static class Token extends IApplicationToken.Stub {
+        final WeakReference<ActivityRecord> weakActivity;
+
+        Token(ActivityRecord activity) {
+            weakActivity = new WeakReference<ActivityRecord>(activity);
+        }
+
+        @Override public void windowsDrawn() {
+            ActivityRecord activity = weakActivity.get();
+            if (activity != null) {
+                activity.windowsDrawn();
+            }
+        }
+
+        @Override public void windowsVisible() {
+            ActivityRecord activity = weakActivity.get();
+            if (activity != null) {
+                activity.windowsVisible();
+            }
+        }
+
+        @Override public void windowsGone() {
+            ActivityRecord activity = weakActivity.get();
+            if (activity != null) {
+                activity.windowsGone();
+            }
+        }
+
+        @Override public boolean keyDispatchingTimedOut(String reason) {
+            ActivityRecord activity = weakActivity.get();
+            return activity != null && activity.keyDispatchingTimedOut(reason);
+        }
+
+        @Override public long getKeyDispatchingTimeout() {
+            ActivityRecord activity = weakActivity.get();
+            if (activity != null) {
+                return activity.getKeyDispatchingTimeout();
+            }
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Token{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(' ');
+            sb.append(weakActivity.get());
+            sb.append('}');
+            return sb.toString();
+        }
+    }
+
+    static ActivityRecord forToken(IBinder token) {
+        try {
+            return token != null ? ((Token)token).weakActivity.get() : null;
+        } catch (ClassCastException e) {
+            Slog.w(ActivityManagerService.TAG, "Bad activity token: " + token, e);
+            return null;
+        }
+    }
+
+    boolean isNotResolverActivity() {
+        return !ResolverActivity.class.getName().equals(realActivity.getClassName());
+    }
+
+    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
+            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
+            ActivityInfo aInfo, Configuration _configuration,
+            ActivityRecord _resultTo, String _resultWho, int _reqCode,
+            boolean _componentSpecified, ActivityStackSupervisor supervisor) {
+        service = _service;
+        appToken = new Token(this);
+        info = aInfo;
+        launchedFromUid = _launchedFromUid;
+        launchedFromPackage = _launchedFromPackage;
+        userId = UserHandle.getUserId(aInfo.applicationInfo.uid);
+        intent = _intent;
+        shortComponentName = _intent.getComponent().flattenToShortString();
+        resolvedType = _resolvedType;
+        componentSpecified = _componentSpecified;
+        configuration = _configuration;
+        resultTo = _resultTo;
+        resultWho = _resultWho;
+        requestCode = _reqCode;
+        state = ActivityState.INITIALIZING;
+        frontOfTask = false;
+        launchFailed = false;
+        stopped = false;
+        delayedResume = false;
+        finishing = false;
+        configDestroy = false;
+        keysPaused = false;
+        inHistory = false;
+        visible = true;
+        waitingVisible = false;
+        nowVisible = false;
+        thumbnailNeeded = false;
+        idle = false;
+        hasBeenLaunched = false;
+        mStackSupervisor = supervisor;
+
+        // This starts out true, since the initial state of an activity
+        // is that we have everything, and we shouldn't never consider it
+        // lacking in state to be removed if it dies.
+        haveState = true;
+
+        if (aInfo != null) {
+            if (aInfo.targetActivity == null
+                    || aInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE
+                    || aInfo.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
+                realActivity = _intent.getComponent();
+            } else {
+                realActivity = new ComponentName(aInfo.packageName,
+                        aInfo.targetActivity);
+            }
+            taskAffinity = aInfo.taskAffinity;
+            stateNotNeeded = (aInfo.flags&
+                    ActivityInfo.FLAG_STATE_NOT_NEEDED) != 0;
+            baseDir = aInfo.applicationInfo.sourceDir;
+            resDir = aInfo.applicationInfo.publicSourceDir;
+            dataDir = aInfo.applicationInfo.dataDir;
+            nonLocalizedLabel = aInfo.nonLocalizedLabel;
+            labelRes = aInfo.labelRes;
+            if (nonLocalizedLabel == null && labelRes == 0) {
+                ApplicationInfo app = aInfo.applicationInfo;
+                nonLocalizedLabel = app.nonLocalizedLabel;
+                labelRes = app.labelRes;
+            }
+            icon = aInfo.getIconResource();
+            logo = aInfo.getLogoResource();
+            theme = aInfo.getThemeResource();
+            realTheme = theme;
+            if (realTheme == 0) {
+                realTheme = aInfo.applicationInfo.targetSdkVersion
+                        < Build.VERSION_CODES.HONEYCOMB
+                        ? android.R.style.Theme
+                        : android.R.style.Theme_Holo;
+            }
+            if ((aInfo.flags&ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+                windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+            }
+            if ((aInfo.flags&ActivityInfo.FLAG_MULTIPROCESS) != 0
+                    && _caller != null
+                    && (aInfo.applicationInfo.uid == Process.SYSTEM_UID
+                            || aInfo.applicationInfo.uid == _caller.info.uid)) {
+                processName = _caller.processName;
+            } else {
+                processName = aInfo.processName;
+            }
+
+            if (intent != null && (aInfo.flags & ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS) != 0) {
+                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+            }
+
+            packageName = aInfo.applicationInfo.packageName;
+            launchMode = aInfo.launchMode;
+
+            AttributeCache.Entry ent = AttributeCache.instance().get(packageName,
+                    realTheme, com.android.internal.R.styleable.Window, userId);
+            fullscreen = ent != null && !ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsFloating, false)
+                    && !ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
+            noDisplay = ent != null && ent.array.getBoolean(
+                    com.android.internal.R.styleable.Window_windowNoDisplay, false);
+
+            if ((!_componentSpecified || _launchedFromUid == Process.myUid()
+                    || _launchedFromUid == 0) &&
+                    Intent.ACTION_MAIN.equals(_intent.getAction()) &&
+                    _intent.hasCategory(Intent.CATEGORY_HOME) &&
+                    _intent.getCategories().size() == 1 &&
+                    _intent.getData() == null &&
+                    _intent.getType() == null &&
+                    (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
+                    isNotResolverActivity()) {
+                // This sure looks like a home activity!
+                mActivityType = HOME_ACTIVITY_TYPE;
+            } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
+                mActivityType = RECENTS_ACTIVITY_TYPE;
+            } else {
+                mActivityType = APPLICATION_ACTIVITY_TYPE;
+            }
+
+            immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0;
+        } else {
+            realActivity = null;
+            taskAffinity = null;
+            stateNotNeeded = false;
+            baseDir = null;
+            resDir = null;
+            dataDir = null;
+            processName = null;
+            packageName = null;
+            fullscreen = true;
+            noDisplay = false;
+            mActivityType = APPLICATION_ACTIVITY_TYPE;
+            immersive = false;
+        }
+    }
+
+    void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) {
+        if (task != null && task.removeActivity(this)) {
+            if (task != newTask) {
+                mStackSupervisor.removeTask(task);
+            } else {
+                Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" +
+                        (newTask == null ? null : newTask.stack));
+            }
+        }
+        if (inHistory && !finishing) {
+            if (task != null) {
+                task.numActivities--;
+            }
+            if (newTask != null) {
+                newTask.numActivities++;
+            }
+        }
+        if (newThumbHolder == null) {
+            newThumbHolder = newTask;
+        }
+        task = newTask;
+        if (!isRoot && (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+            // This is the start of a new sub-task.
+            if (thumbHolder == null) {
+                thumbHolder = new ThumbnailHolder();
+            }
+        } else {
+            thumbHolder = newThumbHolder;
+        }
+    }
+
+    boolean changeWindowTranslucency(boolean toOpaque) {
+        if (fullscreen == toOpaque) {
+            return false;
+        }
+        AttributeCache.Entry ent =
+                AttributeCache.instance().get(packageName, realTheme, styleable.Window, userId);
+        if (ent == null
+                || !ent.array.getBoolean(styleable.Window_windowIsTranslucent, false)
+                || ent.array.getBoolean(styleable.Window_windowIsFloating, false)) {
+            return false;
+        }
+
+        // Keep track of the number of fullscreen activities in this task.
+        task.numFullscreen += toOpaque ? +1 : -1;
+
+        fullscreen = toOpaque;
+        return true;
+    }
+
+    void putInHistory() {
+        if (!inHistory) {
+            inHistory = true;
+            if (task != null && !finishing) {
+                task.numActivities++;
+            }
+        }
+    }
+
+    void takeFromHistory() {
+        if (inHistory) {
+            inHistory = false;
+            if (task != null && !finishing) {
+                task.numActivities--;
+                task = null;
+            }
+            clearOptionsLocked();
+        }
+    }
+
+    boolean isInHistory() {
+        return inHistory;
+    }
+
+    boolean isHomeActivity() {
+        return mActivityType == HOME_ACTIVITY_TYPE;
+    }
+
+    boolean isRecentsActivity() {
+        return mActivityType == RECENTS_ACTIVITY_TYPE;
+    }
+
+    boolean isApplicationActivity() {
+        return mActivityType == APPLICATION_ACTIVITY_TYPE;
+    }
+
+    void makeFinishing() {
+        if (!finishing) {
+            finishing = true;
+            if (task != null && inHistory) {
+                task.numActivities--;
+            }
+            if (stopped) {
+                clearOptionsLocked();
+            }
+        }
+    }
+
+    boolean isRootActivity() {
+        final ArrayList<ActivityRecord> activities = task.mActivities;
+        return activities.size() == 0 || this == activities.get(0);
+    }
+
+    UriPermissionOwner getUriPermissionsLocked() {
+        if (uriPermissions == null) {
+            uriPermissions = new UriPermissionOwner(service, this);
+        }
+        return uriPermissions;
+    }
+
+    void addResultLocked(ActivityRecord from, String resultWho,
+            int requestCode, int resultCode,
+            Intent resultData) {
+        ActivityResult r = new ActivityResult(from, resultWho,
+        		requestCode, resultCode, resultData);
+        if (results == null) {
+            results = new ArrayList<ResultInfo>();
+        }
+        results.add(r);
+    }
+
+    void removeResultsLocked(ActivityRecord from, String resultWho,
+            int requestCode) {
+        if (results != null) {
+            for (int i=results.size()-1; i>=0; i--) {
+                ActivityResult r = (ActivityResult)results.get(i);
+                if (r.mFrom != from) continue;
+                if (r.mResultWho == null) {
+                    if (resultWho != null) continue;
+                } else {
+                    if (!r.mResultWho.equals(resultWho)) continue;
+                }
+                if (r.mRequestCode != requestCode) continue;
+
+                results.remove(i);
+            }
+        }
+    }
+
+    void addNewIntentLocked(Intent intent) {
+        if (newIntents == null) {
+            newIntents = new ArrayList<Intent>();
+        }
+        newIntents.add(intent);
+    }
+
+    /**
+     * Deliver a new Intent to an existing activity, so that its onNewIntent()
+     * method will be called at the proper time.
+     */
+    final void deliverNewIntentLocked(int callingUid, Intent intent) {
+        // The activity now gets access to the data associated with this Intent.
+        service.grantUriPermissionFromIntentLocked(callingUid, packageName,
+                intent, getUriPermissionsLocked());
+        // We want to immediately deliver the intent to the activity if
+        // it is currently the top resumed activity...  however, if the
+        // device is sleeping, then all activities are stopped, so in that
+        // case we will deliver it if this is the current top activity on its
+        // stack.
+        boolean unsent = true;
+        if ((state == ActivityState.RESUMED || (service.mSleeping
+                        && task.stack.topRunningActivityLocked(null) == this))
+                && app != null && app.thread != null) {
+            try {
+                ArrayList<Intent> ar = new ArrayList<Intent>();
+                intent = new Intent(intent);
+                ar.add(intent);
+                app.thread.scheduleNewIntent(ar, appToken);
+                unsent = false;
+            } catch (RemoteException e) {
+                Slog.w(ActivityManagerService.TAG,
+                        "Exception thrown sending new intent to " + this, e);
+            } catch (NullPointerException e) {
+                Slog.w(ActivityManagerService.TAG,
+                        "Exception thrown sending new intent to " + this, e);
+            }
+        }
+        if (unsent) {
+            addNewIntentLocked(new Intent(intent));
+        }
+    }
+
+    void updateOptionsLocked(Bundle options) {
+        if (options != null) {
+            if (pendingOptions != null) {
+                pendingOptions.abort();
+            }
+            pendingOptions = new ActivityOptions(options);
+        }
+    }
+
+    void updateOptionsLocked(ActivityOptions options) {
+        if (options != null) {
+            if (pendingOptions != null) {
+                pendingOptions.abort();
+            }
+            pendingOptions = options;
+        }
+    }
+
+    void applyOptionsLocked() {
+        if (pendingOptions != null) {
+            final int animationType = pendingOptions.getAnimationType();
+            switch (animationType) {
+                case ActivityOptions.ANIM_CUSTOM:
+                    service.mWindowManager.overridePendingAppTransition(
+                            pendingOptions.getPackageName(),
+                            pendingOptions.getCustomEnterResId(),
+                            pendingOptions.getCustomExitResId(),
+                            pendingOptions.getOnAnimationStartListener());
+                    break;
+                case ActivityOptions.ANIM_SCALE_UP:
+                    service.mWindowManager.overridePendingAppTransitionScaleUp(
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getStartWidth(), pendingOptions.getStartHeight());
+                    if (intent.getSourceBounds() == null) {
+                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                pendingOptions.getStartY(),
+                                pendingOptions.getStartX()+pendingOptions.getStartWidth(),
+                                pendingOptions.getStartY()+pendingOptions.getStartHeight()));
+                    }
+                    break;
+                case ActivityOptions.ANIM_THUMBNAIL_SCALE_UP:
+                case ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN:
+                    boolean scaleUp = (animationType == ActivityOptions.ANIM_THUMBNAIL_SCALE_UP);
+                    service.mWindowManager.overridePendingAppTransitionThumb(
+                            pendingOptions.getThumbnail(),
+                            pendingOptions.getStartX(), pendingOptions.getStartY(),
+                            pendingOptions.getOnAnimationStartListener(),
+                            scaleUp);
+                    if (intent.getSourceBounds() == null) {
+                        intent.setSourceBounds(new Rect(pendingOptions.getStartX(),
+                                pendingOptions.getStartY(),
+                                pendingOptions.getStartX()
+                                        + pendingOptions.getThumbnail().getWidth(),
+                                pendingOptions.getStartY()
+                                        + pendingOptions.getThumbnail().getHeight()));
+                    }
+                    break;
+            }
+            pendingOptions = null;
+        }
+    }
+
+    void clearOptionsLocked() {
+        if (pendingOptions != null) {
+            pendingOptions.abort();
+            pendingOptions = null;
+        }
+    }
+
+    ActivityOptions takeOptionsLocked() {
+        ActivityOptions opts = pendingOptions;
+        pendingOptions = null;
+        return opts;
+    }
+
+    void removeUriPermissionsLocked() {
+        if (uriPermissions != null) {
+            uriPermissions.removeUriPermissionsLocked();
+            uriPermissions = null;
+        }
+    }
+
+    void pauseKeyDispatchingLocked() {
+        if (!keysPaused) {
+            keysPaused = true;
+            service.mWindowManager.pauseKeyDispatching(appToken);
+        }
+    }
+
+    void resumeKeyDispatchingLocked() {
+        if (keysPaused) {
+            keysPaused = false;
+            service.mWindowManager.resumeKeyDispatching(appToken);
+        }
+    }
+
+    void updateThumbnail(Bitmap newThumbnail, CharSequence description) {
+        if (thumbHolder != null) {
+            if (newThumbnail != null) {
+                if (ActivityManagerService.DEBUG_THUMBNAILS) Slog.i(ActivityManagerService.TAG,
+                        "Setting thumbnail of " + this + " holder " + thumbHolder
+                        + " to " + newThumbnail);
+                thumbHolder.lastThumbnail = newThumbnail;
+            }
+            thumbHolder.lastDescription = description;
+        }
+    }
+
+    void startLaunchTickingLocked() {
+        if (ActivityManagerService.IS_USER_BUILD) {
+            return;
+        }
+        if (launchTickTime == 0) {
+            launchTickTime = SystemClock.uptimeMillis();
+            continueLaunchTickingLocked();
+        }
+    }
+
+    boolean continueLaunchTickingLocked() {
+        if (launchTickTime != 0) {
+            final ActivityStack stack = task.stack;
+            Message msg = stack.mHandler.obtainMessage(ActivityStack.LAUNCH_TICK_MSG, this);
+            stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
+            stack.mHandler.sendMessageDelayed(msg, ActivityStack.LAUNCH_TICK);
+            return true;
+        }
+        return false;
+    }
+
+    void finishLaunchTickingLocked() {
+        launchTickTime = 0;
+        task.stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG);
+    }
+
+    // IApplicationToken
+
+    public boolean mayFreezeScreenLocked(ProcessRecord app) {
+        // Only freeze the screen if this activity is currently attached to
+        // an application, and that application is not blocked or unresponding.
+        // In any other case, we can't count on getting the screen unfrozen,
+        // so it is best to leave as-is.
+        return app != null && !app.crashing && !app.notResponding;
+    }
+
+    public void startFreezingScreenLocked(ProcessRecord app, int configChanges) {
+        if (mayFreezeScreenLocked(app)) {
+            service.mWindowManager.startAppFreezingScreen(appToken, configChanges);
+        }
+    }
+
+    public void stopFreezingScreenLocked(boolean force) {
+        if (force || frozenBeforeDestroy) {
+            frozenBeforeDestroy = false;
+            service.mWindowManager.stopAppFreezingScreen(appToken, force);
+        }
+    }
+
+    public void reportFullyDrawnLocked() {
+        final long curTime = SystemClock.uptimeMillis();
+        if (displayStartTime != 0) {
+            reportLaunchTimeLocked(curTime);
+        }
+        if (fullyDrawnStartTime != 0) {
+            final ActivityStack stack = task.stack;
+            final long thisTime = curTime - fullyDrawnStartTime;
+            final long totalTime = stack.mFullyDrawnStartTime != 0
+                    ? (curTime - stack.mFullyDrawnStartTime) : thisTime;
+            if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
+                Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
+                EventLog.writeEvent(EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME,
+                        userId, System.identityHashCode(this), shortComponentName,
+                        thisTime, totalTime);
+                StringBuilder sb = service.mStringBuilder;
+                sb.setLength(0);
+                sb.append("Fully drawn ");
+                sb.append(shortComponentName);
+                sb.append(": ");
+                TimeUtils.formatDuration(thisTime, sb);
+                if (thisTime != totalTime) {
+                    sb.append(" (total ");
+                    TimeUtils.formatDuration(totalTime, sb);
+                    sb.append(")");
+                }
+                Log.i(ActivityManagerService.TAG, sb.toString());
+            }
+            if (totalTime > 0) {
+                service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime);
+            }
+            fullyDrawnStartTime = 0;
+            stack.mFullyDrawnStartTime = 0;
+        }
+    }
+
+    private void reportLaunchTimeLocked(final long curTime) {
+        final ActivityStack stack = task.stack;
+        final long thisTime = curTime - displayStartTime;
+        final long totalTime = stack.mLaunchStartTime != 0
+                ? (curTime - stack.mLaunchStartTime) : thisTime;
+        if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
+            EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME,
+                    userId, System.identityHashCode(this), shortComponentName,
+                    thisTime, totalTime);
+            StringBuilder sb = service.mStringBuilder;
+            sb.setLength(0);
+            sb.append("Displayed ");
+            sb.append(shortComponentName);
+            sb.append(": ");
+            TimeUtils.formatDuration(thisTime, sb);
+            if (thisTime != totalTime) {
+                sb.append(" (total ");
+                TimeUtils.formatDuration(totalTime, sb);
+                sb.append(")");
+            }
+            Log.i(ActivityManagerService.TAG, sb.toString());
+        }
+        mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime);
+        if (totalTime > 0) {
+            service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime);
+        }
+        displayStartTime = 0;
+        stack.mLaunchStartTime = 0;
+    }
+
+    public void windowsDrawn() {
+        synchronized(service) {
+            if (displayStartTime != 0) {
+                reportLaunchTimeLocked(SystemClock.uptimeMillis());
+            }
+            startTime = 0;
+            finishLaunchTickingLocked();
+        }
+    }
+
+    public void windowsVisible() {
+        synchronized(service) {
+            mStackSupervisor.reportActivityVisibleLocked(this);
+            if (ActivityManagerService.DEBUG_SWITCH) Log.v(
+                    ActivityManagerService.TAG, "windowsVisible(): " + this);
+            if (!nowVisible) {
+                nowVisible = true;
+                lastVisibleTime = SystemClock.uptimeMillis();
+                if (!idle) {
+                    // Instead of doing the full stop routine here, let's just
+                    // hide any activities we now can, and let them stop when
+                    // the normal idle happens.
+                    mStackSupervisor.processStoppingActivitiesLocked(false);
+                } else {
+                    // If this activity was already idle, then we now need to
+                    // make sure we perform the full stop of any activities
+                    // that are waiting to do so.  This is because we won't
+                    // do that while they are still waiting for this one to
+                    // become visible.
+                    final int N = mStackSupervisor.mWaitingVisibleActivities.size();
+                    if (N > 0) {
+                        for (int i=0; i<N; i++) {
+                            ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i);
+                            r.waitingVisible = false;
+                            if (ActivityManagerService.DEBUG_SWITCH) Log.v(
+                                    ActivityManagerService.TAG,
+                                    "Was waiting for visible: " + r);
+                        }
+                        mStackSupervisor.mWaitingVisibleActivities.clear();
+                        mStackSupervisor.scheduleIdleLocked();
+                    }
+                }
+                service.scheduleAppGcsLocked();
+            }
+        }
+    }
+
+    public void windowsGone() {
+        if (ActivityManagerService.DEBUG_SWITCH) Log.v(
+                ActivityManagerService.TAG, "windowsGone(): " + this);
+        nowVisible = false;
+    }
+
+    private ActivityRecord getWaitingHistoryRecordLocked() {
+        // First find the real culprit...  if we are waiting
+        // for another app to start, then we have paused dispatching
+        // for this activity.
+        ActivityRecord r = this;
+        final ActivityStack stack = task.stack;
+        if (r.waitingVisible) {
+            // Hmmm, who might we be waiting for?
+            r = stack.mResumedActivity;
+            if (r == null) {
+                r = stack.mPausingActivity;
+            }
+            // Both of those null?  Fall back to 'this' again
+            if (r == null) {
+                r = this;
+            }
+        }
+
+        return r;
+    }
+
+    public boolean keyDispatchingTimedOut(String reason) {
+        ActivityRecord r;
+        ProcessRecord anrApp;
+        synchronized(service) {
+            r = getWaitingHistoryRecordLocked();
+            anrApp = r != null ? r.app : null;
+        }
+        return service.inputDispatchingTimedOut(anrApp, r, this, false, reason);
+    }
+
+    /** Returns the key dispatching timeout for this application token. */
+    public long getKeyDispatchingTimeout() {
+        synchronized(service) {
+            ActivityRecord r = getWaitingHistoryRecordLocked();
+            return ActivityManagerService.getInputDispatchingTimeoutLocked(r);
+        }
+    }
+
+    /**
+     * This method will return true if the activity is either visible, is becoming visible, is
+     * currently pausing, or is resumed.
+     */
+    public boolean isInterestingToUserLocked() {
+        return visible || nowVisible || state == ActivityState.PAUSING ||
+                state == ActivityState.RESUMED;
+    }
+
+    public void setSleeping(boolean _sleeping) {
+        if (sleeping == _sleeping) {
+            return;
+        }
+        if (app != null && app.thread != null) {
+            try {
+                app.thread.scheduleSleeping(appToken, _sleeping);
+                if (_sleeping && !mStackSupervisor.mGoingToSleepActivities.contains(this)) {
+                    mStackSupervisor.mGoingToSleepActivities.add(this);
+                }
+                sleeping = _sleeping;
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Exception thrown when sleeping: " + intent.getComponent(), e);
+            }
+        }
+    }
+
+    static void activityResumedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forToken(token);
+        if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; dropping state of: " + r);
+        r.icicle = null;
+        r.haveState = false;
+    }
+
+    static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
+        final ActivityRecord r = ActivityRecord.forToken(token);
+        if (r == null) {
+            return -1;
+        }
+        final TaskRecord task = r.task;
+        switch (task.mActivities.indexOf(r)) {
+            case -1: return -1;
+            case 0: return task.taskId;
+            default: return onlyRoot ? -1 : task.taskId;
+        }
+    }
+
+    static ActivityRecord isInStackLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forToken(token);
+        if (r != null) {
+            return r.task.stack.isInStackLocked(token);
+        }
+        return null;
+    }
+
+    static ActivityStack getStackLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.isInStackLocked(token);
+        if (r != null) {
+            return r.task.stack;
+        }
+        return null;
+    }
+
+    private String activityTypeToString(int type) {
+        switch (type) {
+            case APPLICATION_ACTIVITY_TYPE: return "APPLICATION_ACTIVITY_TYPE";
+            case HOME_ACTIVITY_TYPE: return "HOME_ACTIVITY_TYPE";
+            case RECENTS_ACTIVITY_TYPE: return "RECENTS_ACTIVITY_TYPE";
+            default: return Integer.toString(type);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName != null) {
+            return stringName + " t" + (task == null ? -1 : task.taskId) +
+                    (finishing ? " f}" : "}");
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ActivityRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" u");
+        sb.append(userId);
+        sb.append(' ');
+        sb.append(intent.getComponent().flattenToShortString());
+        stringName = sb.toString();
+        return toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityResult.java b/services/core/java/com/android/server/am/ActivityResult.java
new file mode 100644
index 0000000..6d5bdeb
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityResult.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.ResultInfo;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Pending result information to send back to an activity.
+ */
+final class ActivityResult extends ResultInfo {
+    final ActivityRecord mFrom;
+    
+    public ActivityResult(ActivityRecord from, String resultWho,
+            int requestCode, int resultCode, Intent data) {
+        super(resultWho, requestCode, resultCode, data);
+        mFrom = from;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
new file mode 100644
index 0000000..8faee556
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -0,0 +1,3615 @@
+/*
+ * Copyright (C) 2010 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.am;
+
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityManagerService.localLOGV;
+import static com.android.server.am.ActivityManagerService.DEBUG_CLEANUP;
+import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE;
+import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS;
+import static com.android.server.am.ActivityManagerService.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerService.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerService.DEBUG_TRANSITION;
+import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY;
+import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS;
+
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.Watchdog;
+import com.android.server.am.ActivityManagerService.ItemMatcher;
+import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
+import com.android.server.wm.AppTransition;
+import com.android.server.wm.TaskGroup;
+import com.android.server.wm.WindowManagerService;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
+import android.app.IActivityController;
+import android.app.IThumbnailReceiver;
+import android.app.ResultInfo;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Slog;
+import android.view.Display;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * State and management of a single stack of activities.
+ */
+final class ActivityStack {
+
+    // Ticks during which we check progress while waiting for an app to launch.
+    static final int LAUNCH_TICK = 500;
+
+    // How long we wait until giving up on the last activity to pause.  This
+    // is short because it directly impacts the responsiveness of starting the
+    // next activity.
+    static final int PAUSE_TIMEOUT = 500;
+
+    // How long we wait for the activity to tell us it has stopped before
+    // giving up.  This is a good amount of time because we really need this
+    // from the application in order to get its saved state.
+    static final int STOP_TIMEOUT = 10*1000;
+
+    // How long we wait until giving up on an activity telling us it has
+    // finished destroying itself.
+    static final int DESTROY_TIMEOUT = 10*1000;
+
+    // How long until we reset a task when the user returns to it.  Currently
+    // disabled.
+    static final long ACTIVITY_INACTIVE_RESET_TIME = 0;
+
+    // How long between activity launches that we consider safe to not warn
+    // the user about an unexpected activity being launched on top.
+    static final long START_WARN_TIME = 5*1000;
+
+    // Set to false to disable the preview that is shown while a new activity
+    // is being started.
+    static final boolean SHOW_APP_STARTING_PREVIEW = true;
+
+    // How long to wait for all background Activities to redraw following a call to
+    // convertToTranslucent().
+    static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000;
+
+    static final boolean SCREENSHOT_FORCE_565 = ActivityManager
+            .isLowRamDeviceStatic() ? true : false;
+
+    enum ActivityState {
+        INITIALIZING,
+        RESUMED,
+        PAUSING,
+        PAUSED,
+        STOPPING,
+        STOPPED,
+        FINISHING,
+        DESTROYING,
+        DESTROYED
+    }
+
+    final ActivityManagerService mService;
+    final WindowManagerService mWindowManager;
+
+    /**
+     * The back history of all previous (and possibly still
+     * running) activities.  It contains #TaskRecord objects.
+     */
+    private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>();
+
+    /**
+     * Used for validating app tokens with window manager.
+     */
+    final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>();
+
+    /**
+     * List of running activities, sorted by recent usage.
+     * The first entry in the list is the least recently used.
+     * It contains HistoryRecord objects.
+     */
+    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
+
+    /**
+     * Animations that for the current transition have requested not to
+     * be considered for the transition animation.
+     */
+    final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>();
+
+    /**
+     * When we are in the process of pausing an activity, before starting the
+     * next one, this variable holds the activity that is currently being paused.
+     */
+    ActivityRecord mPausingActivity = null;
+
+    /**
+     * This is the last activity that we put into the paused state.  This is
+     * used to determine if we need to do an activity transition while sleeping,
+     * when we normally hold the top activity paused.
+     */
+    ActivityRecord mLastPausedActivity = null;
+
+    /**
+     * Activities that specify No History must be removed once the user navigates away from them.
+     * If the device goes to sleep with such an activity in the paused state then we save it here
+     * and finish it later if another activity replaces it on wakeup.
+     */
+    ActivityRecord mLastNoHistoryActivity = null;
+
+    /**
+     * Current activity that is resumed, or null if there is none.
+     */
+    ActivityRecord mResumedActivity = null;
+
+    /**
+     * This is the last activity that has been started.  It is only used to
+     * identify when multiple activities are started at once so that the user
+     * can be warned they may not be in the activity they think they are.
+     */
+    ActivityRecord mLastStartedActivity = null;
+
+    // The topmost Activity passed to convertToTranslucent(). When non-null it means we are
+    // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they
+    // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the
+    // Activity in mTranslucentActivityWaiting is notified via
+    // Activity.onTranslucentConversionComplete(false). If a timeout occurs prior to the last
+    // background activity being drawn then the same call will be made with a true value.
+    ActivityRecord mTranslucentActivityWaiting = null;
+    ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent =
+            new ArrayList<ActivityRecord>();
+
+    /**
+     * Set when we know we are going to be calling updateConfiguration()
+     * soon, so want to skip intermediate config checks.
+     */
+    boolean mConfigWillChange;
+
+    long mLaunchStartTime = 0;
+    long mFullyDrawnStartTime = 0;
+
+    /**
+     * Save the most recent screenshot for reuse. This keeps Recents from taking two identical
+     * screenshots, one for the Recents thumbnail and one for the pauseActivity thumbnail.
+     */
+    private ActivityRecord mLastScreenshotActivity = null;
+    private Bitmap mLastScreenshotBitmap = null;
+
+    int mThumbnailWidth = -1;
+    int mThumbnailHeight = -1;
+
+    int mCurrentUser;
+
+    final int mStackId;
+
+    final ActivityContainer mActivityContainer;
+
+    /** Run all ActivityStacks through this */
+    final ActivityStackSupervisor mStackSupervisor;
+
+    static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1;
+    static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2;
+    static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3;
+    static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 4;
+    static final int DESTROY_ACTIVITIES_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5;
+    static final int TRANSLUCENT_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6;
+
+    static class ScheduleDestroyArgs {
+        final ProcessRecord mOwner;
+        final boolean mOomAdj;
+        final String mReason;
+        ScheduleDestroyArgs(ProcessRecord owner, boolean oomAdj, String reason) {
+            mOwner = owner;
+            mOomAdj = oomAdj;
+            mReason = reason;
+        }
+    }
+
+    final Handler mHandler;
+
+    final class ActivityStackHandler extends Handler {
+        //public Handler() {
+        //    if (localLOGV) Slog.v(TAG, "Handler started!");
+        //}
+        ActivityStackHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PAUSE_TIMEOUT_MSG: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    // We don't at this point know if the activity is fullscreen,
+                    // so we need to be conservative and assume it isn't.
+                    Slog.w(TAG, "Activity pause timeout for " + r);
+                    synchronized (mService) {
+                        if (r.app != null) {
+                            mService.logAppTooSlow(r.app, r.pauseTime, "pausing " + r);
+                        }
+                        activityPausedLocked(r.appToken, true);
+                    }
+                } break;
+                case LAUNCH_TICK_MSG: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    synchronized (mService) {
+                        if (r.continueLaunchTickingLocked()) {
+                            mService.logAppTooSlow(r.app, r.launchTickTime, "launching " + r);
+                        }
+                    }
+                } break;
+                case DESTROY_TIMEOUT_MSG: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    // We don't at this point know if the activity is fullscreen,
+                    // so we need to be conservative and assume it isn't.
+                    Slog.w(TAG, "Activity destroy timeout for " + r);
+                    synchronized (mService) {
+                        activityDestroyedLocked(r != null ? r.appToken : null);
+                    }
+                } break;
+                case STOP_TIMEOUT_MSG: {
+                    ActivityRecord r = (ActivityRecord)msg.obj;
+                    // We don't at this point know if the activity is fullscreen,
+                    // so we need to be conservative and assume it isn't.
+                    Slog.w(TAG, "Activity stop timeout for " + r);
+                    synchronized (mService) {
+                        if (r.isInHistory()) {
+                            activityStoppedLocked(r, null, null, null);
+                        }
+                    }
+                } break;
+                case DESTROY_ACTIVITIES_MSG: {
+                    ScheduleDestroyArgs args = (ScheduleDestroyArgs)msg.obj;
+                    synchronized (mService) {
+                        destroyActivitiesLocked(args.mOwner, args.mOomAdj, args.mReason);
+                    }
+                } break;
+                case TRANSLUCENT_TIMEOUT_MSG: {
+                    synchronized (mService) {
+                        notifyActivityDrawnLocked(null);
+                    }
+                } break;
+            }
+        }
+    }
+
+    private int numActivities() {
+        int count = 0;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            count += mTaskHistory.get(taskNdx).mActivities.size();
+        }
+        return count;
+    }
+
+    ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer) {
+        mActivityContainer = activityContainer;
+        mStackSupervisor = activityContainer.getOuter();
+        mService = mStackSupervisor.mService;
+        mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
+        mWindowManager = mService.mWindowManager;
+        mStackId = activityContainer.mStackId;
+        mCurrentUser = mService.mCurrentUserId;
+    }
+
+    boolean okToShow(ActivityRecord r) {
+        return r.userId == mCurrentUser
+                || (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
+    }
+
+    final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked(notTop);
+            if (r != null) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = activities.get(activityNdx);
+                if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This is a simplified version of topRunningActivityLocked that provides a number of
+     * optional skip-over modes.  It is intended for use with the ActivityController hook only.
+     *
+     * @param token If non-null, any history records matching this token will be skipped.
+     * @param taskId If non-zero, we'll attempt to skip over records with the same task ID.
+     *
+     * @return Returns the HistoryRecord of the next activity on the stack.
+     */
+    final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.taskId == taskId) {
+                continue;
+            }
+            ArrayList<ActivityRecord> activities = task.mActivities;
+            for (int i = activities.size() - 1; i >= 0; --i) {
+                final ActivityRecord r = activities.get(i);
+                // Note: the taskId check depends on real taskId fields being non-zero
+                if (!r.finishing && (token != r.appToken) && okToShow(r)) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    final ActivityRecord topActivity() {
+        // Iterate to find the first non-empty task stack. Note that this code can
+        // be simplified once we stop storing tasks with empty mActivities lists.
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                return activities.get(activityNdx);
+            }
+        }
+        return null;
+    }
+
+    final TaskRecord topTask() {
+        final int size = mTaskHistory.size();
+        if (size > 0) {
+            return mTaskHistory.get(size - 1);
+        }
+        return null;
+    }
+
+    TaskRecord taskForIdLocked(int id) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.taskId == id) {
+                return task;
+            }
+        }
+        return null;
+    }
+
+    ActivityRecord isInStackLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forToken(token);
+        if (r != null) {
+            final TaskRecord task = r.task;
+            if (task.mActivities.contains(r) && mTaskHistory.contains(task)) {
+                if (task.stack != this) Slog.w(TAG,
+                    "Illegal state! task does not point to stack it is in.");
+                return r;
+            }
+        }
+        return null;
+    }
+
+    final boolean updateLRUListLocked(ActivityRecord r) {
+        final boolean hadit = mLRUActivities.remove(r);
+        mLRUActivities.add(r);
+        return hadit;
+    }
+
+    final boolean isHomeStack() {
+        return mStackId == HOME_STACK_ID;
+    }
+
+    ArrayList<ActivityStack> getStacksLocked() {
+        if (mActivityContainer.isAttached()) {
+            return mActivityContainer.mActivityDisplayInfo.stacks;
+        }
+        return null;
+    }
+
+    /**
+     * Returns the top activity in any existing task matching the given
+     * Intent.  Returns null if no such task is found.
+     */
+    ActivityRecord findTaskLocked(ActivityRecord target) {
+        Intent intent = target.intent;
+        ActivityInfo info = target.info;
+        ComponentName cls = intent.getComponent();
+        if (info.targetActivity != null) {
+            cls = new ComponentName(info.packageName, info.targetActivity);
+        }
+        final int userId = UserHandle.getUserId(info.applicationInfo.uid);
+
+        if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.userId != userId) {
+                // Looking for a different task.
+                if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user");
+                continue;
+            }
+            final ActivityRecord r = task.getTopActivity();
+            if (r == null || r.finishing || r.userId != userId ||
+                    r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": mismatch root " + r);
+                continue;
+            }
+
+            if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls="
+                    + r.task.intent.getComponent().flattenToShortString()
+                    + "/aff=" + r.task.affinity + " to new cls="
+                    + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
+            if (task.affinity != null) {
+                if (task.affinity.equals(info.taskAffinity)) {
+                    if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
+                    return r;
+                }
+            } else if (task.intent != null && task.intent.getComponent().equals(cls)) {
+                if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
+                //dump();
+                if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
+                        + r.intent);
+                return r;
+            } else if (task.affinityIntent != null
+                    && task.affinityIntent.getComponent().equals(cls)) {
+                if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
+                //dump();
+                if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
+                        + r.intent);
+                return r;
+            } else if (DEBUG_TASKS) {
+                Slog.d(TAG, "Not a match: " + task);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the first activity (starting from the top of the stack) that
+     * is the same as the given activity.  Returns null if no such activity
+     * is found.
+     */
+    ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) {
+        ComponentName cls = intent.getComponent();
+        if (info.targetActivity != null) {
+            cls = new ComponentName(info.packageName, info.targetActivity);
+        }
+        final int userId = UserHandle.getUserId(info.applicationInfo.uid);
+
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.userId != mCurrentUser) {
+                return null;
+            }
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = activities.get(activityNdx);
+                if (!r.finishing && r.intent.getComponent().equals(cls) && r.userId == userId) {
+                    //Slog.i(TAG, "Found matching class!");
+                    //dump();
+                    //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent);
+                    return r;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /*
+     * Move the activities around in the stack to bring a user to the foreground.
+     */
+    final void switchUserLocked(int userId) {
+        if (mCurrentUser == userId) {
+            return;
+        }
+        mCurrentUser = userId;
+
+        // Move userId's tasks to the top.
+        int index = mTaskHistory.size();
+        for (int i = 0; i < index; ) {
+            TaskRecord task = mTaskHistory.get(i);
+            if (task.userId == userId) {
+                if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() +
+                        " moving " + task + " to top");
+                mTaskHistory.remove(i);
+                mTaskHistory.add(task);
+                --index;
+                // Use same value for i.
+            } else {
+                ++i;
+            }
+        }
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
+    }
+
+    void minimalResumeActivityLocked(ActivityRecord r) {
+        r.state = ActivityState.RESUMED;
+        if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + r
+                + " (starting new instance)");
+        r.stopped = false;
+        mResumedActivity = r;
+        r.task.touchActiveTime();
+        mService.addRecentTaskLocked(r.task);
+        completeResumeLocked(r);
+        mStackSupervisor.checkReadyForSleepLocked();
+        setLaunchTime(r);
+        if (DEBUG_SAVED_STATE) Slog.i(TAG, "Launch completed; removing icicle of " + r.icicle);
+    }
+
+    private void startLaunchTraces() {
+        if (mFullyDrawnStartTime != 0)  {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
+        }
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0);
+        Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
+    }
+
+    private void stopFullyDrawnTraceIfNeeded() {
+        if (mFullyDrawnStartTime != 0 && mLaunchStartTime == 0) {
+            Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
+            mFullyDrawnStartTime = 0;
+        }
+    }
+
+    void setLaunchTime(ActivityRecord r) {
+        if (r.displayStartTime == 0) {
+            r.fullyDrawnStartTime = r.displayStartTime = SystemClock.uptimeMillis();
+            if (mLaunchStartTime == 0) {
+                startLaunchTraces();
+                mLaunchStartTime = mFullyDrawnStartTime = r.displayStartTime;
+            }
+        } else if (mLaunchStartTime == 0) {
+            startLaunchTraces();
+            mLaunchStartTime = mFullyDrawnStartTime = SystemClock.uptimeMillis();
+        }
+    }
+
+    void clearLaunchTime(ActivityRecord r) {
+        // Make sure that there is no activity waiting for this to launch.
+        if (mStackSupervisor.mWaitingActivityLaunched.isEmpty()) {
+            r.displayStartTime = r.fullyDrawnStartTime = 0;
+        } else {
+            mStackSupervisor.removeTimeoutsForActivityLocked(r);
+            mStackSupervisor.scheduleIdleTimeoutLocked(r);
+        }
+    }
+
+    void awakeFromSleepingLocked() {
+        // Ensure activities are no longer sleeping.
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                activities.get(activityNdx).setSleeping(false);
+            }
+        }
+    }
+
+    /**
+     * @return true if something must be done before going to sleep.
+     */
+    boolean checkReadyForSleepLocked() {
+        if (mResumedActivity != null) {
+            // Still have something resumed; can't sleep until it is paused.
+            if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity);
+            if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false");
+            startPausingLocked(false, true);
+            return true;
+        }
+        if (mPausingActivity != null) {
+            // Still waiting for something to pause; can't sleep yet.
+            if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity);
+            return true;
+        }
+
+        return false;
+    }
+
+    void goToSleep() {
+        ensureActivitiesVisibleLocked(null, 0);
+
+        // Make sure any stopped but visible activities are now sleeping.
+        // This ensures that the activity's onStop() is called.
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) {
+                    r.setSleeping(true);
+                }
+            }
+        }
+    }
+
+    public final Bitmap screenshotActivities(ActivityRecord who) {
+        if (who.noDisplay) {
+            return null;
+        }
+
+        TaskRecord tr = who.task;
+        if (mService.getMostRecentTask() != tr && tr.intent != null &&
+                (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) {
+            // If this task is being excluded from recents, we don't want to take
+            // the expense of capturing a thumbnail, since we will never show it.
+            return null;
+        }
+
+        Resources res = mService.mContext.getResources();
+        int w = mThumbnailWidth;
+        int h = mThumbnailHeight;
+        if (w < 0) {
+            mThumbnailWidth = w =
+                res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+            mThumbnailHeight = h =
+                res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+        }
+
+        if (w > 0) {
+            if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null
+                    || mLastScreenshotActivity.state == ActivityState.RESUMED
+                    || mLastScreenshotBitmap.getWidth() != w
+                    || mLastScreenshotBitmap.getHeight() != h) {
+                mLastScreenshotActivity = who;
+                mLastScreenshotBitmap = mWindowManager.screenshotApplications(
+                        who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565);
+            }
+            if (mLastScreenshotBitmap != null) {
+                return mLastScreenshotBitmap.copy(mLastScreenshotBitmap.getConfig(), true);
+            }
+        }
+        return null;
+    }
+
+    final void startPausingLocked(boolean userLeaving, boolean uiSleeping) {
+        if (mPausingActivity != null) {
+            Slog.e(TAG, "Trying to pause when pause is already pending for "
+                  + mPausingActivity, new RuntimeException("here").fillInStackTrace());
+        }
+        ActivityRecord prev = mResumedActivity;
+        if (prev == null) {
+            Slog.e(TAG, "Trying to pause when nothing is resumed",
+                    new RuntimeException("here").fillInStackTrace());
+            mStackSupervisor.resumeTopActivitiesLocked();
+            return;
+        }
+        if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev);
+        else if (DEBUG_PAUSE) Slog.v(TAG, "Start pausing: " + prev);
+        mResumedActivity = null;
+        mPausingActivity = prev;
+        mLastPausedActivity = prev;
+        mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
+                || (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null;
+        prev.state = ActivityState.PAUSING;
+        prev.task.touchActiveTime();
+        clearLaunchTime(prev);
+        final ActivityRecord next = mStackSupervisor.topRunningActivityLocked();
+        if (next == null || next.task != prev.task) {
+            prev.updateThumbnail(screenshotActivities(prev), null);
+        }
+        stopFullyDrawnTraceIfNeeded();
+
+        mService.updateCpuStats();
+
+        if (prev.app != null && prev.app.thread != null) {
+            if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev);
+            try {
+                EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY,
+                        prev.userId, System.identityHashCode(prev),
+                        prev.shortComponentName);
+                mService.updateUsageStats(prev, false);
+                prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
+                        userLeaving, prev.configChangeFlags);
+            } catch (Exception e) {
+                // Ignore exception, if process died other code will cleanup.
+                Slog.w(TAG, "Exception thrown during pause", e);
+                mPausingActivity = null;
+                mLastPausedActivity = null;
+                mLastNoHistoryActivity = null;
+            }
+        } else {
+            mPausingActivity = null;
+            mLastPausedActivity = null;
+            mLastNoHistoryActivity = null;
+        }
+
+        // If we are not going to sleep, we want to ensure the device is
+        // awake until the next activity is started.
+        if (!mService.isSleepingOrShuttingDown()) {
+            mStackSupervisor.acquireLaunchWakelock();
+        }
+
+        if (mPausingActivity != null) {
+            // Have the window manager pause its key dispatching until the new
+            // activity has started.  If we're pausing the activity just because
+            // the screen is being turned off and the UI is sleeping, don't interrupt
+            // key dispatch; the same activity will pick it up again on wakeup.
+            if (!uiSleeping) {
+                prev.pauseKeyDispatchingLocked();
+            } else {
+                if (DEBUG_PAUSE) Slog.v(TAG, "Key dispatch not paused for screen off");
+            }
+
+            // Schedule a pause timeout in case the app doesn't respond.
+            // We don't give it much time because this directly impacts the
+            // responsiveness seen by the user.
+            Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);
+            msg.obj = prev;
+            prev.pauseTime = SystemClock.uptimeMillis();
+            mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
+            if (DEBUG_PAUSE) Slog.v(TAG, "Waiting for pause to complete...");
+        } else {
+            // This activity failed to schedule the
+            // pause, so just treat it as being paused now.
+            if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next.");
+            mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
+        }
+    }
+
+    final void activityPausedLocked(IBinder token, boolean timeout) {
+        if (DEBUG_PAUSE) Slog.v(
+            TAG, "Activity paused: token=" + token + ", timeout=" + timeout);
+
+        final ActivityRecord r = isInStackLocked(token);
+        if (r != null) {
+            mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+            if (mPausingActivity == r) {
+                if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r
+                        + (timeout ? " (due to timeout)" : " (pause complete)"));
+                r.state = ActivityState.PAUSED;
+                completePauseLocked();
+            } else {
+                EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE,
+                        r.userId, System.identityHashCode(r), r.shortComponentName,
+                        mPausingActivity != null
+                            ? mPausingActivity.shortComponentName : "(none)");
+            }
+        }
+    }
+
+    final void activityStoppedLocked(ActivityRecord r, Bundle icicle, Bitmap thumbnail,
+            CharSequence description) {
+        if (r.state != ActivityState.STOPPING) {
+            Slog.i(TAG, "Activity reported stop, but no longer stopping: " + r);
+            mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+            return;
+        }
+        if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle);
+        if (icicle != null) {
+            // If icicle is null, this is happening due to a timeout, so we
+            // haven't really saved the state.
+            r.icicle = icicle;
+            r.haveState = true;
+            r.launchCount = 0;
+            r.updateThumbnail(thumbnail, description);
+        }
+        if (!r.stopped) {
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + " (stop complete)");
+            mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+            r.stopped = true;
+            r.state = ActivityState.STOPPED;
+            if (r.finishing) {
+                r.clearOptionsLocked();
+            } else {
+                if (r.configDestroy) {
+                    destroyActivityLocked(r, true, false, "stop-config");
+                    mStackSupervisor.resumeTopActivitiesLocked();
+                } else {
+                    mStackSupervisor.updatePreviousProcessLocked(r);
+                }
+            }
+        }
+    }
+
+    private void completePauseLocked() {
+        ActivityRecord prev = mPausingActivity;
+        if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev);
+
+        if (prev != null) {
+            if (prev.finishing) {
+                if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev);
+                prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
+            } else if (prev.app != null) {
+                if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev);
+                if (prev.waitingVisible) {
+                    prev.waitingVisible = false;
+                    mStackSupervisor.mWaitingVisibleActivities.remove(prev);
+                    if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(
+                            TAG, "Complete pause, no longer waiting: " + prev);
+                }
+                if (prev.configDestroy) {
+                    // The previous is being paused because the configuration
+                    // is changing, which means it is actually stopping...
+                    // To juggle the fact that we are also starting a new
+                    // instance right now, we need to first completely stop
+                    // the current instance before starting the new one.
+                    if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev);
+                    destroyActivityLocked(prev, true, false, "pause-config");
+                } else {
+                    mStackSupervisor.mStoppingActivities.add(prev);
+                    if (mStackSupervisor.mStoppingActivities.size() > 3 ||
+                            prev.frontOfTask && mTaskHistory.size() <= 1) {
+                        // If we already have a few activities waiting to stop,
+                        // then give up on things going idle and start clearing
+                        // them out. Or if r is the last of activity of the last task the stack
+                        // will be empty and must be cleared immediately.
+                        if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle");
+                        mStackSupervisor.scheduleIdleLocked();
+                    } else {
+                        mStackSupervisor.checkReadyForSleepLocked();
+                    }
+                }
+            } else {
+                if (DEBUG_PAUSE) Slog.v(TAG, "App died during pause, not stopping: " + prev);
+                prev = null;
+            }
+            mPausingActivity = null;
+        }
+
+        final ActivityStack topStack = mStackSupervisor.getFocusedStack();
+        if (!mService.isSleepingOrShuttingDown()) {
+            mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null);
+        } else {
+            mStackSupervisor.checkReadyForSleepLocked();
+            ActivityRecord top = topStack.topRunningActivityLocked(null);
+            if (top == null || (prev != null && top != prev)) {
+                // If there are no more activities available to run,
+                // do resume anyway to start something.  Also if the top
+                // activity on the stack is not the just paused activity,
+                // we need to go ahead and resume it to ensure we complete
+                // an in-flight app switch.
+                mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null);
+            }
+        }
+
+        if (prev != null) {
+            prev.resumeKeyDispatchingLocked();
+
+            if (prev.app != null && prev.cpuTimeAtResume > 0
+                    && mService.mBatteryStatsService.isOnBattery()) {
+                long diff;
+                synchronized (mService.mProcessCpuThread) {
+                    diff = mService.mProcessCpuTracker.getCpuTimeForPid(prev.app.pid)
+                            - prev.cpuTimeAtResume;
+                }
+                if (diff > 0) {
+                    BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics();
+                    synchronized (bsi) {
+                        BatteryStatsImpl.Uid.Proc ps =
+                                bsi.getProcessStatsLocked(prev.info.applicationInfo.uid,
+                                        prev.info.packageName);
+                        if (ps != null) {
+                            ps.addForegroundTimeLocked(diff);
+                        }
+                    }
+                }
+            }
+            prev.cpuTimeAtResume = 0; // reset it
+        }
+    }
+
+    /**
+     * Once we know that we have asked an application to put an activity in
+     * the resumed state (either by launching it or explicitly telling it),
+     * this function updates the rest of our state to match that fact.
+     */
+    private void completeResumeLocked(ActivityRecord next) {
+        next.idle = false;
+        next.results = null;
+        next.newIntents = null;
+        if (next.nowVisible) {
+            // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now.
+            mStackSupervisor.dismissKeyguard();
+        }
+
+        // schedule an idle timeout in case the app doesn't do it for us.
+        mStackSupervisor.scheduleIdleTimeoutLocked(next);
+
+        mStackSupervisor.reportResumedActivityLocked(next);
+
+        next.resumeKeyDispatchingLocked();
+        mNoAnimActivities.clear();
+
+        // Mark the point when the activity is resuming
+        // TODO: To be more accurate, the mark should be before the onCreate,
+        //       not after the onResume. But for subsequent starts, onResume is fine.
+        if (next.app != null) {
+            synchronized (mService.mProcessCpuThread) {
+                next.cpuTimeAtResume = mService.mProcessCpuTracker.getCpuTimeForPid(next.app.pid);
+            }
+        } else {
+            next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process
+        }
+    }
+
+    /**
+     * Determine if home should be visible below the passed record.
+     * @param record activity we are querying for.
+     * @return true if home is visible below the passed activity, false otherwise.
+     */
+    boolean isActivityOverHome(ActivityRecord record) {
+        // Start at record and go down, look for either home or a visible fullscreen activity.
+        final TaskRecord recordTask = record.task;
+        for (int taskNdx = mTaskHistory.indexOf(recordTask); taskNdx >= 0; --taskNdx) {
+            TaskRecord task = mTaskHistory.get(taskNdx);
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            final int startNdx =
+                    task == recordTask ? activities.indexOf(record) : activities.size() - 1;
+            for (int activityNdx = startNdx; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.isHomeActivity()) {
+                    return true;
+                }
+                if (!r.finishing && r.fullscreen) {
+                    // Passed activity is over a fullscreen activity.
+                    return false;
+                }
+            }
+            if (task.mOnTopOfHome) {
+                // Got to the bottom of a task on top of home without finding a visible fullscreen
+                // activity. Home is visible.
+                return true;
+            }
+        }
+        // Got to the bottom of this stack and still don't know. If this is over the home stack
+        // then record is over home. May not work if we ever get more than two layers.
+        return mStackSupervisor.isFrontStack(this);
+    }
+
+    /**
+     * Version of ensureActivitiesVisible that can easily be called anywhere.
+     */
+    final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
+        return ensureActivitiesVisibleLocked(starting, configChanges, false);
+    }
+
+    final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges,
+            boolean forceHomeShown) {
+        ActivityRecord r = topRunningActivityLocked(null);
+        return r != null &&
+                ensureActivitiesVisibleLocked(r, starting, null, configChanges, forceHomeShown);
+    }
+
+    /**
+     * Make sure that all activities that need to be visible (that is, they
+     * currently can be seen by the user) actually are.
+     */
+    final boolean ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting,
+            String onlyThisProcess, int configChanges, boolean forceHomeShown) {
+        if (DEBUG_VISBILITY) Slog.v(
+                TAG, "ensureActivitiesVisible behind " + top
+                + " configChanges=0x" + Integer.toHexString(configChanges));
+
+        if (mTranslucentActivityWaiting != top) {
+            mUndrawnActivitiesBelowTopTranslucent.clear();
+            if (mTranslucentActivityWaiting != null) {
+                // Call the callback with a timeout indication.
+                notifyActivityDrawnLocked(null);
+                mTranslucentActivityWaiting = null;
+            }
+            mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+        }
+
+        // If the top activity is not fullscreen, then we need to
+        // make sure any activities under it are now visible.
+        boolean aboveTop = true;
+        boolean showHomeBehindStack = false;
+        boolean behindFullscreen = !mStackSupervisor.isFrontStack(this) &&
+                !(forceHomeShown && isHomeStack());
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.finishing) {
+                    continue;
+                }
+                if (aboveTop && r != top) {
+                    continue;
+                }
+                aboveTop = false;
+                if (!behindFullscreen) {
+                    if (DEBUG_VISBILITY) Slog.v(
+                            TAG, "Make visible? " + r + " finishing=" + r.finishing
+                            + " state=" + r.state);
+
+                    final boolean doThisProcess = onlyThisProcess == null
+                            || onlyThisProcess.equals(r.processName);
+
+                    // First: if this is not the current activity being started, make
+                    // sure it matches the current configuration.
+                    if (r != starting && doThisProcess) {
+                        ensureActivityConfigurationLocked(r, 0);
+                    }
+
+                    if (r.app == null || r.app.thread == null) {
+                        if (onlyThisProcess == null || onlyThisProcess.equals(r.processName)) {
+                            // This activity needs to be visible, but isn't even
+                            // running...  get it started, but don't resume it
+                            // at this point.
+                            if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r);
+                            if (r != starting) {
+                                r.startFreezingScreenLocked(r.app, configChanges);
+                            }
+                            if (!r.visible) {
+                                if (DEBUG_VISBILITY) Slog.v(
+                                        TAG, "Starting and making visible: " + r);
+                                mWindowManager.setAppVisibility(r.appToken, true);
+                            }
+                            if (r != starting) {
+                                mStackSupervisor.startSpecificActivityLocked(r, false, false);
+                            }
+                        }
+
+                    } else if (r.visible) {
+                        // If this activity is already visible, then there is nothing
+                        // else to do here.
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r);
+                        r.stopFreezingScreenLocked(false);
+
+                    } else if (onlyThisProcess == null) {
+                        // This activity is not currently visible, but is running.
+                        // Tell it to become visible.
+                        r.visible = true;
+                        if (r.state != ActivityState.RESUMED && r != starting) {
+                            // If this activity is paused, tell it
+                            // to now show its window.
+                            if (DEBUG_VISBILITY) Slog.v(
+                                    TAG, "Making visible and scheduling visibility: " + r);
+                            try {
+                                if (mTranslucentActivityWaiting != null) {
+                                    mUndrawnActivitiesBelowTopTranslucent.add(r);
+                                }
+                                mWindowManager.setAppVisibility(r.appToken, true);
+                                r.sleeping = false;
+                                r.app.pendingUiClean = true;
+                                r.app.thread.scheduleWindowVisibility(r.appToken, true);
+                                r.stopFreezingScreenLocked(false);
+                            } catch (Exception e) {
+                                // Just skip on any failure; we'll make it
+                                // visible when it next restarts.
+                                Slog.w(TAG, "Exception thrown making visibile: "
+                                        + r.intent.getComponent(), e);
+                            }
+                        }
+                    }
+
+                    // Aggregate current change flags.
+                    configChanges |= r.configChangeFlags;
+
+                    if (r.fullscreen) {
+                        // At this point, nothing else needs to be shown
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r);
+                        behindFullscreen = true;
+                    } else if (isActivityOverHome(r)) {
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
+                        showHomeBehindStack = true;
+                        behindFullscreen = !isHomeStack();
+                    }
+                } else {
+                    if (DEBUG_VISBILITY) Slog.v(
+                        TAG, "Make invisible? " + r + " finishing=" + r.finishing
+                        + " state=" + r.state
+                        + " behindFullscreen=" + behindFullscreen);
+                    // Now for any activities that aren't visible to the user, make
+                    // sure they no longer are keeping the screen frozen.
+                    if (r.visible) {
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r);
+                        r.visible = false;
+                        try {
+                            mWindowManager.setAppVisibility(r.appToken, false);
+                            switch (r.state) {
+                                case STOPPING:
+                                case STOPPED:
+                                    if (r.app != null && r.app.thread != null) {
+                                        if (DEBUG_VISBILITY) Slog.v(
+                                                TAG, "Scheduling invisibility: " + r);
+                                        r.app.thread.scheduleWindowVisibility(r.appToken, false);
+                                    }
+                                    break;
+
+                                case INITIALIZING:
+                                case RESUMED:
+                                case PAUSING:
+                                case PAUSED:
+                                    // This case created for transitioning activities from
+                                    // translucent to opaque {@link Activity#convertToOpaque}.
+                                    if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+                                        mStackSupervisor.mStoppingActivities.add(r);
+                                    }
+                                    mStackSupervisor.scheduleIdleLocked();
+                                    break;
+
+                                default:
+                                    break;
+                            }
+                        } catch (Exception e) {
+                            // Just skip on any failure; we'll make it
+                            // visible when it next restarts.
+                            Slog.w(TAG, "Exception thrown making hidden: "
+                                    + r.intent.getComponent(), e);
+                        }
+                    } else {
+                        if (DEBUG_VISBILITY) Slog.v(TAG, "Already invisible: " + r);
+                    }
+                }
+            }
+        }
+        return showHomeBehindStack;
+    }
+
+    void convertToTranslucent(ActivityRecord r) {
+        mTranslucentActivityWaiting = r;
+        mUndrawnActivitiesBelowTopTranslucent.clear();
+        mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT);
+    }
+
+    /**
+     * Called as activities below the top translucent activity are redrawn. When the last one is
+     * redrawn notify the top activity by calling
+     * {@link Activity#onTranslucentConversionComplete}.
+     *
+     * @param r The most recent background activity to be drawn. Or, if r is null then a timeout
+     * occurred and the activity will be notified immediately.
+     */
+    void notifyActivityDrawnLocked(ActivityRecord r) {
+        if ((r == null)
+                || (mUndrawnActivitiesBelowTopTranslucent.remove(r) &&
+                        mUndrawnActivitiesBelowTopTranslucent.isEmpty())) {
+            // The last undrawn activity below the top has just been drawn. If there is an
+            // opaque activity at the top, notify it that it can become translucent safely now.
+            final ActivityRecord waitingActivity = mTranslucentActivityWaiting;
+            mTranslucentActivityWaiting = null;
+            mUndrawnActivitiesBelowTopTranslucent.clear();
+            mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG);
+
+            if (waitingActivity != null && waitingActivity.app != null &&
+                    waitingActivity.app.thread != null) {
+                try {
+                    waitingActivity.app.thread.scheduleTranslucentConversionComplete(
+                            waitingActivity.appToken, r != null);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Ensure that the top activity in the stack is resumed.
+     *
+     * @param prev The previously resumed activity, for when in the process
+     * of pausing; can be null to call from elsewhere.
+     *
+     * @return Returns true if something is being resumed, or false if
+     * nothing happened.
+     */
+    final boolean resumeTopActivityLocked(ActivityRecord prev) {
+        return resumeTopActivityLocked(prev, null);
+    }
+
+    final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
+        if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
+
+        // Find the first activity that is not finishing.
+        ActivityRecord next = topRunningActivityLocked(null);
+
+        // Remember how we'll process this pause/resume situation, and ensure
+        // that the state is reset however we wind up proceeding.
+        final boolean userLeaving = mStackSupervisor.mUserLeaving;
+        mStackSupervisor.mUserLeaving = false;
+
+        if (next == null) {
+            // There are no more activities!  Let's just start up the
+            // Launcher...
+            ActivityOptions.abort(options);
+            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home");
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return mStackSupervisor.resumeHomeActivity(prev);
+        }
+
+        next.delayedResume = false;
+
+        // If the top activity is the resumed one, nothing to do.
+        if (mResumedActivity == next && next.state == ActivityState.RESUMED &&
+                    mStackSupervisor.allResumedActivitiesComplete()) {
+            // Make sure we have executed any pending transitions, since there
+            // should be nothing left to do at this point.
+            mWindowManager.executeAppTransition();
+            mNoAnimActivities.clear();
+            ActivityOptions.abort(options);
+            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Top activity resumed " + next);
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return false;
+        }
+
+        final TaskRecord nextTask = next.task;
+        final TaskRecord prevTask = prev != null ? prev.task : null;
+        if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) {
+            if (DEBUG_STACK)  mStackSupervisor.validateTopActivitiesLocked();
+            if (prevTask == nextTask) {
+                prevTask.setFrontOfTask();
+            } else if (prevTask != topTask()) {
+                // This task is going away but it was supposed to return to the home task.
+                // Now the task above it has to return to the home task instead.
+                final int taskNdx = mTaskHistory.indexOf(prevTask) + 1;
+                mTaskHistory.get(taskNdx).mOnTopOfHome = true;
+            } else {
+                if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Launching home next");
+                return mStackSupervisor.resumeHomeActivity(prev);
+            }
+        }
+
+        // If we are sleeping, and there is no resumed activity, and the top
+        // activity is paused, well that is the state we want.
+        if (mService.isSleepingOrShuttingDown()
+                && mLastPausedActivity == next
+                && mStackSupervisor.allPausedActivitiesComplete()) {
+            // Make sure we have executed any pending transitions, since there
+            // should be nothing left to do at this point.
+            mWindowManager.executeAppTransition();
+            mNoAnimActivities.clear();
+            ActivityOptions.abort(options);
+            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Going to sleep and all paused");
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return false;
+        }
+
+        // Make sure that the user who owns this activity is started.  If not,
+        // we will just leave it as is because someone should be bringing
+        // another user's activities to the top of the stack.
+        if (mService.mStartedUsers.get(next.userId) == null) {
+            Slog.w(TAG, "Skipping resume of top activity " + next
+                    + ": user " + next.userId + " is stopped");
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return false;
+        }
+
+        // The activity may be waiting for stop, but that is no longer
+        // appropriate for it.
+        mStackSupervisor.mStoppingActivities.remove(next);
+        mStackSupervisor.mGoingToSleepActivities.remove(next);
+        next.sleeping = false;
+        mStackSupervisor.mWaitingVisibleActivities.remove(next);
+
+        next.updateOptionsLocked(options);
+
+        if (DEBUG_SWITCH) Slog.v(TAG, "Resuming " + next);
+
+        // If we are currently pausing an activity, then don't do anything
+        // until that is done.
+        if (!mStackSupervisor.allPausedActivitiesComplete()) {
+            if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG,
+                    "resumeTopActivityLocked: Skip resume: some activity pausing.");
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return false;
+        }
+
+        // Okay we are now going to start a switch, to 'next'.  We may first
+        // have to pause the current activity, but this is an important point
+        // where we have decided to go to 'next' so keep track of that.
+        // XXX "App Redirected" dialog is getting too many false positives
+        // at this point, so turn off for now.
+        if (false) {
+            if (mLastStartedActivity != null && !mLastStartedActivity.finishing) {
+                long now = SystemClock.uptimeMillis();
+                final boolean inTime = mLastStartedActivity.startTime != 0
+                        && (mLastStartedActivity.startTime + START_WARN_TIME) >= now;
+                final int lastUid = mLastStartedActivity.info.applicationInfo.uid;
+                final int nextUid = next.info.applicationInfo.uid;
+                if (inTime && lastUid != nextUid
+                        && lastUid != next.launchedFromUid
+                        && mService.checkPermission(
+                                android.Manifest.permission.STOP_APP_SWITCHES,
+                                -1, next.launchedFromUid)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    mService.showLaunchWarningLocked(mLastStartedActivity, next);
+                } else {
+                    next.startTime = now;
+                    mLastStartedActivity = next;
+                }
+            } else {
+                next.startTime = SystemClock.uptimeMillis();
+                mLastStartedActivity = next;
+            }
+        }
+
+        // We need to start pausing the current activity so the top one
+        // can be resumed...
+        boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving);
+        if (mResumedActivity != null) {
+            pausing = true;
+            startPausingLocked(userLeaving, false);
+            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity);
+        }
+        if (pausing) {
+            if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG,
+                    "resumeTopActivityLocked: Skip resume: need to start pausing");
+            // At this point we want to put the upcoming activity's process
+            // at the top of the LRU list, since we know we will be needing it
+            // very soon and it would be a waste to let it get killed if it
+            // happens to be sitting towards the end.
+            if (next.app != null && next.app.thread != null) {
+                // No reason to do full oom adj update here; we'll let that
+                // happen whenever it needs to later.
+                mService.updateLruProcessLocked(next.app, true, null);
+            }
+            if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+            return true;
+        }
+
+        // If the most recent activity was noHistory but was only stopped rather
+        // than stopped+finished because the device went to sleep, we need to make
+        // sure to finish it as we're making a new activity topmost.
+        if (mService.mSleeping && mLastNoHistoryActivity != null &&
+                !mLastNoHistoryActivity.finishing) {
+            if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity +
+                    " on new resume");
+            requestFinishActivityLocked(mLastNoHistoryActivity.appToken, Activity.RESULT_CANCELED,
+                    null, "no-history", false);
+            mLastNoHistoryActivity = null;
+        }
+
+        if (prev != null && prev != next) {
+            if (!prev.waitingVisible && next != null && !next.nowVisible) {
+                prev.waitingVisible = true;
+                mStackSupervisor.mWaitingVisibleActivities.add(prev);
+                if (DEBUG_SWITCH) Slog.v(
+                        TAG, "Resuming top, waiting visible to hide: " + prev);
+            } else {
+                // The next activity is already visible, so hide the previous
+                // activity's windows right now so we can show the new one ASAP.
+                // We only do this if the previous is finishing, which should mean
+                // it is on top of the one being resumed so hiding it quickly
+                // is good.  Otherwise, we want to do the normal route of allowing
+                // the resumed activity to be shown so we can decide if the
+                // previous should actually be hidden depending on whether the
+                // new one is found to be full-screen or not.
+                if (prev.finishing) {
+                    mWindowManager.setAppVisibility(prev.appToken, false);
+                    if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: "
+                            + prev + ", waitingVisible="
+                            + (prev != null ? prev.waitingVisible : null)
+                            + ", nowVisible=" + next.nowVisible);
+                } else {
+                    if (DEBUG_SWITCH) Slog.v(TAG, "Previous already visible but still waiting to hide: "
+                        + prev + ", waitingVisible="
+                        + (prev != null ? prev.waitingVisible : null)
+                        + ", nowVisible=" + next.nowVisible);
+                }
+            }
+        }
+
+        // Launching this app's activity, make sure the app is no longer
+        // considered stopped.
+        try {
+            AppGlobals.getPackageManager().setPackageStoppedState(
+                    next.packageName, false, next.userId); /* TODO: Verify if correct userid */
+        } catch (RemoteException e1) {
+        } catch (IllegalArgumentException e) {
+            Slog.w(TAG, "Failed trying to unstop package "
+                    + next.packageName + ": " + e);
+        }
+
+        // We are starting up the next activity, so tell the window manager
+        // that the previous one will be hidden soon.  This way it can know
+        // to ignore it when computing the desired screen orientation.
+        boolean anim = true;
+        if (prev != null) {
+            if (prev.finishing) {
+                if (DEBUG_TRANSITION) Slog.v(TAG,
+                        "Prepare close transition: prev=" + prev);
+                if (mNoAnimActivities.contains(prev)) {
+                    anim = false;
+                    mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
+                } else {
+                    mWindowManager.prepareAppTransition(prev.task == next.task
+                            ? AppTransition.TRANSIT_ACTIVITY_CLOSE
+                            : AppTransition.TRANSIT_TASK_CLOSE, false);
+                }
+                mWindowManager.setAppWillBeHidden(prev.appToken);
+                mWindowManager.setAppVisibility(prev.appToken, false);
+            } else {
+                if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev);
+                if (mNoAnimActivities.contains(next)) {
+                    anim = false;
+                    mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
+                } else {
+                    mWindowManager.prepareAppTransition(prev.task == next.task
+                            ? AppTransition.TRANSIT_ACTIVITY_OPEN
+                            : AppTransition.TRANSIT_TASK_OPEN, false);
+                }
+            }
+            if (false) {
+                mWindowManager.setAppWillBeHidden(prev.appToken);
+                mWindowManager.setAppVisibility(prev.appToken, false);
+            }
+        } else {
+            if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous");
+            if (mNoAnimActivities.contains(next)) {
+                anim = false;
+                mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
+            } else {
+                mWindowManager.prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_OPEN, false);
+            }
+        }
+        if (anim) {
+            next.applyOptionsLocked();
+        } else {
+            next.clearOptionsLocked();
+        }
+
+        ActivityStack lastStack = mStackSupervisor.getLastStack();
+        if (next.app != null && next.app.thread != null) {
+            if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next);
+
+            // This activity is now becoming visible.
+            mWindowManager.setAppVisibility(next.appToken, true);
+
+            // schedule launch ticks to collect information about slow apps.
+            next.startLaunchTickingLocked();
+
+            ActivityRecord lastResumedActivity =
+                    lastStack == null ? null :lastStack.mResumedActivity;
+            ActivityState lastState = next.state;
+
+            mService.updateCpuStats();
+
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + next + " (in existing)");
+            next.state = ActivityState.RESUMED;
+            mResumedActivity = next;
+            next.task.touchActiveTime();
+            mService.addRecentTaskLocked(next.task);
+            mService.updateLruProcessLocked(next.app, true, null);
+            updateLRUListLocked(next);
+            mService.updateOomAdjLocked();
+
+            // Have the window manager re-evaluate the orientation of
+            // the screen based on the new activity order.
+            boolean notUpdated = true;
+            if (mStackSupervisor.isFrontStack(this)) {
+                Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                        mService.mConfiguration,
+                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null);
+                if (config != null) {
+                    next.frozenBeforeDestroy = true;
+                }
+                notUpdated = !mService.updateConfigurationLocked(config, next, false, false);
+            }
+
+            if (notUpdated) {
+                // The configuration update wasn't able to keep the existing
+                // instance of the activity, and instead started a new one.
+                // We should be all done, but let's just make sure our activity
+                // is still at the top and schedule another run if something
+                // weird happened.
+                ActivityRecord nextNext = topRunningActivityLocked(null);
+                if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG,
+                        "Activity config changed during resume: " + next
+                        + ", new next: " + nextNext);
+                if (nextNext != next) {
+                    // Do over!
+                    mStackSupervisor.scheduleResumeTopActivities();
+                }
+                if (mStackSupervisor.reportResumedActivityLocked(next)) {
+                    mNoAnimActivities.clear();
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
+                }
+                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                return false;
+            }
+
+            try {
+                // Deliver all pending results.
+                ArrayList<ResultInfo> a = next.results;
+                if (a != null) {
+                    final int N = a.size();
+                    if (!next.finishing && N > 0) {
+                        if (DEBUG_RESULTS) Slog.v(
+                                TAG, "Delivering results to " + next
+                                + ": " + a);
+                        next.app.thread.scheduleSendResult(next.appToken, a);
+                    }
+                }
+
+                if (next.newIntents != null) {
+                    next.app.thread.scheduleNewIntent(next.newIntents, next.appToken);
+                }
+
+                EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY,
+                        next.userId, System.identityHashCode(next),
+                        next.task.taskId, next.shortComponentName);
+
+                next.sleeping = false;
+                mService.showAskCompatModeDialogLocked(next);
+                next.app.pendingUiClean = true;
+                next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+                next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+                        mService.isNextTransitionForward());
+
+                mStackSupervisor.checkReadyForSleepLocked();
+
+                if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Resumed " + next);
+            } catch (Exception e) {
+                // Whoops, need to restart this activity!
+                if (DEBUG_STATES) Slog.v(TAG, "Resume failed; resetting state to "
+                        + lastState + ": " + next);
+                next.state = lastState;
+                if (lastStack != null) {
+                    lastStack.mResumedActivity = lastResumedActivity;
+                }
+                Slog.i(TAG, "Restarting because process died: " + next);
+                if (!next.hasBeenLaunched) {
+                    next.hasBeenLaunched = true;
+                } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
+                        mStackSupervisor.isFrontStack(lastStack)) {
+                    mWindowManager.setAppStartingWindow(
+                            next.appToken, next.packageName, next.theme,
+                            mService.compatibilityInfoForPackageLocked(next.info.applicationInfo),
+                            next.nonLocalizedLabel, next.labelRes, next.icon, next.logo,
+                            next.windowFlags, null, true);
+                }
+                mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                return true;
+            }
+
+            // From this point on, if something goes wrong there is no way
+            // to recover the activity.
+            try {
+                next.visible = true;
+                completeResumeLocked(next);
+            } catch (Exception e) {
+                // If any exception gets thrown, toss away this
+                // activity and try the next one.
+                Slog.w(TAG, "Exception thrown during resume of " + next, e);
+                requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null,
+                        "resume-exception", true);
+                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                return true;
+            }
+            next.stopped = false;
+
+        } else {
+            // Whoops, need to restart this activity!
+            if (!next.hasBeenLaunched) {
+                next.hasBeenLaunched = true;
+            } else {
+                if (SHOW_APP_STARTING_PREVIEW) {
+                    mWindowManager.setAppStartingWindow(
+                            next.appToken, next.packageName, next.theme,
+                            mService.compatibilityInfoForPackageLocked(
+                                    next.info.applicationInfo),
+                            next.nonLocalizedLabel,
+                            next.labelRes, next.icon, next.logo, next.windowFlags,
+                            null, true);
+                }
+                if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next);
+            }
+            if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next);
+            mStackSupervisor.startSpecificActivityLocked(next, true, true);
+        }
+
+        if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+        return true;
+    }
+
+    private void insertTaskAtTop(TaskRecord task) {
+        // If this is being moved to the top by another activity or being launched from the home
+        // activity, set mOnTopOfHome accordingly.
+        ActivityStack lastStack = mStackSupervisor.getLastStack();
+        final boolean fromHome = lastStack == null ? true : lastStack.isHomeStack();
+        if (!isHomeStack() && (fromHome || topTask() != task)) {
+            task.mOnTopOfHome = fromHome;
+        }
+
+        mTaskHistory.remove(task);
+        // Now put task at top.
+        int stackNdx = mTaskHistory.size();
+        if (task.userId != mCurrentUser) {
+            // Put non-current user tasks below current user tasks.
+            while (--stackNdx >= 0) {
+                if (mTaskHistory.get(stackNdx).userId != mCurrentUser) {
+                    break;
+                }
+            }
+            ++stackNdx;
+        }
+        mTaskHistory.add(stackNdx, task);
+    }
+
+    final void startActivityLocked(ActivityRecord r, boolean newTask,
+            boolean doResume, boolean keepCurTransition, Bundle options) {
+        TaskRecord rTask = r.task;
+        final int taskId = rTask.taskId;
+        if (taskForIdLocked(taskId) == null || newTask) {
+            // Last activity in task had been removed or ActivityManagerService is reusing task.
+            // Insert or replace.
+            // Might not even be in.
+            insertTaskAtTop(rTask);
+            mWindowManager.moveTaskToTop(taskId);
+        }
+        TaskRecord task = null;
+        if (!newTask) {
+            // If starting in an existing task, find where that is...
+            boolean startIt = true;
+            for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+                task = mTaskHistory.get(taskNdx);
+                if (task == r.task) {
+                    // Here it is!  Now, if this is not yet visible to the
+                    // user, then just add it without starting; it will
+                    // get started when the user navigates back to it.
+                    if (!startIt) {
+                        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task "
+                                + task, new RuntimeException("here").fillInStackTrace());
+                        task.addActivityToTop(r);
+                        r.putInHistory();
+                        mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
+                                r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
+                                (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
+                                r.userId, r.info.configChanges);
+                        if (VALIDATE_TOKENS) {
+                            validateAppTokensLocked();
+                        }
+                        ActivityOptions.abort(options);
+                        return;
+                    }
+                    break;
+                } else if (task.numFullscreen > 0) {
+                    startIt = false;
+                }
+            }
+        }
+
+        // Place a new activity at top of stack, so it is next to interact
+        // with the user.
+
+        // If we are not placing the new activity frontmost, we do not want
+        // to deliver the onUserLeaving callback to the actual frontmost
+        // activity
+        if (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) {
+            mStackSupervisor.mUserLeaving = false;
+            if (DEBUG_USER_LEAVING) Slog.v(TAG,
+                    "startActivity() behind front, mUserLeaving=false");
+        }
+
+        task = r.task;
+
+        // Slot the activity into the history stack and proceed
+        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task,
+                new RuntimeException("here").fillInStackTrace());
+        task.addActivityToTop(r);
+        task.setFrontOfTask();
+
+        r.putInHistory();
+        if (!isHomeStack() || numActivities() > 0) {
+            // We want to show the starting preview window if we are
+            // switching to a new task, or the next activity's process is
+            // not currently running.
+            boolean showStartingIcon = newTask;
+            ProcessRecord proc = r.app;
+            if (proc == null) {
+                proc = mService.mProcessNames.get(r.processName, r.info.applicationInfo.uid);
+            }
+            if (proc == null || proc.thread == null) {
+                showStartingIcon = true;
+            }
+            if (DEBUG_TRANSITION) Slog.v(TAG,
+                    "Prepare open transition: starting " + r);
+            if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+                mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition);
+                mNoAnimActivities.add(r);
+            } else {
+                mWindowManager.prepareAppTransition(newTask
+                        ? AppTransition.TRANSIT_TASK_OPEN
+                        : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition);
+                mNoAnimActivities.remove(r);
+            }
+            r.updateOptionsLocked(options);
+            mWindowManager.addAppToken(task.mActivities.indexOf(r),
+                    r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
+                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+                    r.info.configChanges);
+            boolean doShow = true;
+            if (newTask) {
+                // Even though this activity is starting fresh, we still need
+                // to reset it to make sure we apply affinities to move any
+                // existing activities from other tasks in to it.
+                // If the caller has requested that the target task be
+                // reset, then do so.
+                if ((r.intent.getFlags()
+                        &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                    resetTaskIfNeededLocked(r, r);
+                    doShow = topRunningNonDelayedActivityLocked(null) == r;
+                }
+            }
+            if (SHOW_APP_STARTING_PREVIEW && doShow) {
+                // Figure out if we are transitioning from another activity that is
+                // "has the same starting icon" as the next one.  This allows the
+                // window manager to keep the previous window it had previously
+                // created, if it still had one.
+                ActivityRecord prev = mResumedActivity;
+                if (prev != null) {
+                    // We don't want to reuse the previous starting preview if:
+                    // (1) The current activity is in a different task.
+                    if (prev.task != r.task) {
+                        prev = null;
+                    }
+                    // (2) The current activity is already displayed.
+                    else if (prev.nowVisible) {
+                        prev = null;
+                    }
+                }
+                mWindowManager.setAppStartingWindow(
+                        r.appToken, r.packageName, r.theme,
+                        mService.compatibilityInfoForPackageLocked(
+                                r.info.applicationInfo), r.nonLocalizedLabel,
+                        r.labelRes, r.icon, r.logo, r.windowFlags,
+                        prev != null ? prev.appToken : null, showStartingIcon);
+            }
+        } else {
+            // If this is the first activity, don't do any fancy animations,
+            // because there is nothing for it to animate on top of.
+            mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,
+                    r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,
+                    (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId,
+                    r.info.configChanges);
+            ActivityOptions.abort(options);
+        }
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
+
+        if (doResume) {
+            mStackSupervisor.resumeTopActivitiesLocked();
+        }
+    }
+
+    final void validateAppTokensLocked() {
+        mValidateAppTokens.clear();
+        mValidateAppTokens.ensureCapacity(numActivities());
+        final int numTasks = mTaskHistory.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            TaskRecord task = mTaskHistory.get(taskNdx);
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            if (activities.isEmpty()) {
+                continue;
+            }
+            TaskGroup group = new TaskGroup();
+            group.taskId = task.taskId;
+            mValidateAppTokens.add(group);
+            final int numActivities = activities.size();
+            for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                group.tokens.add(r.appToken);
+            }
+        }
+        mWindowManager.validateAppTokens(mStackId, mValidateAppTokens);
+    }
+
+    /**
+     * Perform a reset of the given task, if needed as part of launching it.
+     * Returns the new HistoryRecord at the top of the task.
+     */
+    /**
+     * Helper method for #resetTaskIfNeededLocked.
+     * We are inside of the task being reset...  we'll either finish this activity, push it out
+     * for another task, or leave it as-is.
+     * @param task The task containing the Activity (taskTop) that might be reset.
+     * @param forceReset
+     * @return An ActivityOptions that needs to be processed.
+     */
+    final ActivityOptions resetTargetTaskIfNeededLocked(TaskRecord task, boolean forceReset) {
+        ActivityOptions topOptions = null;
+
+        int replyChainEnd = -1;
+        boolean canMoveOptions = true;
+
+        // We only do this for activities that are not the root of the task (since if we finish
+        // the root, we may no longer have the task!).
+        final ArrayList<ActivityRecord> activities = task.mActivities;
+        final int numActivities = activities.size();
+        for (int i = numActivities - 1; i > 0; --i ) {
+            ActivityRecord target = activities.get(i);
+
+            final int flags = target.info.flags;
+            final boolean finishOnTaskLaunch =
+                    (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
+            final boolean allowTaskReparenting =
+                    (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
+            final boolean clearWhenTaskReset =
+                    (target.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0;
+
+            if (!finishOnTaskLaunch
+                    && !clearWhenTaskReset
+                    && target.resultTo != null) {
+                // If this activity is sending a reply to a previous
+                // activity, we can't do anything with it now until
+                // we reach the start of the reply chain.
+                // XXX note that we are assuming the result is always
+                // to the previous activity, which is almost always
+                // the case but we really shouldn't count on.
+                if (replyChainEnd < 0) {
+                    replyChainEnd = i;
+                }
+            } else if (!finishOnTaskLaunch
+                    && !clearWhenTaskReset
+                    && allowTaskReparenting
+                    && target.taskAffinity != null
+                    && !target.taskAffinity.equals(task.affinity)) {
+                // If this activity has an affinity for another
+                // task, then we need to move it out of here.  We will
+                // move it as far out of the way as possible, to the
+                // bottom of the activity stack.  This also keeps it
+                // correctly ordered with any activities we previously
+                // moved.
+                final ThumbnailHolder newThumbHolder;
+                final TaskRecord targetTask;
+                final ActivityRecord bottom =
+                        !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
+                                mTaskHistory.get(0).mActivities.get(0) : null;
+                if (bottom != null && target.taskAffinity != null
+                        && target.taskAffinity.equals(bottom.task.affinity)) {
+                    // If the activity currently at the bottom has the
+                    // same task affinity as the one we are moving,
+                    // then merge it into the same task.
+                    targetTask = bottom.task;
+                    newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder;
+                    if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+                            + " out to bottom task " + bottom.task);
+                } else {
+                    targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
+                            null, false);
+                    newThumbHolder = targetTask;
+                    targetTask.affinityIntent = target.intent;
+                    if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+                            + " out to new task " + target.task);
+                }
+
+                if (clearWhenTaskReset) {
+                    // This is the start of a new sub-task.
+                    if (target.thumbHolder == null) {
+                        target.thumbHolder = new ThumbnailHolder();
+                    }
+                } else {
+                    target.thumbHolder = newThumbHolder;
+                }
+
+                final int targetTaskId = targetTask.taskId;
+                mWindowManager.setAppGroupId(target.appToken, targetTaskId);
+
+                boolean noOptions = canMoveOptions;
+                final int start = replyChainEnd < 0 ? i : replyChainEnd;
+                for (int srcPos = start; srcPos >= i; --srcPos) {
+                    final ActivityRecord p = activities.get(srcPos);
+                    if (p.finishing) {
+                        continue;
+                    }
+
+                    ThumbnailHolder curThumbHolder = p.thumbHolder;
+                    canMoveOptions = false;
+                    if (noOptions && topOptions == null) {
+                        topOptions = p.takeOptionsLocked();
+                        if (topOptions != null) {
+                            noOptions = false;
+                        }
+                    }
+                    if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task="
+                            + task + " adding to task=" + targetTask
+                            + " Callers=" + Debug.getCallers(4));
+                    if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
+                            + " out to target's task " + target.task);
+                    p.setTask(targetTask, curThumbHolder, false);
+                    targetTask.addActivityAtBottom(p);
+
+                    mWindowManager.setAppGroupId(p.appToken, targetTaskId);
+                }
+
+                mWindowManager.moveTaskToBottom(targetTaskId);
+                if (VALIDATE_TOKENS) {
+                    validateAppTokensLocked();
+                }
+
+                replyChainEnd = -1;
+            } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) {
+                // If the activity should just be removed -- either
+                // because it asks for it, or the task should be
+                // cleared -- then finish it and anything that is
+                // part of its reply chain.
+                int end;
+                if (clearWhenTaskReset) {
+                    // In this case, we want to finish this activity
+                    // and everything above it, so be sneaky and pretend
+                    // like these are all in the reply chain.
+                    end = numActivities - 1;
+                } else if (replyChainEnd < 0) {
+                    end = i;
+                } else {
+                    end = replyChainEnd;
+                }
+                boolean noOptions = canMoveOptions;
+                for (int srcPos = i; srcPos <= end; srcPos++) {
+                    ActivityRecord p = activities.get(srcPos);
+                    if (p.finishing) {
+                        continue;
+                    }
+                    canMoveOptions = false;
+                    if (noOptions && topOptions == null) {
+                        topOptions = p.takeOptionsLocked();
+                        if (topOptions != null) {
+                            noOptions = false;
+                        }
+                    }
+                    if (DEBUG_TASKS) Slog.w(TAG,
+                            "resetTaskIntendedTask: calling finishActivity on " + p);
+                    if (finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false)) {
+                        end--;
+                        srcPos--;
+                    }
+                }
+                replyChainEnd = -1;
+            } else {
+                // If we were in the middle of a chain, well the
+                // activity that started it all doesn't want anything
+                // special, so leave it all as-is.
+                replyChainEnd = -1;
+            }
+        }
+
+        return topOptions;
+    }
+
+    /**
+     * Helper method for #resetTaskIfNeededLocked. Processes all of the activities in a given
+     * TaskRecord looking for an affinity with the task of resetTaskIfNeededLocked.taskTop.
+     * @param affinityTask The task we are looking for an affinity to.
+     * @param task Task that resetTaskIfNeededLocked.taskTop belongs to.
+     * @param topTaskIsHigher True if #task has already been processed by resetTaskIfNeededLocked.
+     * @param forceReset Flag passed in to resetTaskIfNeededLocked.
+     */
+    private int resetAffinityTaskIfNeededLocked(TaskRecord affinityTask, TaskRecord task,
+            boolean topTaskIsHigher, boolean forceReset, int taskInsertionPoint) {
+        int replyChainEnd = -1;
+        final int taskId = task.taskId;
+        final String taskAffinity = task.affinity;
+
+        final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
+        final int numActivities = activities.size();
+        // Do not operate on the root Activity.
+        for (int i = numActivities - 1; i > 0; --i) {
+            ActivityRecord target = activities.get(i);
+
+            final int flags = target.info.flags;
+            boolean finishOnTaskLaunch = (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0;
+            boolean allowTaskReparenting = (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0;
+
+            if (target.resultTo != null) {
+                // If this activity is sending a reply to a previous
+                // activity, we can't do anything with it now until
+                // we reach the start of the reply chain.
+                // XXX note that we are assuming the result is always
+                // to the previous activity, which is almost always
+                // the case but we really shouldn't count on.
+                if (replyChainEnd < 0) {
+                    replyChainEnd = i;
+                }
+            } else if (topTaskIsHigher
+                    && allowTaskReparenting
+                    && taskAffinity != null
+                    && taskAffinity.equals(target.taskAffinity)) {
+                // This activity has an affinity for our task. Either remove it if we are
+                // clearing or move it over to our task.  Note that
+                // we currently punt on the case where we are resetting a
+                // task that is not at the top but who has activities above
+                // with an affinity to it...  this is really not a normal
+                // case, and we will need to later pull that task to the front
+                // and usually at that point we will do the reset and pick
+                // up those remaining activities.  (This only happens if
+                // someone starts an activity in a new task from an activity
+                // in a task that is not currently on top.)
+                if (forceReset || finishOnTaskLaunch) {
+                    final int start = replyChainEnd >= 0 ? replyChainEnd : i;
+                    if (DEBUG_TASKS) Slog.v(TAG, "Finishing task at index " + start + " to " + i);
+                    for (int srcPos = start; srcPos >= i; --srcPos) {
+                        final ActivityRecord p = activities.get(srcPos);
+                        if (p.finishing) {
+                            continue;
+                        }
+                        finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false);
+                    }
+                } else {
+                    if (taskInsertionPoint < 0) {
+                        taskInsertionPoint = task.mActivities.size();
+
+                    }
+
+                    final int start = replyChainEnd >= 0 ? replyChainEnd : i;
+                    if (DEBUG_TASKS) Slog.v(TAG, "Reparenting from task=" + affinityTask + ":"
+                            + start + "-" + i + " to task=" + task + ":" + taskInsertionPoint);
+                    for (int srcPos = start; srcPos >= i; --srcPos) {
+                        final ActivityRecord p = activities.get(srcPos);
+                        p.setTask(task, null, false);
+                        task.addActivityAtIndex(taskInsertionPoint, p);
+
+                        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + p
+                                + " to stack at " + task,
+                                new RuntimeException("here").fillInStackTrace());
+                        if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " from " + srcPos
+                                + " in to resetting task " + task);
+                        mWindowManager.setAppGroupId(p.appToken, taskId);
+                    }
+                    mWindowManager.moveTaskToTop(taskId);
+                    if (VALIDATE_TOKENS) {
+                        validateAppTokensLocked();
+                    }
+
+                    // Now we've moved it in to place...  but what if this is
+                    // a singleTop activity and we have put it on top of another
+                    // instance of the same activity?  Then we drop the instance
+                    // below so it remains singleTop.
+                    if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
+                        ArrayList<ActivityRecord> taskActivities = task.mActivities;
+                        int targetNdx = taskActivities.indexOf(target);
+                        if (targetNdx > 0) {
+                            ActivityRecord p = taskActivities.get(targetNdx - 1);
+                            if (p.intent.getComponent().equals(target.intent.getComponent())) {
+                                finishActivityLocked(p, Activity.RESULT_CANCELED, null, "replace",
+                                        false);
+                            }
+                        }
+                    }
+                }
+
+                replyChainEnd = -1;
+            }
+        }
+        return taskInsertionPoint;
+    }
+
+    final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop,
+            ActivityRecord newActivity) {
+        boolean forceReset =
+                (newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0;
+        if (ACTIVITY_INACTIVE_RESET_TIME > 0
+                && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) {
+            if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) {
+                forceReset = true;
+            }
+        }
+
+        final TaskRecord task = taskTop.task;
+
+        /** False until we evaluate the TaskRecord associated with taskTop. Switches to true
+         * for remaining tasks. Used for later tasks to reparent to task. */
+        boolean taskFound = false;
+
+        /** If ActivityOptions are moved out and need to be aborted or moved to taskTop. */
+        ActivityOptions topOptions = null;
+
+        // Preserve the location for reparenting in the new task.
+        int reparentInsertionPoint = -1;
+
+        for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
+            final TaskRecord targetTask = mTaskHistory.get(i);
+
+            if (targetTask == task) {
+                topOptions = resetTargetTaskIfNeededLocked(task, forceReset);
+                taskFound = true;
+            } else {
+                reparentInsertionPoint = resetAffinityTaskIfNeededLocked(targetTask, task,
+                        taskFound, forceReset, reparentInsertionPoint);
+            }
+        }
+
+        int taskNdx = mTaskHistory.indexOf(task);
+        do {
+            taskTop = mTaskHistory.get(taskNdx--).getTopActivity();
+        } while (taskTop == null && taskNdx >= 0);
+
+        if (topOptions != null) {
+            // If we got some ActivityOptions from an activity on top that
+            // was removed from the task, propagate them to the new real top.
+            if (taskTop != null) {
+                taskTop.updateOptionsLocked(topOptions);
+            } else {
+                topOptions.abort();
+            }
+        }
+
+        return taskTop;
+    }
+
+    void sendActivityResultLocked(int callingUid, ActivityRecord r,
+            String resultWho, int requestCode, int resultCode, Intent data) {
+
+        if (callingUid > 0) {
+            mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
+                    data, r.getUriPermissionsLocked());
+        }
+
+        if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r
+                + " : who=" + resultWho + " req=" + requestCode
+                + " res=" + resultCode + " data=" + data);
+        if (mResumedActivity == r && r.app != null && r.app.thread != null) {
+            try {
+                ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
+                list.add(new ResultInfo(resultWho, requestCode,
+                        resultCode, data));
+                r.app.thread.scheduleSendResult(r.appToken, list);
+                return;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown sending result to " + r, e);
+            }
+        }
+
+        r.addResultLocked(null, resultWho, requestCode, resultCode, data);
+    }
+
+    private void adjustFocusedActivityLocked(ActivityRecord r) {
+        if (mStackSupervisor.isFrontStack(this) && mService.mFocusedActivity == r) {
+            ActivityRecord next = topRunningActivityLocked(null);
+            if (next != r) {
+                final TaskRecord task = r.task;
+                if (r.frontOfTask && task == topTask() && task.mOnTopOfHome) {
+                    mStackSupervisor.moveHomeToTop();
+                }
+            }
+            mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked());
+        }
+    }
+
+    final void stopActivityLocked(ActivityRecord r) {
+        if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r);
+        if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
+                || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {
+            if (!r.finishing) {
+                if (!mService.mSleeping) {
+                    if (DEBUG_STATES) {
+                        Slog.d(TAG, "no-history finish of " + r);
+                    }
+                    requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
+                            "no-history", false);
+                } else {
+                    if (DEBUG_STATES) Slog.d(TAG, "Not finishing noHistory " + r
+                            + " on stop because we're just sleeping");
+                }
+            }
+        }
+
+        if (r.app != null && r.app.thread != null) {
+            adjustFocusedActivityLocked(r);
+            r.resumeKeyDispatchingLocked();
+            try {
+                r.stopped = false;
+                if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+                        + " (stop requested)");
+                r.state = ActivityState.STOPPING;
+                if (DEBUG_VISBILITY) Slog.v(
+                        TAG, "Stopping visible=" + r.visible + " for " + r);
+                if (!r.visible) {
+                    mWindowManager.setAppVisibility(r.appToken, false);
+                }
+                r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+                if (mService.isSleepingOrShuttingDown()) {
+                    r.setSleeping(true);
+                }
+                Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r);
+                mHandler.sendMessageDelayed(msg, STOP_TIMEOUT);
+            } catch (Exception e) {
+                // Maybe just ignore exceptions here...  if the process
+                // has crashed, our death notification will clean things
+                // up.
+                Slog.w(TAG, "Exception thrown during pause", e);
+                // Just in case, assume it to be stopped.
+                r.stopped = true;
+                if (DEBUG_STATES) Slog.v(TAG, "Stop failed; moving to STOPPED: " + r);
+                r.state = ActivityState.STOPPED;
+                if (r.configDestroy) {
+                    destroyActivityLocked(r, true, false, "stop-except");
+                }
+            }
+        }
+    }
+
+    /**
+     * @return Returns true if the activity is being finished, false if for
+     * some reason it is being left as-is.
+     */
+    final boolean requestFinishActivityLocked(IBinder token, int resultCode,
+            Intent resultData, String reason, boolean oomAdj) {
+        ActivityRecord r = isInStackLocked(token);
+        if (DEBUG_RESULTS || DEBUG_STATES) Slog.v(
+                TAG, "Finishing activity token=" + token + " r="
+                + ", result=" + resultCode + ", data=" + resultData
+                + ", reason=" + reason);
+        if (r == null) {
+            return false;
+        }
+
+        finishActivityLocked(r, resultCode, resultData, reason, oomAdj);
+        return true;
+    }
+
+    final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                ActivityRecord r = activities.get(activityNdx);
+                if (r.resultTo == self && r.requestCode == requestCode) {
+                    if ((r.resultWho == null && resultWho == null) ||
+                        (r.resultWho != null && r.resultWho.equals(resultWho))) {
+                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, "request-sub",
+                                false);
+                    }
+                }
+            }
+        }
+        mService.updateOomAdjLocked();
+    }
+
+    final void finishTopRunningActivityLocked(ProcessRecord app) {
+        ActivityRecord r = topRunningActivityLocked(null);
+        if (r != null && r.app == app) {
+            // If the top running activity is from this crashing
+            // process, then terminate it to avoid getting in a loop.
+            Slog.w(TAG, "  Force finishing activity "
+                    + r.intent.getComponent().flattenToShortString());
+            int taskNdx = mTaskHistory.indexOf(r.task);
+            int activityNdx = r.task.mActivities.indexOf(r);
+            finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+            // Also terminate any activities below it that aren't yet
+            // stopped, to avoid a situation where one will get
+            // re-start our crashing activity once it gets resumed again.
+            --activityNdx;
+            if (activityNdx < 0) {
+                do {
+                    --taskNdx;
+                    if (taskNdx < 0) {
+                        break;
+                    }
+                    activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
+                } while (activityNdx < 0);
+            }
+            if (activityNdx >= 0) {
+                r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
+                if (r.state == ActivityState.RESUMED
+                        || r.state == ActivityState.PAUSING
+                        || r.state == ActivityState.PAUSED) {
+                    if (!r.isHomeActivity() || mService.mHomeProcess != r.app) {
+                        Slog.w(TAG, "  Force finishing activity "
+                                + r.intent.getComponent().flattenToShortString());
+                        finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+                    }
+                }
+            }
+        }
+    }
+
+    final boolean finishActivityAffinityLocked(ActivityRecord r) {
+        ArrayList<ActivityRecord> activities = r.task.mActivities;
+        for (int index = activities.indexOf(r); index >= 0; --index) {
+            ActivityRecord cur = activities.get(index);
+            if (!Objects.equals(cur.taskAffinity, r.taskAffinity)) {
+                break;
+            }
+            finishActivityLocked(cur, Activity.RESULT_CANCELED, null, "request-affinity", true);
+        }
+        return true;
+    }
+
+    final void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
+        // send the result
+        ActivityRecord resultTo = r.resultTo;
+        if (resultTo != null) {
+            if (DEBUG_RESULTS) Slog.v(TAG, "Adding result to " + resultTo
+                    + " who=" + r.resultWho + " req=" + r.requestCode
+                    + " res=" + resultCode + " data=" + resultData);
+            if (r.info.applicationInfo.uid > 0) {
+                mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
+                        resultTo.packageName, resultData,
+                        resultTo.getUriPermissionsLocked());
+            }
+            resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode,
+                                     resultData);
+            r.resultTo = null;
+        }
+        else if (DEBUG_RESULTS) Slog.v(TAG, "No result destination from " + r);
+
+        // Make sure this HistoryRecord is not holding on to other resources,
+        // because clients have remote IPC references to this object so we
+        // can't assume that will go away and want to avoid circular IPC refs.
+        r.results = null;
+        r.pendingResults = null;
+        r.newIntents = null;
+        r.icicle = null;
+    }
+
+    /**
+     * @return Returns true if this activity has been removed from the history
+     * list, or false if it is still in the list and will be removed later.
+     */
+    final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData,
+            String reason, boolean oomAdj) {
+        if (r.finishing) {
+            Slog.w(TAG, "Duplicate finish request for " + r);
+            return false;
+        }
+
+        r.makeFinishing();
+        EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
+                r.userId, System.identityHashCode(r),
+                r.task.taskId, r.shortComponentName, reason);
+        final ArrayList<ActivityRecord> activities = r.task.mActivities;
+        final int index = activities.indexOf(r);
+        if (index < (activities.size() - 1)) {
+            r.task.setFrontOfTask();
+            if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+                // If the caller asked that this activity (and all above it)
+                // be cleared when the task is reset, don't lose that information,
+                // but propagate it up to the next activity.
+                ActivityRecord next = activities.get(index+1);
+                next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
+            }
+        }
+
+        r.pauseKeyDispatchingLocked();
+
+        adjustFocusedActivityLocked(r);
+
+        finishActivityResultsLocked(r, resultCode, resultData);
+
+        if (!mService.mPendingThumbnails.isEmpty()) {
+            // There are clients waiting to receive thumbnails so, in case
+            // this is an activity that someone is waiting for, add it
+            // to the pending list so we can correctly update the clients.
+            mStackSupervisor.mCancelledThumbnails.add(r);
+        }
+
+        if (mResumedActivity == r) {
+            boolean endTask = index <= 0;
+            if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG,
+                    "Prepare close transition: finishing " + r);
+            mWindowManager.prepareAppTransition(endTask
+                    ? AppTransition.TRANSIT_TASK_CLOSE
+                    : AppTransition.TRANSIT_ACTIVITY_CLOSE, false);
+
+            // Tell window manager to prepare for this one to be removed.
+            mWindowManager.setAppVisibility(r.appToken, false);
+
+            if (mPausingActivity == null) {
+                if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
+                if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
+                startPausingLocked(false, false);
+            }
+
+        } else if (r.state != ActivityState.PAUSING) {
+            // If the activity is PAUSING, we will complete the finish once
+            // it is done pausing; else we can just directly finish it here.
+            if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r);
+            return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null;
+        } else {
+            if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r);
+        }
+
+        return false;
+    }
+
+    static final int FINISH_IMMEDIATELY = 0;
+    static final int FINISH_AFTER_PAUSE = 1;
+    static final int FINISH_AFTER_VISIBLE = 2;
+
+    final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {
+        // First things first: if this activity is currently visible,
+        // and the resumed activity is not yet visible, then hold off on
+        // finishing until the resumed one becomes visible.
+        if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
+            if (!mStackSupervisor.mStoppingActivities.contains(r)) {
+                mStackSupervisor.mStoppingActivities.add(r);
+                if (mStackSupervisor.mStoppingActivities.size() > 3
+                        || r.frontOfTask && mTaskHistory.size() <= 1) {
+                    // If we already have a few activities waiting to stop,
+                    // then give up on things going idle and start clearing
+                    // them out. Or if r is the last of activity of the last task the stack
+                    // will be empty and must be cleared immediately.
+                    mStackSupervisor.scheduleIdleLocked();
+                } else {
+                    mStackSupervisor.checkReadyForSleepLocked();
+                }
+            }
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r
+                    + " (finish requested)");
+            r.state = ActivityState.STOPPING;
+            if (oomAdj) {
+                mService.updateOomAdjLocked();
+            }
+            return r;
+        }
+
+        // make sure the record is cleaned out of other places.
+        mStackSupervisor.mStoppingActivities.remove(r);
+        mStackSupervisor.mGoingToSleepActivities.remove(r);
+        mStackSupervisor.mWaitingVisibleActivities.remove(r);
+        if (mResumedActivity == r) {
+            mResumedActivity = null;
+        }
+        final ActivityState prevState = r.state;
+        if (DEBUG_STATES) Slog.v(TAG, "Moving to FINISHING: " + r);
+        r.state = ActivityState.FINISHING;
+
+        if (mode == FINISH_IMMEDIATELY
+                || prevState == ActivityState.STOPPED
+                || prevState == ActivityState.INITIALIZING) {
+            // If this activity is already stopped, we can just finish
+            // it right now.
+            boolean activityRemoved = destroyActivityLocked(r, true,
+                    oomAdj, "finish-imm");
+            if (activityRemoved) {
+                mStackSupervisor.resumeTopActivitiesLocked();
+            }
+            return activityRemoved ? null : r;
+        }
+
+        // Need to go through the full pause cycle to get this
+        // activity into the stopped state and then finish it.
+        if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r);
+        mStackSupervisor.mFinishingActivities.add(r);
+        r.resumeKeyDispatchingLocked();
+        mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
+        return r;
+    }
+
+    final boolean navigateUpToLocked(IBinder token, Intent destIntent, int resultCode,
+            Intent resultData) {
+        final ActivityRecord srec = ActivityRecord.forToken(token);
+        final TaskRecord task = srec.task;
+        final ArrayList<ActivityRecord> activities = task.mActivities;
+        final int start = activities.indexOf(srec);
+        if (!mTaskHistory.contains(task) || (start < 0)) {
+            return false;
+        }
+        int finishTo = start - 1;
+        ActivityRecord parent = finishTo < 0 ? null : activities.get(finishTo);
+        boolean foundParentInTask = false;
+        final ComponentName dest = destIntent.getComponent();
+        if (start > 0 && dest != null) {
+            for (int i = finishTo; i >= 0; i--) {
+                ActivityRecord r = activities.get(i);
+                if (r.info.packageName.equals(dest.getPackageName()) &&
+                        r.info.name.equals(dest.getClassName())) {
+                    finishTo = i;
+                    parent = r;
+                    foundParentInTask = true;
+                    break;
+                }
+            }
+        }
+
+        IActivityController controller = mService.mController;
+        if (controller != null) {
+            ActivityRecord next = topRunningActivityLocked(srec.appToken, 0);
+            if (next != null) {
+                // ask watcher if this is allowed
+                boolean resumeOK = true;
+                try {
+                    resumeOK = controller.activityResuming(next.packageName);
+                } catch (RemoteException e) {
+                    mService.mController = null;
+                    Watchdog.getInstance().setActivityController(null);
+                }
+
+                if (!resumeOK) {
+                    return false;
+                }
+            }
+        }
+        final long origId = Binder.clearCallingIdentity();
+        for (int i = start; i > finishTo; i--) {
+            ActivityRecord r = activities.get(i);
+            requestFinishActivityLocked(r.appToken, resultCode, resultData, "navigate-up", true);
+            // Only return the supplied result for the first activity finished
+            resultCode = Activity.RESULT_CANCELED;
+            resultData = null;
+        }
+
+        if (parent != null && foundParentInTask) {
+            final int parentLaunchMode = parent.info.launchMode;
+            final int destIntentFlags = destIntent.getFlags();
+            if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
+                    parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
+                    parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
+                    (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+                parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent);
+            } else {
+                try {
+                    ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
+                            destIntent.getComponent(), 0, srec.userId);
+                    int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
+                            null, aInfo, parent.appToken, null,
+                            0, -1, parent.launchedFromUid, parent.launchedFromPackage,
+                            0, null, true, null);
+                    foundParentInTask = res == ActivityManager.START_SUCCESS;
+                } catch (RemoteException e) {
+                    foundParentInTask = false;
+                }
+                requestFinishActivityLocked(parent.appToken, resultCode,
+                        resultData, "navigate-up", true);
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+        return foundParentInTask;
+    }
+    /**
+     * Perform the common clean-up of an activity record.  This is called both
+     * as part of destroyActivityLocked() (when destroying the client-side
+     * representation) and cleaning things up as a result of its hosting
+     * processing going away, in which case there is no remaining client-side
+     * state to destroy so only the cleanup here is needed.
+     */
+    final void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices,
+            boolean setState) {
+        if (mResumedActivity == r) {
+            mResumedActivity = null;
+        }
+        if (mService.mFocusedActivity == r) {
+            mService.mFocusedActivity = null;
+        }
+
+        r.configDestroy = false;
+        r.frozenBeforeDestroy = false;
+
+        if (setState) {
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (cleaning up)");
+            r.state = ActivityState.DESTROYED;
+            if (DEBUG_APP) Slog.v(TAG, "Clearing app during cleanUp for activity " + r);
+            r.app = null;
+        }
+
+        // Make sure this record is no longer in the pending finishes list.
+        // This could happen, for example, if we are trimming activities
+        // down to the max limit while they are still waiting to finish.
+        mStackSupervisor.mFinishingActivities.remove(r);
+        mStackSupervisor.mWaitingVisibleActivities.remove(r);
+
+        // Remove any pending results.
+        if (r.finishing && r.pendingResults != null) {
+            for (WeakReference<PendingIntentRecord> apr : r.pendingResults) {
+                PendingIntentRecord rec = apr.get();
+                if (rec != null) {
+                    mService.cancelIntentSenderLocked(rec, false);
+                }
+            }
+            r.pendingResults = null;
+        }
+
+        if (cleanServices) {
+            cleanUpActivityServicesLocked(r);
+        }
+
+        if (!mService.mPendingThumbnails.isEmpty()) {
+            // There are clients waiting to receive thumbnails so, in case
+            // this is an activity that someone is waiting for, add it
+            // to the pending list so we can correctly update the clients.
+            mStackSupervisor.mCancelledThumbnails.add(r);
+        }
+
+        // Get rid of any pending idle timeouts.
+        removeTimeoutsForActivityLocked(r);
+    }
+
+    private void removeTimeoutsForActivityLocked(ActivityRecord r) {
+        mStackSupervisor.removeTimeoutsForActivityLocked(r);
+        mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+        mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
+        mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
+        r.finishLaunchTickingLocked();
+    }
+
+    final void removeActivityFromHistoryLocked(ActivityRecord r) {
+        finishActivityResultsLocked(r, Activity.RESULT_CANCELED, null);
+        r.makeFinishing();
+        if (DEBUG_ADD_REMOVE) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.i(TAG, "Removing activity " + r + " from stack");
+        }
+        final TaskRecord task = r.task;
+        if (task != null && task.removeActivity(r)) {
+            if (DEBUG_STACK) Slog.i(TAG,
+                    "removeActivityFromHistoryLocked: last activity removed from " + this);
+            if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) {
+                mStackSupervisor.moveHomeToTop();
+            }
+            mStackSupervisor.removeTask(task);
+        }
+        r.takeFromHistory();
+        removeTimeoutsForActivityLocked(r);
+        if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (removed from history)");
+        r.state = ActivityState.DESTROYED;
+        if (DEBUG_APP) Slog.v(TAG, "Clearing app during remove for activity " + r);
+        r.app = null;
+        mWindowManager.removeAppToken(r.appToken);
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
+        cleanUpActivityServicesLocked(r);
+        r.removeUriPermissionsLocked();
+    }
+
+    /**
+     * Perform clean-up of service connections in an activity record.
+     */
+    final void cleanUpActivityServicesLocked(ActivityRecord r) {
+        // Throw away any services that have been bound by this activity.
+        if (r.connections != null) {
+            Iterator<ConnectionRecord> it = r.connections.iterator();
+            while (it.hasNext()) {
+                ConnectionRecord c = it.next();
+                mService.mServices.removeConnectionLocked(c, null, r);
+            }
+            r.connections = null;
+        }
+    }
+
+    final void scheduleDestroyActivities(ProcessRecord owner, boolean oomAdj, String reason) {
+        Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG);
+        msg.obj = new ScheduleDestroyArgs(owner, oomAdj, reason);
+        mHandler.sendMessage(msg);
+    }
+
+    final void destroyActivitiesLocked(ProcessRecord owner, boolean oomAdj, String reason) {
+        boolean lastIsOpaque = false;
+        boolean activityRemoved = false;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.finishing) {
+                    continue;
+                }
+                if (r.fullscreen) {
+                    lastIsOpaque = true;
+                }
+                if (owner != null && r.app != owner) {
+                    continue;
+                }
+                if (!lastIsOpaque) {
+                    continue;
+                }
+                // We can destroy this one if we have its icicle saved and
+                // it is not in the process of pausing/stopping/finishing.
+                if (r.app != null && r != mResumedActivity && r != mPausingActivity
+                        && r.haveState && !r.visible && r.stopped
+                        && r.state != ActivityState.DESTROYING
+                        && r.state != ActivityState.DESTROYED) {
+                    if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state
+                            + " resumed=" + mResumedActivity
+                            + " pausing=" + mPausingActivity);
+                    if (destroyActivityLocked(r, true, oomAdj, reason)) {
+                        activityRemoved = true;
+                    }
+                }
+            }
+        }
+        if (activityRemoved) {
+            mStackSupervisor.resumeTopActivitiesLocked();
+
+        }
+    }
+
+    /**
+     * Destroy the current CLIENT SIDE instance of an activity.  This may be
+     * called both when actually finishing an activity, or when performing
+     * a configuration switch where we destroy the current client-side object
+     * but then create a new client-side object for this same HistoryRecord.
+     */
+    final boolean destroyActivityLocked(ActivityRecord r,
+            boolean removeFromApp, boolean oomAdj, String reason) {
+        if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(
+            TAG, "Removing activity from " + reason + ": token=" + r
+              + ", app=" + (r.app != null ? r.app.processName : "(null)"));
+        EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY,
+                r.userId, System.identityHashCode(r),
+                r.task.taskId, r.shortComponentName, reason);
+
+        boolean removedFromHistory = false;
+
+        cleanUpActivityLocked(r, false, false);
+
+        final boolean hadApp = r.app != null;
+
+        if (hadApp) {
+            if (removeFromApp) {
+                r.app.activities.remove(r);
+                if (mService.mHeavyWeightProcess == r.app && r.app.activities.size() <= 0) {
+                    mService.mHeavyWeightProcess = null;
+                    mService.mHandler.sendEmptyMessage(
+                            ActivityManagerService.CANCEL_HEAVY_NOTIFICATION_MSG);
+                }
+                if (r.app.activities.isEmpty()) {
+                    // No longer have activities, so update LRU list and oom adj.
+                    mService.updateLruProcessLocked(r.app, false, null);
+                    mService.updateOomAdjLocked();
+                }
+            }
+
+            boolean skipDestroy = false;
+
+            try {
+                if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r);
+                r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
+                        r.configChangeFlags);
+            } catch (Exception e) {
+                // We can just ignore exceptions here...  if the process
+                // has crashed, our death notification will clean things
+                // up.
+                //Slog.w(TAG, "Exception thrown during finish", e);
+                if (r.finishing) {
+                    removeActivityFromHistoryLocked(r);
+                    removedFromHistory = true;
+                    skipDestroy = true;
+                }
+            }
+
+            r.nowVisible = false;
+
+            // If the activity is finishing, we need to wait on removing it
+            // from the list to give it a chance to do its cleanup.  During
+            // that time it may make calls back with its token so we need to
+            // be able to find it on the list and so we don't want to remove
+            // it from the list yet.  Otherwise, we can just immediately put
+            // it in the destroyed state since we are not removing it from the
+            // list.
+            if (r.finishing && !skipDestroy) {
+                if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r
+                        + " (destroy requested)");
+                r.state = ActivityState.DESTROYING;
+                Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r);
+                mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);
+            } else {
+                if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (destroy skipped)");
+                r.state = ActivityState.DESTROYED;
+                if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
+                r.app = null;
+            }
+        } else {
+            // remove this record from the history.
+            if (r.finishing) {
+                removeActivityFromHistoryLocked(r);
+                removedFromHistory = true;
+            } else {
+                if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (no app)");
+                r.state = ActivityState.DESTROYED;
+                if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r);
+                r.app = null;
+            }
+        }
+
+        r.configChangeFlags = 0;
+
+        if (!mLRUActivities.remove(r) && hadApp) {
+            Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list");
+        }
+
+        return removedFromHistory;
+    }
+
+    final void activityDestroyedLocked(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            ActivityRecord r = ActivityRecord.forToken(token);
+            if (r != null) {
+                mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
+            }
+
+            if (isInStackLocked(token) != null) {
+                if (r.state == ActivityState.DESTROYING) {
+                    cleanUpActivityLocked(r, true, false);
+                    removeActivityFromHistoryLocked(r);
+                }
+            }
+            mStackSupervisor.resumeTopActivitiesLocked();
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list,
+            ProcessRecord app, String listName) {
+        int i = list.size();
+        if (DEBUG_CLEANUP) Slog.v(
+            TAG, "Removing app " + app + " from list " + listName
+            + " with " + i + " entries");
+        while (i > 0) {
+            i--;
+            ActivityRecord r = list.get(i);
+            if (DEBUG_CLEANUP) Slog.v(TAG, "Record #" + i + " " + r);
+            if (r.app == app) {
+                if (DEBUG_CLEANUP) Slog.v(TAG, "---> REMOVING this entry!");
+                list.remove(i);
+                removeTimeoutsForActivityLocked(r);
+            }
+        }
+    }
+
+    boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
+        removeHistoryRecordsForAppLocked(mLRUActivities, app, "mLRUActivities");
+        removeHistoryRecordsForAppLocked(mStackSupervisor.mStoppingActivities, app,
+                "mStoppingActivities");
+        removeHistoryRecordsForAppLocked(mStackSupervisor.mGoingToSleepActivities, app,
+                "mGoingToSleepActivities");
+        removeHistoryRecordsForAppLocked(mStackSupervisor.mWaitingVisibleActivities, app,
+                "mWaitingVisibleActivities");
+        removeHistoryRecordsForAppLocked(mStackSupervisor.mFinishingActivities, app,
+                "mFinishingActivities");
+
+        boolean hasVisibleActivities = false;
+
+        // Clean out the history list.
+        int i = numActivities();
+        if (DEBUG_CLEANUP) Slog.v(
+            TAG, "Removing app " + app + " from history with " + i + " entries");
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                --i;
+                if (DEBUG_CLEANUP) Slog.v(
+                    TAG, "Record #" + i + " " + r + ": app=" + r.app);
+                if (r.app == app) {
+                    boolean remove;
+                    if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
+                        // Don't currently have state for the activity, or
+                        // it is finishing -- always remove it.
+                        remove = true;
+                    } else if (r.launchCount > 2 &&
+                            r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
+                        // We have launched this activity too many times since it was
+                        // able to run, so give up and remove it.
+                        remove = true;
+                    } else {
+                        // The process may be gone, but the activity lives on!
+                        remove = false;
+                    }
+                    if (remove) {
+                        if (DEBUG_ADD_REMOVE || DEBUG_CLEANUP) {
+                            RuntimeException here = new RuntimeException("here");
+                            here.fillInStackTrace();
+                            Slog.i(TAG, "Removing activity " + r + " from stack at " + i
+                                    + ": haveState=" + r.haveState
+                                    + " stateNotNeeded=" + r.stateNotNeeded
+                                    + " finishing=" + r.finishing
+                                    + " state=" + r.state, here);
+                        }
+                        if (!r.finishing) {
+                            Slog.w(TAG, "Force removing " + r + ": app died, no saved state");
+                            EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY,
+                                    r.userId, System.identityHashCode(r),
+                                    r.task.taskId, r.shortComponentName,
+                                    "proc died without state saved");
+                            if (r.state == ActivityState.RESUMED) {
+                                mService.updateUsageStats(r, false);
+                            }
+                        }
+                        removeActivityFromHistoryLocked(r);
+
+                    } else {
+                        // We have the current state for this activity, so
+                        // it can be restarted later when needed.
+                        if (localLOGV) Slog.v(
+                            TAG, "Keeping entry, setting app to null");
+                        if (r.visible) {
+                            hasVisibleActivities = true;
+                        }
+                        if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity "
+                                + r);
+                        r.app = null;
+                        r.nowVisible = false;
+                        if (!r.haveState) {
+                            if (DEBUG_SAVED_STATE) Slog.i(TAG,
+                                    "App died, clearing saved state of " + r);
+                            r.icicle = null;
+                        }
+                    }
+
+                    cleanUpActivityLocked(r, true, true);
+                }
+            }
+        }
+
+        return hasVisibleActivities;
+    }
+
+    final void updateTransitLocked(int transit, Bundle options) {
+        if (options != null) {
+            ActivityRecord r = topRunningActivityLocked(null);
+            if (r != null && r.state != ActivityState.RESUMED) {
+                r.updateOptionsLocked(options);
+            } else {
+                ActivityOptions.abort(options);
+            }
+        }
+        mWindowManager.prepareAppTransition(transit, false);
+    }
+
+    void moveHomeTaskToTop() {
+        final int top = mTaskHistory.size() - 1;
+        for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.isHomeTask()) {
+                if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
+                mTaskHistory.remove(taskNdx);
+                mTaskHistory.add(top, task);
+                mWindowManager.moveTaskToTop(task.taskId);
+                return;
+            }
+        }
+    }
+
+    final boolean findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
+        final TaskRecord task = taskForIdLocked(taskId);
+        if (task != null) {
+            if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
+                mStackSupervisor.mUserLeaving = true;
+            }
+            if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+                // Caller wants the home activity moved with it.  To accomplish this,
+                // we'll just indicate that this task returns to the home task.
+                task.mOnTopOfHome = true;
+            }
+            moveTaskToFrontLocked(task, null, options);
+            return true;
+        }
+        return false;
+    }
+
+    final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) {
+        if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr);
+
+        final int numTasks = mTaskHistory.size();
+        final int index = mTaskHistory.indexOf(tr);
+        if (numTasks == 0 || index < 0)  {
+            // nothing to do!
+            if (reason != null &&
+                    (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+                ActivityOptions.abort(options);
+            } else {
+                updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
+            }
+            return;
+        }
+
+        mStackSupervisor.moveHomeStack(isHomeStack());
+
+        // Shift all activities with this task up to the top
+        // of the stack, keeping them in the same internal order.
+        insertTaskAtTop(tr);
+
+        if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr);
+        if (reason != null &&
+                (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+            mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
+            ActivityRecord r = topRunningActivityLocked(null);
+            if (r != null) {
+                mNoAnimActivities.add(r);
+            }
+            ActivityOptions.abort(options);
+        } else {
+            updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
+        }
+
+        mWindowManager.moveTaskToTop(tr.taskId);
+
+        mStackSupervisor.resumeTopActivitiesLocked();
+        EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId);
+
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
+    }
+
+    /**
+     * Worker method for rearranging history stack. Implements the function of moving all
+     * activities for a specific task (gathering them if disjoint) into a single group at the
+     * bottom of the stack.
+     *
+     * If a watcher is installed, the action is preflighted and the watcher has an opportunity
+     * to premeptively cancel the move.
+     *
+     * @param taskId The taskId to collect and move to the bottom.
+     * @return Returns true if the move completed, false if not.
+     */
+    final boolean moveTaskToBackLocked(int taskId, ActivityRecord reason) {
+        Slog.i(TAG, "moveTaskToBack: " + taskId);
+
+        // If we have a watcher, preflight the move before committing to it.  First check
+        // for *other* available tasks, but if none are available, then try again allowing the
+        // current task to be selected.
+        if (mStackSupervisor.isFrontStack(this) && mService.mController != null) {
+            ActivityRecord next = topRunningActivityLocked(null, taskId);
+            if (next == null) {
+                next = topRunningActivityLocked(null, 0);
+            }
+            if (next != null) {
+                // ask watcher if this is allowed
+                boolean moveOK = true;
+                try {
+                    moveOK = mService.mController.activityResuming(next.packageName);
+                } catch (RemoteException e) {
+                    mService.mController = null;
+                    Watchdog.getInstance().setActivityController(null);
+                }
+                if (!moveOK) {
+                    return false;
+                }
+            }
+        }
+
+        if (DEBUG_TRANSITION) Slog.v(TAG,
+                "Prepare to back transition: task=" + taskId);
+
+        final TaskRecord tr = taskForIdLocked(taskId);
+        if (tr == null) {
+            return false;
+        }
+
+        mTaskHistory.remove(tr);
+        mTaskHistory.add(0, tr);
+
+        // There is an assumption that moving a task to the back moves it behind the home activity.
+        // We make sure here that some activity in the stack will launch home.
+        ActivityRecord lastActivity = null;
+        int numTasks = mTaskHistory.size();
+        for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            if (task.mOnTopOfHome) {
+                break;
+            }
+            if (taskNdx == 1) {
+                // Set the last task before tr to go to home.
+                task.mOnTopOfHome = true;
+            }
+        }
+
+        if (reason != null &&
+                (reason.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
+            mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
+            ActivityRecord r = topRunningActivityLocked(null);
+            if (r != null) {
+                mNoAnimActivities.add(r);
+            }
+        } else {
+            mWindowManager.prepareAppTransition(AppTransition.TRANSIT_TASK_TO_BACK, false);
+        }
+        mWindowManager.moveTaskToBottom(taskId);
+
+        if (VALIDATE_TOKENS) {
+            validateAppTokensLocked();
+        }
+
+        final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null;
+        if (task == tr && task.mOnTopOfHome || numTasks <= 1) {
+            tr.mOnTopOfHome = false;
+            return mStackSupervisor.resumeHomeActivity(null);
+        }
+
+        mStackSupervisor.resumeTopActivitiesLocked();
+        return true;
+    }
+
+    static final void logStartActivity(int tag, ActivityRecord r,
+            TaskRecord task) {
+        final Uri data = r.intent.getData();
+        final String strData = data != null ? data.toSafeString() : null;
+
+        EventLog.writeEvent(tag,
+                r.userId, System.identityHashCode(r), task.taskId,
+                r.shortComponentName, r.intent.getAction(),
+                r.intent.getType(), strData, r.intent.getFlags());
+    }
+
+    /**
+     * Make sure the given activity matches the current configuration.  Returns
+     * false if the activity had to be destroyed.  Returns true if the
+     * configuration is the same, or the activity will remain running as-is
+     * for whatever reason.  Ensures the HistoryRecord is updated with the
+     * correct configuration and all other bookkeeping is handled.
+     */
+    final boolean ensureActivityConfigurationLocked(ActivityRecord r,
+            int globalChanges) {
+        if (mConfigWillChange) {
+            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Skipping config check (will change): " + r);
+            return true;
+        }
+
+        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                "Ensuring correct configuration: " + r);
+
+        // Short circuit: if the two configurations are the exact same
+        // object (the common case), then there is nothing to do.
+        Configuration newConfig = mService.mConfiguration;
+        if (r.configuration == newConfig && !r.forceNewConfig) {
+            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Configuration unchanged in " + r);
+            return true;
+        }
+
+        // We don't worry about activities that are finishing.
+        if (r.finishing) {
+            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Configuration doesn't matter in finishing " + r);
+            r.stopFreezingScreenLocked(false);
+            return true;
+        }
+
+        // Okay we now are going to make this activity have the new config.
+        // But then we need to figure out how it needs to deal with that.
+        Configuration oldConfig = r.configuration;
+        r.configuration = newConfig;
+
+        // Determine what has changed.  May be nothing, if this is a config
+        // that has come back from the app after going idle.  In that case
+        // we just want to leave the official config object now in the
+        // activity and do nothing else.
+        final int changes = oldConfig.diff(newConfig);
+        if (changes == 0 && !r.forceNewConfig) {
+            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Configuration no differences in " + r);
+            return true;
+        }
+
+        // If the activity isn't currently running, just leave the new
+        // configuration and it will pick that up next time it starts.
+        if (r.app == null || r.app.thread == null) {
+            if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Configuration doesn't matter not running " + r);
+            r.stopFreezingScreenLocked(false);
+            r.forceNewConfig = false;
+            return true;
+        }
+
+        // Figure out how to handle the changes between the configurations.
+        if (DEBUG_SWITCH || DEBUG_CONFIGURATION) {
+            Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x"
+                    + Integer.toHexString(changes) + ", handles=0x"
+                    + Integer.toHexString(r.info.getRealConfigChanged())
+                    + ", newConfig=" + newConfig);
+        }
+        if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
+            // Aha, the activity isn't handling the change, so DIE DIE DIE.
+            r.configChangeFlags |= changes;
+            r.startFreezingScreenLocked(r.app, globalChanges);
+            r.forceNewConfig = false;
+            if (r.app == null || r.app.thread == null) {
+                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                        "Config is destroying non-running " + r);
+                destroyActivityLocked(r, true, false, "config");
+            } else if (r.state == ActivityState.PAUSING) {
+                // A little annoying: we are waiting for this activity to
+                // finish pausing.  Let's not do anything now, but just
+                // flag that it needs to be restarted when done pausing.
+                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                        "Config is skipping already pausing " + r);
+                r.configDestroy = true;
+                return true;
+            } else if (r.state == ActivityState.RESUMED) {
+                // Try to optimize this case: the configuration is changing
+                // and we need to restart the top, resumed activity.
+                // Instead of doing the normal handshaking, just say
+                // "restart!".
+                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                        "Config is relaunching resumed " + r);
+                relaunchActivityLocked(r, r.configChangeFlags, true);
+                r.configChangeFlags = 0;
+            } else {
+                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG,
+                        "Config is relaunching non-resumed " + r);
+                relaunchActivityLocked(r, r.configChangeFlags, false);
+                r.configChangeFlags = 0;
+            }
+
+            // All done...  tell the caller we weren't able to keep this
+            // activity around.
+            return false;
+        }
+
+        // Default case: the activity can handle this new configuration, so
+        // hand it over.  Note that we don't need to give it the new
+        // configuration, since we always send configuration changes to all
+        // process when they happen so it can just use whatever configuration
+        // it last got.
+        if (r.app != null && r.app.thread != null) {
+            try {
+                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + r);
+                r.app.thread.scheduleActivityConfigurationChanged(r.appToken);
+            } catch (RemoteException e) {
+                // If process died, whatever.
+            }
+        }
+        r.stopFreezingScreenLocked(false);
+
+        return true;
+    }
+
+    private boolean relaunchActivityLocked(ActivityRecord r,
+            int changes, boolean andResume) {
+        List<ResultInfo> results = null;
+        List<Intent> newIntents = null;
+        if (andResume) {
+            results = r.results;
+            newIntents = r.newIntents;
+        }
+        if (DEBUG_SWITCH) Slog.v(TAG, "Relaunching: " + r
+                + " with results=" + results + " newIntents=" + newIntents
+                + " andResume=" + andResume);
+        EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY
+                : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r),
+                r.task.taskId, r.shortComponentName);
+
+        r.startFreezingScreenLocked(r.app, 0);
+
+        try {
+            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG,
+                    (andResume ? "Relaunching to RESUMED " : "Relaunching to PAUSED ")
+                    + r);
+            r.forceNewConfig = false;
+            r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents,
+                    changes, !andResume, new Configuration(mService.mConfiguration));
+            // Note: don't need to call pauseIfSleepingLocked() here, because
+            // the caller will only pass in 'andResume' if this activity is
+            // currently resumed, which implies we aren't sleeping.
+        } catch (RemoteException e) {
+            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, "Relaunch failed", e);
+        }
+
+        if (andResume) {
+            r.results = null;
+            r.newIntents = null;
+            r.state = ActivityState.RESUMED;
+        } else {
+            mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
+            r.state = ActivityState.PAUSED;
+        }
+
+        return true;
+    }
+
+    boolean willActivityBeVisibleLocked(IBinder token) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.appToken == token) {
+                        return true;
+                }
+                if (r.fullscreen && !r.finishing) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    void closeSystemDialogsLocked() {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "close-sys", true);
+                }
+            }
+        }
+    }
+
+    boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) {
+        boolean didSomething = false;
+        TaskRecord lastTask = null;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            int numActivities = activities.size();
+            for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+                ActivityRecord r = activities.get(activityNdx);
+                final boolean samePackage = r.packageName.equals(name)
+                        || (name == null && r.userId == userId);
+                if ((userId == UserHandle.USER_ALL || r.userId == userId)
+                        && (samePackage || r.task == lastTask)
+                        && (r.app == null || evenPersistent || !r.app.persistent)) {
+                    if (!doit) {
+                        if (r.finishing) {
+                            // If this activity is just finishing, then it is not
+                            // interesting as far as something to stop.
+                            continue;
+                        }
+                        return true;
+                    }
+                    didSomething = true;
+                    Slog.i(TAG, "  Force finishing activity " + r);
+                    if (samePackage) {
+                        if (r.app != null) {
+                            r.app.removed = true;
+                        }
+                        r.app = null;
+                    }
+                    lastTask = r.task;
+                    if (finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
+                            true)) {
+                        // r has been deleted from mActivities, accommodate.
+                        --numActivities;
+                        --activityNdx;
+                    }
+                }
+            }
+        }
+        return didSomething;
+    }
+
+    ActivityRecord getTasksLocked(IThumbnailReceiver receiver,
+            PendingThumbnailsRecord pending, List<RunningTaskInfo> list) {
+        ActivityRecord topRecord = null;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            ActivityRecord r = null;
+            ActivityRecord top = null;
+            int numActivities = 0;
+            int numRunning = 0;
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            if (activities.isEmpty()) {
+                continue;
+            }
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                r = activities.get(activityNdx);
+
+                // Initialize state for next task if needed.
+                if (top == null || (top.state == ActivityState.INITIALIZING)) {
+                    top = r;
+                    numActivities = numRunning = 0;
+                }
+
+                // Add 'r' into the current task.
+                numActivities++;
+                if (r.app != null && r.app.thread != null) {
+                    numRunning++;
+                }
+
+                if (localLOGV) Slog.v(
+                    TAG, r.intent.getComponent().flattenToShortString()
+                    + ": task=" + r.task);
+            }
+
+            RunningTaskInfo ci = new RunningTaskInfo();
+            ci.id = task.taskId;
+            ci.baseActivity = r.intent.getComponent();
+            ci.topActivity = top.intent.getComponent();
+            ci.lastActiveTime = task.lastActiveTime;
+
+            if (top.thumbHolder != null) {
+                ci.description = top.thumbHolder.lastDescription;
+            }
+            ci.numActivities = numActivities;
+            ci.numRunning = numRunning;
+            //System.out.println(
+            //    "#" + maxNum + ": " + " descr=" + ci.description);
+            if (receiver != null) {
+                if (localLOGV) Slog.v(
+                    TAG, "State=" + top.state + "Idle=" + top.idle
+                    + " app=" + top.app
+                    + " thr=" + (top.app != null ? top.app.thread : null));
+                if (top.state == ActivityState.RESUMED || top.state == ActivityState.PAUSING) {
+                    if (top.idle && top.app != null && top.app.thread != null) {
+                        topRecord = top;
+                    } else {
+                        top.thumbnailNeeded = true;
+                    }
+                }
+                pending.pendingRecords.add(top);
+            }
+            list.add(ci);
+        }
+        return topRecord;
+    }
+
+    public void unhandledBackLocked() {
+        final int top = mTaskHistory.size() - 1;
+        if (DEBUG_SWITCH) Slog.d(
+            TAG, "Performing unhandledBack(): top activity at " + top);
+        if (top >= 0) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
+            int activityTop = activities.size() - 1;
+            if (activityTop > 0) {
+                finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED, null,
+                        "unhandled-back", true);
+            }
+        }
+    }
+
+    /**
+     * Reset local parameters because an app's activity died.
+     * @param app The app of the activity that died.
+     * @return result from removeHistoryRecordsForAppLocked.
+     */
+    boolean handleAppDiedLocked(ProcessRecord app) {
+        if (mPausingActivity != null && mPausingActivity.app == app) {
+            if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG,
+                    "App died while pausing: " + mPausingActivity);
+            mPausingActivity = null;
+        }
+        if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
+            mLastPausedActivity = null;
+            mLastNoHistoryActivity = null;
+        }
+
+        return removeHistoryRecordsForAppLocked(app);
+    }
+
+    void handleAppCrashLocked(ProcessRecord app) {
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.app == app) {
+                    Slog.w(TAG, "  Force finishing activity "
+                            + r.intent.getComponent().flattenToShortString());
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false);
+                }
+            }
+        }
+    }
+
+    boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
+            boolean dumpClient, String dumpPackage, boolean needSep, String header) {
+        boolean printed = false;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            printed |= ActivityStackSupervisor.dumpHistoryList(fd, pw,
+                    mTaskHistory.get(taskNdx).mActivities, "    ", "Hist", true, !dumpAll,
+                    dumpClient, dumpPackage, needSep, header,
+                    "    Task id #" + task.taskId);
+            if (printed) {
+                header = null;
+            }
+        }
+        return printed;
+    }
+
+    ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
+        ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+
+        if ("all".equals(name)) {
+            for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+                activities.addAll(mTaskHistory.get(taskNdx).mActivities);
+            }
+        } else if ("top".equals(name)) {
+            final int top = mTaskHistory.size() - 1;
+            if (top >= 0) {
+                final ArrayList<ActivityRecord> list = mTaskHistory.get(top).mActivities;
+                int listTop = list.size() - 1;
+                if (listTop >= 0) {
+                    activities.add(list.get(listTop));
+                }
+            }
+        } else {
+            ItemMatcher matcher = new ItemMatcher();
+            matcher.build(name);
+
+            for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+                for (ActivityRecord r1 : mTaskHistory.get(taskNdx).mActivities) {
+                    if (matcher.match(r1, r1.intent.getComponent())) {
+                        activities.add(r1);
+                    }
+                }
+            }
+        }
+
+        return activities;
+    }
+
+    ActivityRecord restartPackage(String packageName) {
+        ActivityRecord starting = topRunningActivityLocked(null);
+
+        // All activities that came from the package must be
+        // restarted as if there was a config change.
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord a = activities.get(activityNdx);
+                if (a.info.packageName.equals(packageName)) {
+                    a.forceNewConfig = true;
+                    if (starting != null && a == starting && a.visible) {
+                        a.startFreezingScreenLocked(starting.app,
+                                ActivityInfo.CONFIG_SCREEN_LAYOUT);
+                    }
+                }
+            }
+        }
+
+        return starting;
+    }
+
+    boolean removeTask(TaskRecord task) {
+        final int taskNdx = mTaskHistory.indexOf(task);
+        final int topTaskNdx = mTaskHistory.size() - 1;
+        if (task.mOnTopOfHome && taskNdx < topTaskNdx) {
+            mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
+        }
+        mTaskHistory.remove(task);
+        return mTaskHistory.isEmpty();
+    }
+
+    TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) {
+        TaskRecord task = new TaskRecord(taskId, info, intent);
+        addTask(task, toTop);
+        return task;
+    }
+
+    ArrayList<TaskRecord> getAllTasks() {
+        return new ArrayList<TaskRecord>(mTaskHistory);
+    }
+
+    void addTask(final TaskRecord task, final boolean toTop) {
+        task.stack = this;
+        if (toTop) {
+            insertTaskAtTop(task);
+        } else {
+            mTaskHistory.add(0, task);
+        }
+    }
+
+    public int getStackId() {
+        return mStackId;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this))
+                + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}";
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
new file mode 100644
index 0000000..f1ac7fe
--- /dev/null
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -0,0 +1,2974 @@
+/*
+ * 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.am;
+
+import static android.Manifest.permission.START_ANY_ACTIVITY;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.server.am.ActivityManagerService.localLOGV;
+import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerService.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE;
+import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS;
+import static com.android.server.am.ActivityManagerService.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerService.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
+import static com.android.server.am.ActivityManagerService.TAG;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.StackInfo;
+import android.app.ActivityOptions;
+import android.app.AppGlobals;
+import android.app.IActivityContainer;
+import android.app.IActivityContainerCallback;
+import android.app.IActivityManager;
+import android.app.IApplicationThread;
+import android.app.IThumbnailReceiver;
+import android.app.PendingIntent;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.IActivityManager.WaitResult;
+import android.app.ResultInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import android.util.SparseIntArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import com.android.internal.app.HeavyWeightSwitcherActivity;
+import com.android.internal.os.TransferPipe;
+import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
+import com.android.server.am.ActivityStack.ActivityState;
+import com.android.server.wm.WindowManagerService;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public final class ActivityStackSupervisor implements DisplayListener {
+    static final boolean DEBUG = ActivityManagerService.DEBUG || false;
+    static final boolean DEBUG_ADD_REMOVE = DEBUG || false;
+    static final boolean DEBUG_APP = DEBUG || false;
+    static final boolean DEBUG_SAVED_STATE = DEBUG || false;
+    static final boolean DEBUG_STATES = DEBUG || false;
+    static final boolean DEBUG_IDLE = DEBUG || false;
+
+    public static final int HOME_STACK_ID = 0;
+
+    /** How long we wait until giving up on the last activity telling us it is idle. */
+    static final int IDLE_TIMEOUT = 10*1000;
+
+    /** How long we can hold the sleep wake lock before giving up. */
+    static final int SLEEP_TIMEOUT = 5*1000;
+
+    // How long we can hold the launch wake lock before giving up.
+    static final int LAUNCH_TIMEOUT = 10*1000;
+
+    static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG;
+    static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1;
+    static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2;
+    static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3;
+    static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4;
+    static final int HANDLE_DISPLAY_ADDED = FIRST_SUPERVISOR_STACK_MSG + 5;
+    static final int HANDLE_DISPLAY_CHANGED = FIRST_SUPERVISOR_STACK_MSG + 6;
+    static final int HANDLE_DISPLAY_REMOVED = FIRST_SUPERVISOR_STACK_MSG + 7;
+
+
+    // For debugging to make sure the caller when acquiring/releasing our
+    // wake lock is the system process.
+    static final boolean VALIDATE_WAKE_LOCK_CALLER = false;
+
+    final ActivityManagerService mService;
+
+    final ActivityStackSupervisorHandler mHandler;
+
+    /** Short cut */
+    WindowManagerService mWindowManager;
+    DisplayManager mDisplayManager;
+
+    /** Dismiss the keyguard after the next activity is displayed? */
+    boolean mDismissKeyguardOnNextActivity = false;
+
+    /** Identifier counter for all ActivityStacks */
+    private int mLastStackId = HOME_STACK_ID;
+
+    /** Task identifier that activities are currently being started in.  Incremented each time a
+     * new task is created. */
+    private int mCurTaskId = 0;
+
+    /** The current user */
+    private int mCurrentUser;
+
+    /** The stack containing the launcher app */
+    private ActivityStack mHomeStack;
+
+    /** The non-home stack currently receiving input or launching the next activity. If home is
+     * in front then mHomeStack overrides mFocusedStack.
+     * DO NOT ACCESS DIRECTLY - It may be null, use getFocusedStack() */
+    private ActivityStack mFocusedStack;
+
+    /** If this is the same as mFocusedStack then the activity on the top of the focused stack has
+     * been resumed. If stacks are changing position this will hold the old stack until the new
+     * stack becomes resumed after which it will be set to the new stack. */
+    private ActivityStack mLastFocusedStack;
+
+    /** List of activities that are waiting for a new activity to become visible before completing
+     * whatever operation they are supposed to do. */
+    final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<ActivityRecord>();
+
+    /** List of processes waiting to find out about the next visible activity. */
+    final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible =
+            new ArrayList<IActivityManager.WaitResult>();
+
+    /** List of processes waiting to find out about the next launched activity. */
+    final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched =
+            new ArrayList<IActivityManager.WaitResult>();
+
+    /** List of activities that are ready to be stopped, but waiting for the next activity to
+     * settle down before doing so. */
+    final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<ActivityRecord>();
+
+    /** List of activities that are ready to be finished, but waiting for the previous activity to
+     * settle down before doing so.  It contains ActivityRecord objects. */
+    final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<ActivityRecord>();
+
+    /** List of activities that are in the process of going to sleep. */
+    final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>();
+
+    /** List of ActivityRecord objects that have been finished and must still report back to a
+     * pending thumbnail receiver. */
+    final ArrayList<ActivityRecord> mCancelledThumbnails = new ArrayList<ActivityRecord>();
+
+    /** Used on user changes */
+    final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>();
+
+    /** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity
+     * is being brought in front of us. */
+    boolean mUserLeaving = false;
+
+    /** Set when we have taken too long waiting to go to sleep. */
+    boolean mSleepTimeout = false;
+
+    /**
+     * We don't want to allow the device to go to sleep while in the process
+     * of launching an activity.  This is primarily to allow alarm intent
+     * receivers to launch an activity and get that to run before the device
+     * goes back to sleep.
+     */
+    final PowerManager.WakeLock mLaunchingActivity;
+
+    /**
+     * Set when the system is going to sleep, until we have
+     * successfully paused the current activity and released our wake lock.
+     * At that point the system is allowed to actually sleep.
+     */
+    final PowerManager.WakeLock mGoingToSleep;
+
+    /** Stack id of the front stack when user switched, indexed by userId. */
+    SparseIntArray mUserStackInFront = new SparseIntArray(2);
+
+    /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
+    SparseArray<ActivityContainer> mActivityContainers = new SparseArray<ActivityContainer>();
+
+    /** Mapping from displayId to display current state */
+    SparseArray<ActivityDisplayInfo> mDisplayInfos = new SparseArray<ActivityDisplayInfo>();
+
+    public ActivityStackSupervisor(ActivityManagerService service) {
+        mService = service;
+        PowerManager pm = (PowerManager)mService.mContext.getSystemService(Context.POWER_SERVICE);
+        mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep");
+        mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
+        if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
+            throw new IllegalStateException("Calling must be system uid");
+        }
+        mLaunchingActivity =
+                pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch");
+        mLaunchingActivity.setReferenceCounted(false);
+    }
+
+    void setWindowManager(WindowManagerService wm) {
+        synchronized (mService) {
+            mWindowManager = wm;
+
+            mDisplayManager =
+                    (DisplayManager)mService.mContext.getSystemService(Context.DISPLAY_SERVICE);
+            mDisplayManager.registerDisplayListener(this, null);
+
+            Display[] displays = mDisplayManager.getDisplays();
+            for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
+                final int displayId = displays[displayNdx].getDisplayId();
+                ActivityDisplayInfo info = new ActivityDisplayInfo(displayId);
+                mDisplayInfos.put(displayId, info);
+            }
+
+            createStackOnDisplay(null, HOME_STACK_ID, Display.DEFAULT_DISPLAY);
+            mHomeStack = mFocusedStack = mLastFocusedStack = getStack(HOME_STACK_ID);
+        }
+    }
+
+    void dismissKeyguard() {
+        if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen("");
+        if (mDismissKeyguardOnNextActivity) {
+            mDismissKeyguardOnNextActivity = false;
+            mWindowManager.dismissKeyguard();
+        }
+    }
+
+    ActivityStack getFocusedStack() {
+        return mFocusedStack;
+    }
+
+    ActivityStack getLastStack() {
+        return mLastFocusedStack;
+    }
+
+    // TODO: Split into two methods isFrontStack for any visible stack and isFrontmostStack for the
+    // top of all visible stacks.
+    boolean isFrontStack(ActivityStack stack) {
+        ArrayList<ActivityStack> stacks = stack.getStacksLocked();
+        if (stacks != null && !stacks.isEmpty()) {
+            return stack == stacks.get(stacks.size() - 1);
+        }
+        return false;
+    }
+
+    void moveHomeStack(boolean toFront) {
+        ArrayList<ActivityStack> stacks = mHomeStack.getStacksLocked();
+        int topNdx = stacks.size() - 1;
+        if (topNdx <= 0) {
+            return;
+        }
+        ActivityStack topStack = stacks.get(topNdx);
+        final boolean homeInFront = topStack == mHomeStack;
+        if (homeInFront != toFront) {
+            mLastFocusedStack = topStack;
+            stacks.remove(mHomeStack);
+            stacks.add(toFront ? topNdx : 0, mHomeStack);
+            mFocusedStack = stacks.get(topNdx);
+            if (DEBUG_STACK) Slog.d(TAG, "moveHomeTask: topStack old=" + topStack + " new="
+                    + mFocusedStack);
+        }
+    }
+
+    void moveHomeToTop() {
+        moveHomeStack(true);
+        mHomeStack.moveHomeTaskToTop();
+    }
+
+    boolean resumeHomeActivity(ActivityRecord prev) {
+        moveHomeToTop();
+        if (prev != null) {
+            prev.task.mOnTopOfHome = false;
+        }
+        ActivityRecord r = mHomeStack.topRunningActivityLocked(null);
+        if (r != null && r.isHomeActivity()) {
+            mService.setFocusedActivityLocked(r);
+            return resumeTopActivitiesLocked(mHomeStack, prev, null);
+        }
+        return mService.startHomeActivityLocked(mCurrentUser);
+    }
+
+    void setDismissKeyguard(boolean dismiss) {
+        if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(" dismiss=" + dismiss);
+        mDismissKeyguardOnNextActivity = dismiss;
+    }
+
+    TaskRecord anyTaskForIdLocked(int id) {
+        int numDisplays = mDisplayInfos.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                ActivityStack stack = stacks.get(stackNdx);
+                TaskRecord task = stack.taskForIdLocked(id);
+                if (task != null) {
+                    return task;
+                }
+            }
+        }
+        return null;
+    }
+
+    ActivityRecord isInAnyStackLocked(IBinder token) {
+        int numDisplays = mDisplayInfos.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityRecord r = stacks.get(stackNdx).isInStackLocked(token);
+                if (r != null) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    int getNextTaskId() {
+        do {
+            mCurTaskId++;
+            if (mCurTaskId <= 0) {
+                mCurTaskId = 1;
+            }
+        } while (anyTaskForIdLocked(mCurTaskId) != null);
+        return mCurTaskId;
+    }
+
+    void removeTask(TaskRecord task) {
+        mWindowManager.removeTask(task.taskId);
+        final ActivityStack stack = task.stack;
+        final ActivityRecord r = stack.mResumedActivity;
+        if (r != null && r.task == task) {
+            stack.mResumedActivity = null;
+        }
+        if (stack.removeTask(task) && !stack.isHomeStack()) {
+            if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack " + stack);
+            stack.mActivityContainer.detachLocked();
+            final int stackId = stack.mStackId;
+            final int nextStackId = mWindowManager.removeStack(stackId);
+            // TODO: Perhaps we need to let the ActivityManager determine the next focus...
+            mFocusedStack = getStack(nextStackId);
+        }
+    }
+
+    ActivityRecord resumedAppLocked() {
+        ActivityStack stack = getFocusedStack();
+        if (stack == null) {
+            return null;
+        }
+        ActivityRecord resumedActivity = stack.mResumedActivity;
+        if (resumedActivity == null || resumedActivity.app == null) {
+            resumedActivity = stack.mPausingActivity;
+            if (resumedActivity == null || resumedActivity.app == null) {
+                resumedActivity = stack.topRunningActivityLocked(null);
+            }
+        }
+        return resumedActivity;
+    }
+
+    boolean attachApplicationLocked(ProcessRecord app) throws Exception {
+        final String processName = app.processName;
+        boolean didSomething = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (!isFrontStack(stack)) {
+                    continue;
+                }
+                ActivityRecord hr = stack.topRunningActivityLocked(null);
+                if (hr != null) {
+                    if (hr.app == null && app.uid == hr.info.applicationInfo.uid
+                            && processName.equals(hr.processName)) {
+                        try {
+                            if (realStartActivityLocked(hr, app, true, true)) {
+                                didSomething = true;
+                            }
+                        } catch (Exception e) {
+                            Slog.w(TAG, "Exception in new application when starting activity "
+                                  + hr.intent.getComponent().flattenToShortString(), e);
+                            throw e;
+                        }
+                    }
+                }
+            }
+        }
+        if (!didSomething) {
+            ensureActivitiesVisibleLocked(null, 0);
+        }
+        return didSomething;
+    }
+
+    boolean allResumedActivitiesIdle() {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (!isFrontStack(stack)) {
+                    continue;
+                }
+                final ActivityRecord resumedActivity = stack.mResumedActivity;
+                if (resumedActivity == null || !resumedActivity.idle) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    boolean allResumedActivitiesComplete() {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (isFrontStack(stack)) {
+                    final ActivityRecord r = stack.mResumedActivity;
+                    if (r != null && r.state != ActivityState.RESUMED) {
+                        return false;
+                    }
+                }
+            }
+        }
+        // TODO: Not sure if this should check if all Paused are complete too.
+        if (DEBUG_STACK) Slog.d(TAG,
+                "allResumedActivitiesComplete: mLastFocusedStack changing from=" +
+                mLastFocusedStack + " to=" + mFocusedStack);
+        mLastFocusedStack = mFocusedStack;
+        return true;
+    }
+
+    boolean allResumedActivitiesVisible() {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityRecord r = stack.mResumedActivity;
+                if (r != null && (!r.nowVisible || r.waitingVisible)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Pause all activities in either all of the stacks or just the back stacks.
+     * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving().
+     * @return true if any activity was paused as a result of this call.
+     */
+    boolean pauseBackStacks(boolean userLeaving) {
+        boolean someActivityPaused = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (!isFrontStack(stack) && stack.mResumedActivity != null) {
+                    if (DEBUG_STATES) Slog.d(TAG, "pauseBackStacks: stack=" + stack +
+                            " mResumedActivity=" + stack.mResumedActivity);
+                    stack.startPausingLocked(userLeaving, false);
+                    someActivityPaused = true;
+                }
+            }
+        }
+        return someActivityPaused;
+    }
+
+    boolean allPausedActivitiesComplete() {
+        boolean pausing = true;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                final ActivityRecord r = stack.mPausingActivity;
+                if (r != null && r.state != ActivityState.PAUSED
+                        && r.state != ActivityState.STOPPED
+                        && r.state != ActivityState.STOPPING) {
+                    if (DEBUG_STATES) {
+                        Slog.d(TAG, "allPausedActivitiesComplete: r=" + r + " state=" + r.state);
+                        pausing = false;
+                    } else {
+                        return false;
+                    }
+                }
+            }
+        }
+        return pausing;
+    }
+
+    void reportActivityVisibleLocked(ActivityRecord r) {
+        for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) {
+            WaitResult w = mWaitingActivityVisible.get(i);
+            w.timeout = false;
+            if (r != null) {
+                w.who = new ComponentName(r.info.packageName, r.info.name);
+            }
+            w.totalTime = SystemClock.uptimeMillis() - w.thisTime;
+            w.thisTime = w.totalTime;
+        }
+        mService.notifyAll();
+        dismissKeyguard();
+    }
+
+    void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r,
+            long thisTime, long totalTime) {
+        for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
+            WaitResult w = mWaitingActivityLaunched.remove(i);
+            w.timeout = timeout;
+            if (r != null) {
+                w.who = new ComponentName(r.info.packageName, r.info.name);
+            }
+            w.thisTime = thisTime;
+            w.totalTime = totalTime;
+        }
+        mService.notifyAll();
+    }
+
+    ActivityRecord topRunningActivityLocked() {
+        final ActivityStack focusedStack = getFocusedStack();
+        ActivityRecord r = focusedStack.topRunningActivityLocked(null);
+        if (r != null) {
+            return r;
+        }
+
+        // Return to the home stack.
+        final ArrayList<ActivityStack> stacks = mHomeStack.getStacksLocked();
+        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = stacks.get(stackNdx);
+            if (stack != focusedStack && isFrontStack(stack)) {
+                r = stack.topRunningActivityLocked(null);
+                if (r != null) {
+                    return r;
+                }
+            }
+        }
+        return null;
+    }
+
+    ActivityRecord getTasksLocked(int maxNum, IThumbnailReceiver receiver,
+            PendingThumbnailsRecord pending, List<RunningTaskInfo> list) {
+        ActivityRecord r = null;
+
+        // Gather all of the running tasks for each stack into runningTaskLists.
+        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
+                new ArrayList<ArrayList<RunningTaskInfo>>();
+        final int numDisplays = mDisplayInfos.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>();
+                runningTaskLists.add(stackTaskList);
+                final ActivityRecord ar = stack.getTasksLocked(receiver, pending, stackTaskList);
+                if (r == null && isFrontStack(stack)) {
+                    r = ar;
+                }
+            }
+        }
+
+        // The lists are already sorted from most recent to oldest. Just pull the most recent off
+        // each list and add it to list. Stop when all lists are empty or maxNum reached.
+        while (maxNum > 0) {
+            long mostRecentActiveTime = Long.MIN_VALUE;
+            ArrayList<RunningTaskInfo> selectedStackList = null;
+            final int numTaskLists = runningTaskLists.size();
+            for (int stackNdx = 0; stackNdx < numTaskLists; ++stackNdx) {
+                ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists.get(stackNdx);
+                if (!stackTaskList.isEmpty()) {
+                    final long lastActiveTime = stackTaskList.get(0).lastActiveTime;
+                    if (lastActiveTime > mostRecentActiveTime) {
+                        mostRecentActiveTime = lastActiveTime;
+                        selectedStackList = stackTaskList;
+                    }
+                }
+            }
+            if (selectedStackList != null) {
+                list.add(selectedStackList.remove(0));
+                --maxNum;
+            } else {
+                break;
+            }
+        }
+
+        return r;
+    }
+
+    ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
+            String profileFile, ParcelFileDescriptor profileFd, int userId) {
+        // Collect information about the target of the Intent.
+        ActivityInfo aInfo;
+        try {
+            ResolveInfo rInfo =
+                AppGlobals.getPackageManager().resolveIntent(
+                        intent, resolvedType,
+                        PackageManager.MATCH_DEFAULT_ONLY
+                                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
+            aInfo = rInfo != null ? rInfo.activityInfo : null;
+        } catch (RemoteException e) {
+            aInfo = null;
+        }
+
+        if (aInfo != null) {
+            // Store the found target back into the intent, because now that
+            // we have it we never want to do this again.  For example, if the
+            // user navigates back to this point in the history, we should
+            // always restart the exact same activity.
+            intent.setComponent(new ComponentName(
+                    aInfo.applicationInfo.packageName, aInfo.name));
+
+            // Don't debug things in the system process
+            if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) {
+                if (!aInfo.processName.equals("system")) {
+                    mService.setDebugApp(aInfo.processName, true, false);
+                }
+            }
+
+            if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) {
+                if (!aInfo.processName.equals("system")) {
+                    mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName);
+                }
+            }
+
+            if (profileFile != null) {
+                if (!aInfo.processName.equals("system")) {
+                    mService.setProfileApp(aInfo.applicationInfo, aInfo.processName,
+                            profileFile, profileFd,
+                            (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0);
+                }
+            }
+        }
+        return aInfo;
+    }
+
+    void startHomeActivity(Intent intent, ActivityInfo aInfo) {
+        moveHomeToTop();
+        startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0,
+                null, false, null);
+    }
+
+    final int startActivityMayWait(IApplicationThread caller, int callingUid,
+            String callingPackage, Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
+            Bundle options, int userId) {
+        // Refuse possible leaked file descriptors
+        if (intent != null && intent.hasFileDescriptors()) {
+            throw new IllegalArgumentException("File descriptors passed in Intent");
+        }
+        boolean componentSpecified = intent.getComponent() != null;
+
+        // Don't modify the client's object!
+        intent = new Intent(intent);
+
+        // Collect information about the target of the Intent.
+        ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
+                profileFile, profileFd, userId);
+
+        synchronized (mService) {
+            int callingPid;
+            if (callingUid >= 0) {
+                callingPid = -1;
+            } else if (caller == null) {
+                callingPid = Binder.getCallingPid();
+                callingUid = Binder.getCallingUid();
+            } else {
+                callingPid = callingUid = -1;
+            }
+
+            final ActivityStack stack = getFocusedStack();
+            stack.mConfigWillChange = config != null
+                    && mService.mConfiguration.diff(config) != 0;
+            if (DEBUG_CONFIGURATION) Slog.v(TAG,
+                    "Starting activity when config will change = " + stack.mConfigWillChange);
+
+            final long origId = Binder.clearCallingIdentity();
+
+            if (aInfo != null &&
+                    (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
+                // This may be a heavy-weight process!  Check to see if we already
+                // have another, different heavy-weight process running.
+                if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) {
+                    if (mService.mHeavyWeightProcess != null &&
+                            (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid ||
+                            !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) {
+                        int realCallingUid = callingUid;
+                        if (caller != null) {
+                            ProcessRecord callerApp = mService.getRecordForAppLocked(caller);
+                            if (callerApp != null) {
+                                realCallingUid = callerApp.info.uid;
+                            } else {
+                                Slog.w(TAG, "Unable to find app for caller " + caller
+                                      + " (pid=" + callingPid + ") when starting: "
+                                      + intent.toString());
+                                ActivityOptions.abort(options);
+                                return ActivityManager.START_PERMISSION_DENIED;
+                            }
+                        }
+
+                        IIntentSender target = mService.getIntentSenderLocked(
+                                ActivityManager.INTENT_SENDER_ACTIVITY, "android",
+                                realCallingUid, userId, null, null, 0, new Intent[] { intent },
+                                new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
+                                | PendingIntent.FLAG_ONE_SHOT, null);
+
+                        Intent newIntent = new Intent();
+                        if (requestCode >= 0) {
+                            // Caller is requesting a result.
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true);
+                        }
+                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT,
+                                new IntentSender(target));
+                        if (mService.mHeavyWeightProcess.activities.size() > 0) {
+                            ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0);
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP,
+                                    hist.packageName);
+                            newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK,
+                                    hist.task.taskId);
+                        }
+                        newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP,
+                                aInfo.packageName);
+                        newIntent.setFlags(intent.getFlags());
+                        newIntent.setClassName("android",
+                                HeavyWeightSwitcherActivity.class.getName());
+                        intent = newIntent;
+                        resolvedType = null;
+                        caller = null;
+                        callingUid = Binder.getCallingUid();
+                        callingPid = Binder.getCallingPid();
+                        componentSpecified = true;
+                        try {
+                            ResolveInfo rInfo =
+                                AppGlobals.getPackageManager().resolveIntent(
+                                        intent, null,
+                                        PackageManager.MATCH_DEFAULT_ONLY
+                                        | ActivityManagerService.STOCK_PM_FLAGS, userId);
+                            aInfo = rInfo != null ? rInfo.activityInfo : null;
+                            aInfo = mService.getActivityInfoForUser(aInfo, userId);
+                        } catch (RemoteException e) {
+                            aInfo = null;
+                        }
+                    }
+                }
+            }
+
+            int res = startActivityLocked(caller, intent, resolvedType,
+                    aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
+                    callingPackage, startFlags, options, componentSpecified, null);
+
+            if (stack.mConfigWillChange) {
+                // If the caller also wants to switch to a new configuration,
+                // do so now.  This allows a clean switch, as we are waiting
+                // for the current activity to pause (so we will not destroy
+                // it), and have not yet started the next activity.
+                mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION,
+                        "updateConfiguration()");
+                stack.mConfigWillChange = false;
+                if (DEBUG_CONFIGURATION) Slog.v(TAG,
+                        "Updating to new configuration after starting activity.");
+                mService.updateConfigurationLocked(config, null, false, false);
+            }
+
+            Binder.restoreCallingIdentity(origId);
+
+            if (outResult != null) {
+                outResult.result = res;
+                if (res == ActivityManager.START_SUCCESS) {
+                    mWaitingActivityLaunched.add(outResult);
+                    do {
+                        try {
+                            mService.wait();
+                        } catch (InterruptedException e) {
+                        }
+                    } while (!outResult.timeout && outResult.who == null);
+                } else if (res == ActivityManager.START_TASK_TO_FRONT) {
+                    ActivityRecord r = stack.topRunningActivityLocked(null);
+                    if (r.nowVisible) {
+                        outResult.timeout = false;
+                        outResult.who = new ComponentName(r.info.packageName, r.info.name);
+                        outResult.totalTime = 0;
+                        outResult.thisTime = 0;
+                    } else {
+                        outResult.thisTime = SystemClock.uptimeMillis();
+                        mWaitingActivityVisible.add(outResult);
+                        do {
+                            try {
+                                mService.wait();
+                            } catch (InterruptedException e) {
+                            }
+                        } while (!outResult.timeout && outResult.who == null);
+                    }
+                }
+            }
+
+            return res;
+        }
+    }
+
+    final int startActivities(IApplicationThread caller, int callingUid, String callingPackage,
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options, int userId) {
+        if (intents == null) {
+            throw new NullPointerException("intents is null");
+        }
+        if (resolvedTypes == null) {
+            throw new NullPointerException("resolvedTypes is null");
+        }
+        if (intents.length != resolvedTypes.length) {
+            throw new IllegalArgumentException("intents are length different than resolvedTypes");
+        }
+
+
+        int callingPid;
+        if (callingUid >= 0) {
+            callingPid = -1;
+        } else if (caller == null) {
+            callingPid = Binder.getCallingPid();
+            callingUid = Binder.getCallingUid();
+        } else {
+            callingPid = callingUid = -1;
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mService) {
+                ActivityRecord[] outActivity = new ActivityRecord[1];
+                for (int i=0; i<intents.length; i++) {
+                    Intent intent = intents[i];
+                    if (intent == null) {
+                        continue;
+                    }
+
+                    // Refuse possible leaked file descriptors
+                    if (intent != null && intent.hasFileDescriptors()) {
+                        throw new IllegalArgumentException("File descriptors passed in Intent");
+                    }
+
+                    boolean componentSpecified = intent.getComponent() != null;
+
+                    // Don't modify the client's object!
+                    intent = new Intent(intent);
+
+                    // Collect information about the target of the Intent.
+                    ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i],
+                            0, null, null, userId);
+                    // TODO: New, check if this is correct
+                    aInfo = mService.getActivityInfoForUser(aInfo, userId);
+
+                    if (aInfo != null &&
+                            (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE)
+                                    != 0) {
+                        throw new IllegalArgumentException(
+                                "FLAG_CANT_SAVE_STATE not supported here");
+                    }
+
+                    Bundle theseOptions;
+                    if (options != null && i == intents.length-1) {
+                        theseOptions = options;
+                    } else {
+                        theseOptions = null;
+                    }
+                    int res = startActivityLocked(caller, intent, resolvedTypes[i],
+                            aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage,
+                            0, theseOptions, componentSpecified, outActivity);
+                    if (res < 0) {
+                        return res;
+                    }
+
+                    resultTo = outActivity[0] != null ? outActivity[0].appToken : null;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return ActivityManager.START_SUCCESS;
+    }
+
+    final boolean realStartActivityLocked(ActivityRecord r,
+            ProcessRecord app, boolean andResume, boolean checkConfig)
+            throws RemoteException {
+
+        r.startFreezingScreenLocked(app, 0);
+        if (false) Slog.d(TAG, "realStartActivity: setting app visibility true");
+        mWindowManager.setAppVisibility(r.appToken, true);
+
+        // schedule launch ticks to collect information about slow apps.
+        r.startLaunchTickingLocked();
+
+        // Have the window manager re-evaluate the orientation of
+        // the screen based on the new activity order.  Note that
+        // as a result of this, it can call back into the activity
+        // manager with a new orientation.  We don't care about that,
+        // because the activity is not currently running so we are
+        // just restarting it anyway.
+        if (checkConfig) {
+            Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                    mService.mConfiguration,
+                    r.mayFreezeScreenLocked(app) ? r.appToken : null);
+            mService.updateConfigurationLocked(config, r, false, false);
+        }
+
+        r.app = app;
+        app.waitingToKill = null;
+        r.launchCount++;
+        r.lastLaunchTime = SystemClock.uptimeMillis();
+
+        if (localLOGV) Slog.v(TAG, "Launching: " + r);
+
+        int idx = app.activities.indexOf(r);
+        if (idx < 0) {
+            app.activities.add(r);
+        }
+        mService.updateLruProcessLocked(app, true, null);
+        mService.updateOomAdjLocked();
+
+        final ActivityStack stack = r.task.stack;
+        try {
+            if (app.thread == null) {
+                throw new RemoteException();
+            }
+            List<ResultInfo> results = null;
+            List<Intent> newIntents = null;
+            if (andResume) {
+                results = r.results;
+                newIntents = r.newIntents;
+            }
+            if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r
+                    + " icicle=" + r.icicle
+                    + " with results=" + results + " newIntents=" + newIntents
+                    + " andResume=" + andResume);
+            if (andResume) {
+                EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY,
+                        r.userId, System.identityHashCode(r),
+                        r.task.taskId, r.shortComponentName);
+            }
+            if (r.isHomeActivity() && r.isNotResolverActivity()) {
+                // Home process is the root process of the task.
+                mService.mHomeProcess = r.task.mActivities.get(0).app;
+            }
+            mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
+            r.sleeping = false;
+            r.forceNewConfig = false;
+            mService.showAskCompatModeDialogLocked(r);
+            r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
+            String profileFile = null;
+            ParcelFileDescriptor profileFd = null;
+            boolean profileAutoStop = false;
+            if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) {
+                if (mService.mProfileProc == null || mService.mProfileProc == app) {
+                    mService.mProfileProc = app;
+                    profileFile = mService.mProfileFile;
+                    profileFd = mService.mProfileFd;
+                    profileAutoStop = mService.mAutoStopProfiler;
+                }
+            }
+            app.hasShownUi = true;
+            app.pendingUiClean = true;
+            if (profileFd != null) {
+                try {
+                    profileFd = profileFd.dup();
+                } catch (IOException e) {
+                    if (profileFd != null) {
+                        try {
+                            profileFd.close();
+                        } catch (IOException o) {
+                        }
+                        profileFd = null;
+                    }
+                }
+            }
+            app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP);
+            app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+                    System.identityHashCode(r), r.info,
+                    new Configuration(mService.mConfiguration), r.compat,
+                    app.repProcState, r.icicle, results, newIntents, !andResume,
+                    mService.isNextTransitionForward(), profileFile, profileFd,
+                    profileAutoStop);
+
+            if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {
+                // This may be a heavy-weight process!  Note that the package
+                // manager will ensure that only activity can run in the main
+                // process of the .apk, which is the only thing that will be
+                // considered heavy-weight.
+                if (app.processName.equals(app.info.packageName)) {
+                    if (mService.mHeavyWeightProcess != null
+                            && mService.mHeavyWeightProcess != app) {
+                        Slog.w(TAG, "Starting new heavy weight process " + app
+                                + " when already running "
+                                + mService.mHeavyWeightProcess);
+                    }
+                    mService.mHeavyWeightProcess = app;
+                    Message msg = mService.mHandler.obtainMessage(
+                            ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG);
+                    msg.obj = r;
+                    mService.mHandler.sendMessage(msg);
+                }
+            }
+
+        } catch (RemoteException e) {
+            if (r.launchFailed) {
+                // This is the second time we failed -- finish activity
+                // and give up.
+                Slog.e(TAG, "Second failure launching "
+                      + r.intent.getComponent().flattenToShortString()
+                      + ", giving up", e);
+                mService.appDiedLocked(app, app.pid, app.thread);
+                stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null,
+                        "2nd-crash", false);
+                return false;
+            }
+
+            // This is the first time we failed -- restart process and
+            // retry.
+            app.activities.remove(r);
+            throw e;
+        }
+
+        r.launchFailed = false;
+        if (stack.updateLRUListLocked(r)) {
+            Slog.w(TAG, "Activity " + r
+                  + " being launched, but already in LRU list");
+        }
+
+        if (andResume) {
+            // As part of the process of launching, ActivityThread also performs
+            // a resume.
+            stack.minimalResumeActivityLocked(r);
+        } else {
+            // This activity is not starting in the resumed state... which
+            // should look like we asked it to pause+stop (but remain visible),
+            // and it has done so and reported back the current icicle and
+            // other state.
+            if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r
+                    + " (starting in stopped state)");
+            r.state = ActivityState.STOPPED;
+            r.stopped = true;
+        }
+
+        // Launch the new version setup screen if needed.  We do this -after-
+        // launching the initial activity (that is, home), so that it can have
+        // a chance to initialize itself while in the background, making the
+        // switch back to it faster and look better.
+        if (isFrontStack(stack)) {
+            mService.startSetupActivityLocked();
+        }
+
+        return true;
+    }
+
+    void startSpecificActivityLocked(ActivityRecord r,
+            boolean andResume, boolean checkConfig) {
+        // Is this activity's application already running?
+        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
+                r.info.applicationInfo.uid, true);
+
+        r.task.stack.setLaunchTime(r);
+
+        if (app != null && app.thread != null) {
+            try {
+                if ((r.info.flags&ActivityInfo.FLAG_MULTIPROCESS) == 0
+                        || !"android".equals(r.info.packageName)) {
+                    // Don't add this if it is a platform component that is marked
+                    // to run in multiple processes, because this is actually
+                    // part of the framework so doesn't make sense to track as a
+                    // separate apk in the process.
+                    app.addPackage(r.info.packageName, mService.mProcessStats);
+                }
+                realStartActivityLocked(r, app, andResume, checkConfig);
+                return;
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Exception when starting activity "
+                        + r.intent.getComponent().flattenToShortString(), e);
+            }
+
+            // If a dead object exception was thrown -- fall through to
+            // restart the application.
+        }
+
+        mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
+                "activity", r.intent.getComponent(), false, false, true);
+    }
+
+    final int startActivityLocked(IApplicationThread caller,
+            Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
+            String resultWho, int requestCode,
+            int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
+            boolean componentSpecified, ActivityRecord[] outActivity) {
+        int err = ActivityManager.START_SUCCESS;
+
+        ProcessRecord callerApp = null;
+        if (caller != null) {
+            callerApp = mService.getRecordForAppLocked(caller);
+            if (callerApp != null) {
+                callingPid = callerApp.pid;
+                callingUid = callerApp.info.uid;
+            } else {
+                Slog.w(TAG, "Unable to find app for caller " + caller
+                      + " (pid=" + callingPid + ") when starting: "
+                      + intent.toString());
+                err = ActivityManager.START_PERMISSION_DENIED;
+            }
+        }
+
+        if (err == ActivityManager.START_SUCCESS) {
+            final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+            Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
+                    + "} from pid " + (callerApp != null ? callerApp.pid : callingPid));
+        }
+
+        ActivityRecord sourceRecord = null;
+        ActivityRecord resultRecord = null;
+        if (resultTo != null) {
+            sourceRecord = isInAnyStackLocked(resultTo);
+            if (DEBUG_RESULTS) Slog.v(
+                TAG, "Will send result to " + resultTo + " " + sourceRecord);
+            if (sourceRecord != null) {
+                if (requestCode >= 0 && !sourceRecord.finishing) {
+                    resultRecord = sourceRecord;
+                }
+            }
+        }
+        ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack;
+
+        int launchFlags = intent.getFlags();
+
+        if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0
+                && sourceRecord != null) {
+            // Transfer the result target from the source activity to the new
+            // one being started, including any failures.
+            if (requestCode >= 0) {
+                ActivityOptions.abort(options);
+                return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
+            }
+            resultRecord = sourceRecord.resultTo;
+            resultWho = sourceRecord.resultWho;
+            requestCode = sourceRecord.requestCode;
+            sourceRecord.resultTo = null;
+            if (resultRecord != null) {
+                resultRecord.removeResultsLocked(
+                    sourceRecord, resultWho, requestCode);
+            }
+        }
+
+        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
+            // We couldn't find a class that can handle the given Intent.
+            // That's the end of that!
+            err = ActivityManager.START_INTENT_NOT_RESOLVED;
+        }
+
+        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+            // We couldn't find the specific class specified in the Intent.
+            // Also the end of the line.
+            err = ActivityManager.START_CLASS_NOT_FOUND;
+        }
+
+        if (err != ActivityManager.START_SUCCESS) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1,
+                    resultRecord, resultWho, requestCode,
+                    Activity.RESULT_CANCELED, null);
+            }
+            setDismissKeyguard(false);
+            ActivityOptions.abort(options);
+            return err;
+        }
+
+        final int startAnyPerm = mService.checkPermission(
+                START_ANY_ACTIVITY, callingPid, callingUid);
+        final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid,
+                callingUid, aInfo.applicationInfo.uid, aInfo.exported);
+        if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1,
+                    resultRecord, resultWho, requestCode,
+                    Activity.RESULT_CANCELED, null);
+            }
+            setDismissKeyguard(false);
+            String msg;
+            if (!aInfo.exported) {
+                msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")"
+                        + " not exported from uid " + aInfo.applicationInfo.uid;
+            } else {
+                msg = "Permission Denial: starting " + intent.toString()
+                        + " from " + callerApp + " (pid=" + callingPid
+                        + ", uid=" + callingUid + ")"
+                        + " requires " + aInfo.permission;
+            }
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
+                callingPid, resolvedType, aInfo.applicationInfo);
+
+        if (mService.mController != null) {
+            try {
+                // The Intent we give to the watcher has the extra data
+                // stripped off, since it can contain private information.
+                Intent watchIntent = intent.cloneFilter();
+                abort |= !mService.mController.activityStarting(watchIntent,
+                        aInfo.applicationInfo.packageName);
+            } catch (RemoteException e) {
+                mService.mController = null;
+            }
+        }
+
+        if (abort) {
+            if (resultRecord != null) {
+                resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode,
+                        Activity.RESULT_CANCELED, null);
+            }
+            // We pretend to the caller that it was really started, but
+            // they will just get a cancel result.
+            setDismissKeyguard(false);
+            ActivityOptions.abort(options);
+            return ActivityManager.START_SUCCESS;
+        }
+
+        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
+                intent, resolvedType, aInfo, mService.mConfiguration,
+                resultRecord, resultWho, requestCode, componentSpecified, this);
+        if (outActivity != null) {
+            outActivity[0] = r;
+        }
+
+        final ActivityStack stack = getFocusedStack();
+        if (stack.mResumedActivity == null
+                || stack.mResumedActivity.info.applicationInfo.uid != callingUid) {
+            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
+                PendingActivityLaunch pal =
+                        new PendingActivityLaunch(r, sourceRecord, startFlags, stack);
+                mService.mPendingActivityLaunches.add(pal);
+                setDismissKeyguard(false);
+                ActivityOptions.abort(options);
+                return ActivityManager.START_SWITCHES_CANCELED;
+            }
+        }
+
+        if (mService.mDidAppSwitch) {
+            // This is the second allowed switch since we stopped switches,
+            // so now just generally allow switches.  Use case: user presses
+            // home (switches disabled, switch to home, mDidAppSwitch now true);
+            // user taps a home icon (coming from home so allowed, we hit here
+            // and now allow anyone to switch again).
+            mService.mAppSwitchesAllowedTime = 0;
+        } else {
+            mService.mDidAppSwitch = true;
+        }
+
+        mService.doPendingActivityLaunchesLocked(false);
+
+        err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options);
+
+        if (allPausedActivitiesComplete()) {
+            // If someone asked to have the keyguard dismissed on the next
+            // activity start, but we are not actually doing an activity
+            // switch...  just dismiss the keyguard now, because we
+            // probably want to see whatever is behind it.
+            dismissKeyguard();
+        }
+        return err;
+    }
+
+    ActivityStack adjustStackFocus(ActivityRecord r) {
+        final TaskRecord task = r.task;
+        if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) {
+            if (task != null) {
+                final ActivityStack taskStack = task.stack;
+                if (mFocusedStack != taskStack) {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
+                            "adjustStackFocus: Setting focused stack to r=" + r + " task=" + task);
+                    mFocusedStack = taskStack;
+                } else {
+                    if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
+                        "adjustStackFocus: Focused stack already=" + mFocusedStack);
+                }
+                return taskStack;
+            }
+
+            if (mFocusedStack != mHomeStack) {
+                if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
+                        "adjustStackFocus: Have a focused stack=" + mFocusedStack);
+                return mFocusedStack;
+            }
+
+            int numDisplays = mDisplayInfos.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                    ActivityStack stack = stacks.get(stackNdx);
+                    if (!stack.isHomeStack()) {
+                        if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG,
+                                "adjustStackFocus: Setting focused stack=" + stack);
+                        mFocusedStack = stack;
+                        return mFocusedStack;
+                    }
+                }
+            }
+
+            // Need to create an app stack for this user.
+            int stackId = createStackOnDisplay(null, getNextStackId(), Display.DEFAULT_DISPLAY);
+            if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r +
+                    " stackId=" + stackId);
+            mFocusedStack = getStack(stackId);
+            return mFocusedStack;
+        }
+        return mHomeStack;
+    }
+
+    void setFocusedStack(ActivityRecord r) {
+        if (r != null) {
+            final boolean isHomeActivity =
+                    !r.isApplicationActivity() || (r.task != null && !r.task.isApplicationTask());
+            moveHomeStack(isHomeActivity);
+        }
+    }
+
+    final int startActivityUncheckedLocked(ActivityRecord r,
+            ActivityRecord sourceRecord, int startFlags, boolean doResume,
+            Bundle options) {
+        final Intent intent = r.intent;
+        final int callingUid = r.launchedFromUid;
+
+        int launchFlags = intent.getFlags();
+
+        // We'll invoke onUserLeaving before onPause only if the launching
+        // activity did not explicitly state that this is an automated launch.
+        mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
+        if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving);
+
+        // If the caller has asked not to resume at this point, we make note
+        // of this in the record so that we can skip it when trying to find
+        // the top running activity.
+        if (!doResume) {
+            r.delayedResume = true;
+        }
+
+        ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;
+
+        // If the onlyIfNeeded flag is set, then we can do this if the activity
+        // being launched is the same as the one making the call...  or, as
+        // a special case, if we do not know the caller then we count the
+        // current top activity as the caller.
+        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+            ActivityRecord checkedCaller = sourceRecord;
+            if (checkedCaller == null) {
+                checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop);
+            }
+            if (!checkedCaller.realActivity.equals(r.realActivity)) {
+                // Caller is not the same as launcher, so always needed.
+                startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED;
+            }
+        }
+
+        if (sourceRecord == null) {
+            // This activity is not being started from another...  in this
+            // case we -always- start a new task.
+            if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+                Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
+                        "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
+                launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+            }
+        } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+            // The original activity who is starting us is running as a single
+            // instance...  this new activity it is starting must go on its
+            // own task.
+            launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+        } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE
+                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+            // The activity being started is a single instance...  it always
+            // gets launched into its own task.
+            launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+        }
+
+        ActivityInfo newTaskInfo = null;
+        Intent newTaskIntent = null;
+        final ActivityStack sourceStack;
+        if (sourceRecord != null) {
+            if (sourceRecord.finishing) {
+                // If the source is finishing, we can't further count it as our source.  This
+                // is because the task it is associated with may now be empty and on its way out,
+                // so we don't want to blindly throw it in to that task.  Instead we will take
+                // the NEW_TASK flow and try to find a task for it. But save the task information
+                // so it can be used when creating the new task.
+                if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+                    Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+                            + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
+                    launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+                    newTaskInfo = sourceRecord.info;
+                    newTaskIntent = sourceRecord.task.intent;
+                }
+                sourceRecord = null;
+                sourceStack = null;
+            } else {
+                sourceStack = sourceRecord.task.stack;
+            }
+        } else {
+            sourceStack = null;
+        }
+
+        if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            // For whatever reason this activity is being launched into a new
+            // task...  yet the caller has requested a result back.  Well, that
+            // is pretty messed up, so instead immediately send back a cancel
+            // and let the new task continue launched as normal without a
+            // dependency on its originator.
+            Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
+            r.resultTo.task.stack.sendActivityResultLocked(-1,
+                    r.resultTo, r.resultWho, r.requestCode,
+                Activity.RESULT_CANCELED, null);
+            r.resultTo = null;
+        }
+
+        boolean addingToTask = false;
+        boolean movedHome = false;
+        TaskRecord reuseTask = null;
+        ActivityStack targetStack;
+        if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
+                (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
+                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
+                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+            // If bring to front is requested, and no result is requested, and
+            // we can find a task that was started with this same
+            // component, then instead of launching bring that one to the front.
+            if (r.resultTo == null) {
+                // See if there is a task to bring to the front.  If this is
+                // a SINGLE_INSTANCE activity, there can be one and only one
+                // instance of it in the history, and it is always in its own
+                // unique task, so we do a special search.
+                ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE
+                        ? findTaskLocked(r)
+                        : findActivityLocked(intent, r.info);
+                if (intentActivity != null) {
+                    if (r.task == null) {
+                        r.task = intentActivity.task;
+                    }
+                    targetStack = intentActivity.task.stack;
+                    targetStack.mLastPausedActivity = null;
+                    if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack
+                            + " from " + intentActivity);
+                    moveHomeStack(targetStack.isHomeStack());
+                    if (intentActivity.task.intent == null) {
+                        // This task was started because of movement of
+                        // the activity based on affinity...  now that we
+                        // are actually launching it, we can assign the
+                        // base intent.
+                        intentActivity.task.setIntent(intent, r.info);
+                    }
+                    // If the target task is not in the front, then we need
+                    // to bring it to the front...  except...  well, with
+                    // SINGLE_TASK_LAUNCH it's not entirely clear.  We'd like
+                    // to have the same behavior as if a new instance was
+                    // being started, which means not bringing it to the front
+                    // if the caller is not itself in the front.
+                    final ActivityStack lastStack = getLastStack();
+                    ActivityRecord curTop = lastStack == null?
+                            null : lastStack.topRunningNonDelayedActivityLocked(notTop);
+                    if (curTop != null && (curTop.task != intentActivity.task ||
+                            curTop.task != lastStack.topTask())) {
+                        r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+                        if (sourceRecord == null || (sourceStack.topActivity() != null &&
+                                sourceStack.topActivity().task == sourceRecord.task)) {
+                            // We really do want to push this one into the
+                            // user's face, right now.
+                            movedHome = true;
+                            targetStack.moveTaskToFrontLocked(intentActivity.task, r, options);
+                            if ((launchFlags &
+                                    (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
+                                    == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
+                                // Caller wants to appear on home activity.
+                                intentActivity.task.mOnTopOfHome = true;
+                            }
+                            options = null;
+                        }
+                    }
+                    // If the caller has requested that the target task be
+                    // reset, then do so.
+                    if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                        intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
+                    }
+                    if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+                        // We don't need to start a new activity, and
+                        // the client said not to do anything if that
+                        // is the case, so this is it!  And for paranoia, make
+                        // sure we have correctly resumed the top activity.
+                        if (doResume) {
+                            resumeTopActivitiesLocked(targetStack, null, options);
+                        } else {
+                            ActivityOptions.abort(options);
+                        }
+                        if (r.task == null)  Slog.v(TAG,
+                                "startActivityUncheckedLocked: task left null",
+                                new RuntimeException("here").fillInStackTrace());
+                        return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+                    }
+                    if ((launchFlags &
+                            (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
+                            == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) {
+                        // The caller has requested to completely replace any
+                        // existing task with its new activity.  Well that should
+                        // not be too hard...
+                        reuseTask = intentActivity.task;
+                        reuseTask.performClearTaskLocked();
+                        reuseTask.setIntent(r.intent, r.info);
+                    } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
+                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
+                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
+                        // In this situation we want to remove all activities
+                        // from the task up to the one being started.  In most
+                        // cases this means we are resetting the task to its
+                        // initial state.
+                        ActivityRecord top =
+                                intentActivity.task.performClearTaskLocked(r, launchFlags);
+                        if (top != null) {
+                            if (top.frontOfTask) {
+                                // Activity aliases may mean we use different
+                                // intents for the top activity, so make sure
+                                // the task now has the identity of the new
+                                // intent.
+                                top.task.setIntent(r.intent, r.info);
+                            }
+                            ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT,
+                                    r, top.task);
+                            top.deliverNewIntentLocked(callingUid, r.intent);
+                        } else {
+                            // A special case: we need to
+                            // start the activity because it is not currently
+                            // running, and the caller has asked to clear the
+                            // current task to have this activity at the top.
+                            addingToTask = true;
+                            // Now pretend like this activity is being started
+                            // by the top of its task, so it is put in the
+                            // right place.
+                            sourceRecord = intentActivity;
+                        }
+                    } else if (r.realActivity.equals(intentActivity.task.realActivity)) {
+                        // In this case the top activity on the task is the
+                        // same as the one being launched, so we take that
+                        // as a request to bring the task to the foreground.
+                        // If the top activity in the task is the root
+                        // activity, deliver this new intent to it if it
+                        // desires.
+                        if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+                                || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP)
+                                && intentActivity.realActivity.equals(r.realActivity)) {
+                            ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,
+                                    intentActivity.task);
+                            if (intentActivity.frontOfTask) {
+                                intentActivity.task.setIntent(r.intent, r.info);
+                            }
+                            intentActivity.deliverNewIntentLocked(callingUid, r.intent);
+                        } else if (!r.intent.filterEquals(intentActivity.task.intent)) {
+                            // In this case we are launching the root activity
+                            // of the task, but with a different intent.  We
+                            // should start a new instance on top.
+                            addingToTask = true;
+                            sourceRecord = intentActivity;
+                        }
+                    } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+                        // In this case an activity is being launched in to an
+                        // existing task, without resetting that task.  This
+                        // is typically the situation of launching an activity
+                        // from a notification or shortcut.  We want to place
+                        // the new activity on top of the current task.
+                        addingToTask = true;
+                        sourceRecord = intentActivity;
+                    } else if (!intentActivity.task.rootWasReset) {
+                        // In this case we are launching in to an existing task
+                        // that has not yet been started from its front door.
+                        // The current task has been brought to the front.
+                        // Ideally, we'd probably like to place this new task
+                        // at the bottom of its stack, but that's a little hard
+                        // to do with the current organization of the code so
+                        // for now we'll just drop it.
+                        intentActivity.task.setIntent(r.intent, r.info);
+                    }
+                    if (!addingToTask && reuseTask == null) {
+                        // We didn't do anything...  but it was needed (a.k.a., client
+                        // don't use that intent!)  And for paranoia, make
+                        // sure we have correctly resumed the top activity.
+                        if (doResume) {
+                            targetStack.resumeTopActivityLocked(null, options);
+                        } else {
+                            ActivityOptions.abort(options);
+                        }
+                        if (r.task == null)  Slog.v(TAG,
+                            "startActivityUncheckedLocked: task left null",
+                            new RuntimeException("here").fillInStackTrace());
+                        return ActivityManager.START_TASK_TO_FRONT;
+                    }
+                }
+            }
+        }
+
+        //String uri = r.intent.toURI();
+        //Intent intent2 = new Intent(uri);
+        //Slog.i(TAG, "Given intent: " + r.intent);
+        //Slog.i(TAG, "URI is: " + uri);
+        //Slog.i(TAG, "To intent: " + intent2);
+
+        if (r.packageName != null) {
+            // If the activity being launched is the same as the one currently
+            // at the top, then we need to check if it should only be launched
+            // once.
+            ActivityStack topStack = getFocusedStack();
+            ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
+            if (top != null && r.resultTo == null) {
+                if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
+                    if (top.app != null && top.app.thread != null) {
+                        if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
+                            || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
+                            ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,
+                                    top.task);
+                            // For paranoia, make sure we have correctly
+                            // resumed the top activity.
+                            topStack.mLastPausedActivity = null;
+                            if (doResume) {
+                                resumeTopActivitiesLocked();
+                            }
+                            ActivityOptions.abort(options);
+                            if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+                                // We don't need to start a new activity, and
+                                // the client said not to do anything if that
+                                // is the case, so this is it!
+                                if (r.task == null)  Slog.v(TAG,
+                                    "startActivityUncheckedLocked: task left null",
+                                    new RuntimeException("here").fillInStackTrace());
+                                return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+                            }
+                            top.deliverNewIntentLocked(callingUid, r.intent);
+                            if (r.task == null)  Slog.v(TAG,
+                                "startActivityUncheckedLocked: task left null",
+                                new RuntimeException("here").fillInStackTrace());
+                            return ActivityManager.START_DELIVERED_TO_TOP;
+                        }
+                    }
+                }
+            }
+
+        } else {
+            if (r.resultTo != null) {
+                r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho,
+                        r.requestCode, Activity.RESULT_CANCELED, null);
+            }
+            ActivityOptions.abort(options);
+            if (r.task == null)  Slog.v(TAG,
+                "startActivityUncheckedLocked: task left null",
+                new RuntimeException("here").fillInStackTrace());
+            return ActivityManager.START_CLASS_NOT_FOUND;
+        }
+
+        boolean newTask = false;
+        boolean keepCurTransition = false;
+
+        // Should this be considered a new task?
+        if (r.resultTo == null && !addingToTask
+                && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            targetStack = adjustStackFocus(r);
+            moveHomeStack(targetStack.isHomeStack());
+            if (reuseTask == null) {
+                r.setTask(targetStack.createTaskRecord(getNextTaskId(),
+                        newTaskInfo != null ? newTaskInfo : r.info,
+                        newTaskIntent != null ? newTaskIntent : intent,
+                        true), null, true);
+                if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " +
+                        r.task);
+            } else {
+                r.setTask(reuseTask, reuseTask, true);
+            }
+            newTask = true;
+            if (!movedHome) {
+                if ((launchFlags &
+                        (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME))
+                        == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) {
+                    // Caller wants to appear on home activity, so before starting
+                    // their own activity we will bring home to the front.
+                    r.task.mOnTopOfHome = true;
+                }
+            }
+        } else if (sourceRecord != null) {
+            TaskRecord sourceTask = sourceRecord.task;
+            targetStack = sourceTask.stack;
+            moveHomeStack(targetStack.isHomeStack());
+            if (!addingToTask &&
+                    (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+                // In this case, we are adding the activity to an existing
+                // task, but the caller has asked to clear that task if the
+                // activity is already running.
+                ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags);
+                keepCurTransition = true;
+                if (top != null) {
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
+                    top.deliverNewIntentLocked(callingUid, r.intent);
+                    // For paranoia, make sure we have correctly
+                    // resumed the top activity.
+                    targetStack.mLastPausedActivity = null;
+                    if (doResume) {
+                        targetStack.resumeTopActivityLocked(null);
+                    }
+                    ActivityOptions.abort(options);
+                    if (r.task == null)  Slog.w(TAG,
+                        "startActivityUncheckedLocked: task left null",
+                        new RuntimeException("here").fillInStackTrace());
+                    return ActivityManager.START_DELIVERED_TO_TOP;
+                }
+            } else if (!addingToTask &&
+                    (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
+                // In this case, we are launching an activity in our own task
+                // that may already be running somewhere in the history, and
+                // we want to shuffle it to the front of the stack if so.
+                final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r);
+                if (top != null) {
+                    final TaskRecord task = top.task;
+                    task.moveActivityToFrontLocked(top);
+                    ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task);
+                    top.updateOptionsLocked(options);
+                    top.deliverNewIntentLocked(callingUid, r.intent);
+                    targetStack.mLastPausedActivity = null;
+                    if (doResume) {
+                        targetStack.resumeTopActivityLocked(null);
+                    }
+                    return ActivityManager.START_DELIVERED_TO_TOP;
+                }
+            }
+            // An existing activity is starting this new activity, so we want
+            // to keep the new one in the same task as the one that is starting
+            // it.
+            r.setTask(sourceTask, sourceRecord.thumbHolder, false);
+            if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+                    + " in existing task " + r.task + " from source " + sourceRecord);
+
+        } else {
+            // This not being started from an existing activity, and not part
+            // of a new task...  just put it in the top task, though these days
+            // this case should never happen.
+            targetStack = adjustStackFocus(r);
+            moveHomeStack(targetStack.isHomeStack());
+            ActivityRecord prev = targetStack.topActivity();
+            r.setTask(prev != null ? prev.task
+                    : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true),
+                    null, true);
+            if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+                    + " in new guessed " + r.task);
+        }
+
+        mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
+                intent, r.getUriPermissionsLocked());
+
+        if (newTask) {
+            EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId);
+        }
+        ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
+        targetStack.mLastPausedActivity = null;
+        targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);
+        mService.setFocusedActivityLocked(r);
+        return ActivityManager.START_SUCCESS;
+    }
+
+    void acquireLaunchWakelock() {
+        if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
+            throw new IllegalStateException("Calling must be system uid");
+        }
+        mLaunchingActivity.acquire();
+        if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) {
+            // To be safe, don't allow the wake lock to be held for too long.
+            mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT);
+        }
+    }
+
+    // Checked.
+    final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
+            Configuration config) {
+        if (localLOGV) Slog.v(TAG, "Activity idle: " + token);
+
+        ArrayList<ActivityRecord> stops = null;
+        ArrayList<ActivityRecord> finishes = null;
+        ArrayList<UserStartedState> startingUsers = null;
+        int NS = 0;
+        int NF = 0;
+        IApplicationThread sendThumbnail = null;
+        boolean booting = false;
+        boolean enableScreen = false;
+        boolean activityRemoved = false;
+
+        ActivityRecord r = ActivityRecord.forToken(token);
+        if (r != null) {
+            if (DEBUG_IDLE) Slog.d(TAG, "activityIdleInternalLocked: Callers=" +
+                    Debug.getCallers(4));
+            mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
+            r.finishLaunchTickingLocked();
+            if (fromTimeout) {
+                reportActivityLaunchedLocked(fromTimeout, r, -1, -1);
+            }
+
+            // This is a hack to semi-deal with a race condition
+            // in the client where it can be constructed with a
+            // newer configuration from when we asked it to launch.
+            // We'll update with whatever configuration it now says
+            // it used to launch.
+            if (config != null) {
+                r.configuration = config;
+            }
+
+            // We are now idle.  If someone is waiting for a thumbnail from
+            // us, we can now deliver.
+            r.idle = true;
+
+            if (r.thumbnailNeeded && r.app != null && r.app.thread != null) {
+                sendThumbnail = r.app.thread;
+                r.thumbnailNeeded = false;
+            }
+
+            //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout);
+            if (!mService.mBooted && isFrontStack(r.task.stack)) {
+                mService.mBooted = true;
+                enableScreen = true;
+            }
+        }
+
+        if (allResumedActivitiesIdle()) {
+            if (r != null) {
+                mService.scheduleAppGcsLocked();
+            }
+
+            if (mLaunchingActivity.isHeld()) {
+                mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
+                if (VALIDATE_WAKE_LOCK_CALLER &&
+                        Binder.getCallingUid() != Process.myUid()) {
+                    throw new IllegalStateException("Calling must be system uid");
+                }
+                mLaunchingActivity.release();
+            }
+            ensureActivitiesVisibleLocked(null, 0);
+        }
+
+        // Atomically retrieve all of the other things to do.
+        stops = processStoppingActivitiesLocked(true);
+        NS = stops != null ? stops.size() : 0;
+        if ((NF=mFinishingActivities.size()) > 0) {
+            finishes = new ArrayList<ActivityRecord>(mFinishingActivities);
+            mFinishingActivities.clear();
+        }
+
+        final ArrayList<ActivityRecord> thumbnails;
+        final int NT = mCancelledThumbnails.size();
+        if (NT > 0) {
+            thumbnails = new ArrayList<ActivityRecord>(mCancelledThumbnails);
+            mCancelledThumbnails.clear();
+        } else {
+            thumbnails = null;
+        }
+
+        if (isFrontStack(mHomeStack)) {
+            booting = mService.mBooting;
+            mService.mBooting = false;
+        }
+
+        if (mStartingUsers.size() > 0) {
+            startingUsers = new ArrayList<UserStartedState>(mStartingUsers);
+            mStartingUsers.clear();
+        }
+
+        // Perform the following actions from unsynchronized state.
+        final IApplicationThread thumbnailThread = sendThumbnail;
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (thumbnailThread != null) {
+                    try {
+                        thumbnailThread.requestThumbnail(token);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Exception thrown when requesting thumbnail", e);
+                        mService.sendPendingThumbnail(null, token, null, null, true);
+                    }
+                }
+
+                // Report back to any thumbnail receivers.
+                for (int i = 0; i < NT; i++) {
+                    ActivityRecord r = thumbnails.get(i);
+                    mService.sendPendingThumbnail(r, null, null, null, true);
+                }
+            }
+        });
+
+        // Stop any activities that are scheduled to do so but have been
+        // waiting for the next one to start.
+        for (int i = 0; i < NS; i++) {
+            r = stops.get(i);
+            final ActivityStack stack = r.task.stack;
+            if (r.finishing) {
+                stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false);
+            } else {
+                stack.stopActivityLocked(r);
+            }
+        }
+
+        // Finish any activities that are scheduled to do so but have been
+        // waiting for the next one to start.
+        for (int i = 0; i < NF; i++) {
+            r = finishes.get(i);
+            activityRemoved |= r.task.stack.destroyActivityLocked(r, true, false, "finish-idle");
+        }
+
+        if (booting) {
+            mService.finishBooting();
+        } else if (startingUsers != null) {
+            for (int i = 0; i < startingUsers.size(); i++) {
+                mService.finishUserSwitch(startingUsers.get(i));
+            }
+        }
+
+        mService.trimApplications();
+        //dump();
+        //mWindowManager.dump();
+
+        if (enableScreen) {
+            mService.enableScreenAfterBoot();
+        }
+
+        if (activityRemoved) {
+            resumeTopActivitiesLocked();
+        }
+
+        return r;
+    }
+
+    boolean handleAppDiedLocked(ProcessRecord app) {
+        boolean hasVisibleActivities = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                hasVisibleActivities |= stacks.get(stackNdx).handleAppDiedLocked(app);
+            }
+        }
+        return hasVisibleActivities;
+    }
+
+    void closeSystemDialogsLocked() {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                stacks.get(stackNdx).closeSystemDialogsLocked();
+            }
+        }
+    }
+
+    void removeUserLocked(int userId) {
+        mUserStackInFront.delete(userId);
+    }
+
+    /**
+     * @return true if some activity was finished (or would have finished if doit were true).
+     */
+    boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) {
+        boolean didSomething = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (stack.forceStopPackageLocked(name, doit, evenPersistent, userId)) {
+                    didSomething = true;
+                }
+            }
+        }
+        return didSomething;
+    }
+
+    void updatePreviousProcessLocked(ActivityRecord r) {
+        // Now that this process has stopped, we may want to consider
+        // it to be the previous app to try to keep around in case
+        // the user wants to return to it.
+
+        // First, found out what is currently the foreground app, so that
+        // we don't blow away the previous app if this activity is being
+        // hosted by the process that is actually still the foreground.
+        ProcessRecord fgApp = null;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (isFrontStack(stack)) {
+                    if (stack.mResumedActivity != null) {
+                        fgApp = stack.mResumedActivity.app;
+                    } else if (stack.mPausingActivity != null) {
+                        fgApp = stack.mPausingActivity.app;
+                    }
+                    break;
+                }
+            }
+        }
+
+        // Now set this one as the previous process, only if that really
+        // makes sense to.
+        if (r.app != null && fgApp != null && r.app != fgApp
+                && r.lastVisibleTime > mService.mPreviousProcessVisibleTime
+                && r.app != mService.mHomeProcess) {
+            mService.mPreviousProcess = r.app;
+            mService.mPreviousProcessVisibleTime = r.lastVisibleTime;
+        }
+    }
+
+    boolean resumeTopActivitiesLocked() {
+        return resumeTopActivitiesLocked(null, null, null);
+    }
+
+    boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target,
+            Bundle targetOptions) {
+        if (targetStack == null) {
+            targetStack = getFocusedStack();
+        }
+        boolean result = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (isFrontStack(stack)) {
+                    if (stack == targetStack) {
+                        result = stack.resumeTopActivityLocked(target, targetOptions);
+                    } else {
+                        stack.resumeTopActivityLocked(null);
+                    }
+                }
+            }
+        }
+        return result;
+    }
+
+    void finishTopRunningActivityLocked(ProcessRecord app) {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                stack.finishTopRunningActivityLocked(app);
+            }
+        }
+    }
+
+    void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                if (stacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) {
+                    if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack="
+                            + stacks.get(stackNdx));
+                    return;
+                }
+            }
+        }
+    }
+
+    ActivityStack getStack(int stackId) {
+        ActivityContainer activityContainer = mActivityContainers.get(stackId);
+        if (activityContainer != null) {
+            return activityContainer.mStack;
+        }
+        return null;
+    }
+
+    ArrayList<ActivityStack> getStacks() {
+        ArrayList<ActivityStack> allStacks = new ArrayList<ActivityStack>();
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            allStacks.addAll(mDisplayInfos.valueAt(displayNdx).stacks);
+        }
+        return allStacks;
+    }
+
+    IBinder getHomeActivityToken() {
+        final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks();
+        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = tasks.get(taskNdx);
+            if (task.isHomeTask()) {
+                final ArrayList<ActivityRecord> activities = task.mActivities;
+                for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                    final ActivityRecord r = activities.get(activityNdx);
+                    if (r.isHomeActivity()) {
+                        return r.appToken;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    ActivityContainer createActivityContainer(ActivityRecord parentActivity, int stackId,
+            IActivityContainerCallback callback) {
+        ActivityContainer activityContainer = new ActivityContainer(parentActivity, stackId,
+                callback);
+        mActivityContainers.put(stackId, activityContainer);
+        if (parentActivity != null) {
+            parentActivity.mChildContainers.add(activityContainer.mStack);
+        }
+        return activityContainer;
+    }
+
+    ActivityContainer createActivityContainer(ActivityRecord parentActivity,
+            IActivityContainerCallback callback) {
+        return createActivityContainer(parentActivity, getNextStackId(), callback);
+    }
+
+    private int createStackOnDisplay(ActivityRecord parentActivity, int stackId, int displayId) {
+        ActivityDisplayInfo displayInfo = mDisplayInfos.get(displayId);
+        if (displayInfo == null) {
+            return -1;
+        }
+
+        ActivityContainer activityContainer =
+                createActivityContainer(parentActivity, stackId, null);
+        activityContainer.attachToDisplayLocked(displayInfo);
+        return stackId;
+    }
+
+    int getNextStackId() {
+        while (true) {
+            if (++mLastStackId <= HOME_STACK_ID) {
+                mLastStackId = HOME_STACK_ID + 1;
+            }
+            if (getStack(mLastStackId) == null) {
+                break;
+            }
+        }
+        return mLastStackId;
+    }
+
+    void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        final TaskRecord task = anyTaskForIdLocked(taskId);
+        if (task == null) {
+            return;
+        }
+        final ActivityStack stack = getStack(stackId);
+        if (stack == null) {
+            Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId);
+            return;
+        }
+        removeTask(task);
+        stack.addTask(task, toTop);
+        mWindowManager.addTask(taskId, stackId, toTop);
+        resumeTopActivitiesLocked();
+    }
+
+    ActivityRecord findTaskLocked(ActivityRecord r) {
+        if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r);
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (!r.isApplicationActivity() && !stack.isHomeStack()) {
+                    if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack);
+                    continue;
+                }
+                final ActivityRecord ar = stack.findTaskLocked(r);
+                if (ar != null) {
+                    return ar;
+                }
+            }
+        }
+        if (DEBUG_TASKS) Slog.d(TAG, "No task found");
+        return null;
+    }
+
+    ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityRecord ar = stacks.get(stackNdx).findActivityLocked(intent, info);
+                if (ar != null) {
+                    return ar;
+                }
+            }
+        }
+        return null;
+    }
+
+    void goingToSleepLocked() {
+        scheduleSleepTimeout();
+        if (!mGoingToSleep.isHeld()) {
+            mGoingToSleep.acquire();
+            if (mLaunchingActivity.isHeld()) {
+                if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
+                    throw new IllegalStateException("Calling must be system uid");
+                }
+                mLaunchingActivity.release();
+                mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
+            }
+        }
+        checkReadyForSleepLocked();
+    }
+
+    boolean shutdownLocked(int timeout) {
+        boolean timedout = false;
+        goingToSleepLocked();
+
+        final long endTime = System.currentTimeMillis() + timeout;
+        while (true) {
+            boolean cantShutdown = false;
+            for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+                final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                    cantShutdown |= stacks.get(stackNdx).checkReadyForSleepLocked();
+                }
+            }
+            if (cantShutdown) {
+                long timeRemaining = endTime - System.currentTimeMillis();
+                if (timeRemaining > 0) {
+                    try {
+                        mService.wait(timeRemaining);
+                    } catch (InterruptedException e) {
+                    }
+                } else {
+                    Slog.w(TAG, "Activity manager shutdown timed out");
+                    timedout = true;
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+
+        // Force checkReadyForSleep to complete.
+        mSleepTimeout = true;
+        checkReadyForSleepLocked();
+
+        return timedout;
+    }
+
+    void comeOutOfSleepIfNeededLocked() {
+        removeSleepTimeouts();
+        if (mGoingToSleep.isHeld()) {
+            mGoingToSleep.release();
+        }
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                stack.awakeFromSleepingLocked();
+                if (isFrontStack(stack)) {
+                    resumeTopActivitiesLocked();
+                }
+            }
+        }
+        mGoingToSleepActivities.clear();
+    }
+
+    void activitySleptLocked(ActivityRecord r) {
+        mGoingToSleepActivities.remove(r);
+        checkReadyForSleepLocked();
+    }
+
+    void checkReadyForSleepLocked() {
+        if (!mService.isSleepingOrShuttingDown()) {
+            // Do not care.
+            return;
+        }
+
+        if (!mSleepTimeout) {
+            boolean dontSleep = false;
+            for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+                final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                    dontSleep |= stacks.get(stackNdx).checkReadyForSleepLocked();
+                }
+            }
+
+            if (mStoppingActivities.size() > 0) {
+                // Still need to tell some activities to stop; can't sleep yet.
+                if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop "
+                        + mStoppingActivities.size() + " activities");
+                scheduleIdleLocked();
+                dontSleep = true;
+            }
+
+            if (mGoingToSleepActivities.size() > 0) {
+                // Still need to tell some activities to sleep; can't sleep yet.
+                if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep "
+                        + mGoingToSleepActivities.size() + " activities");
+                dontSleep = true;
+            }
+
+            if (dontSleep) {
+                return;
+            }
+        }
+
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                stacks.get(stackNdx).goToSleep();
+            }
+        }
+
+        removeSleepTimeouts();
+
+        if (mGoingToSleep.isHeld()) {
+            mGoingToSleep.release();
+        }
+        if (mService.mShuttingDown) {
+            mService.notifyAll();
+        }
+    }
+
+    boolean reportResumedActivityLocked(ActivityRecord r) {
+        final ActivityStack stack = r.task.stack;
+        if (isFrontStack(stack)) {
+            mService.updateUsageStats(r, true);
+        }
+        if (allResumedActivitiesComplete()) {
+            ensureActivitiesVisibleLocked(null, 0);
+            mWindowManager.executeAppTransition();
+            return true;
+        }
+        return false;
+    }
+
+    void handleAppCrashLocked(ProcessRecord app) {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                stack.handleAppCrashLocked(app);
+            }
+        }
+    }
+
+    void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) {
+        // First the front stacks. In case any are not fullscreen and are in front of home.
+        boolean showHomeBehindStack = false;
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            final int topStackNdx = stacks.size() - 1;
+            for (int stackNdx = topStackNdx; stackNdx >= 0; --stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                if (stackNdx == topStackNdx) {
+                    // Top stack.
+                    showHomeBehindStack =
+                            stack.ensureActivitiesVisibleLocked(starting, configChanges);
+                } else {
+                    // Back stack.
+                    stack.ensureActivitiesVisibleLocked(starting, configChanges,
+                            showHomeBehindStack);
+                }
+            }
+        }
+    }
+
+    void scheduleDestroyAllActivities(ProcessRecord app, String reason) {
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                stack.scheduleDestroyActivities(app, false, reason);
+            }
+        }
+    }
+
+    boolean switchUserLocked(int userId, UserStartedState uss) {
+        mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
+        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
+        mCurrentUser = userId;
+
+        mStartingUsers.add(uss);
+        for (int displayNdx = mDisplayInfos.size() - 1; displayNdx >= 0; --displayNdx) {
+            final ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                stacks.get(stackNdx).switchUserLocked(userId);
+            }
+        }
+
+        ActivityStack stack = getStack(restoreStackId);
+        if (stack == null) {
+            stack = mHomeStack;
+        }
+        final boolean homeInFront = stack.isHomeStack();
+        moveHomeStack(homeInFront);
+        mWindowManager.moveTaskToTop(stack.topTask().taskId);
+        return homeInFront;
+    }
+
+    final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) {
+        int N = mStoppingActivities.size();
+        if (N <= 0) return null;
+
+        ArrayList<ActivityRecord> stops = null;
+
+        final boolean nowVisible = allResumedActivitiesVisible();
+        for (int i=0; i<N; i++) {
+            ActivityRecord s = mStoppingActivities.get(i);
+            if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible="
+                    + nowVisible + " waitingVisible=" + s.waitingVisible
+                    + " finishing=" + s.finishing);
+            if (s.waitingVisible && nowVisible) {
+                mWaitingVisibleActivities.remove(s);
+                s.waitingVisible = false;
+                if (s.finishing) {
+                    // If this activity is finishing, it is sitting on top of
+                    // everyone else but we now know it is no longer needed...
+                    // so get rid of it.  Otherwise, we need to go through the
+                    // normal flow and hide it once we determine that it is
+                    // hidden by the activities in front of it.
+                    if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s);
+                    mWindowManager.setAppVisibility(s.appToken, false);
+                }
+            }
+            if ((!s.waitingVisible || mService.isSleepingOrShuttingDown()) && remove) {
+                if (localLOGV) Slog.v(TAG, "Ready to stop: " + s);
+                if (stops == null) {
+                    stops = new ArrayList<ActivityRecord>();
+                }
+                stops.add(s);
+                mStoppingActivities.remove(i);
+                N--;
+                i--;
+            }
+        }
+
+        return stops;
+    }
+
+    void validateTopActivitiesLocked() {
+        // FIXME
+/*        for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = stacks.get(stackNdx);
+            final ActivityRecord r = stack.topRunningActivityLocked(null);
+            final ActivityState state = r == null ? ActivityState.DESTROYED : r.state;
+            if (isFrontStack(stack)) {
+                if (r == null) {
+                    Slog.e(TAG, "validateTop...: null top activity, stack=" + stack);
+                } else {
+                    final ActivityRecord pausing = stack.mPausingActivity;
+                    if (pausing != null && pausing == r) {
+                        Slog.e(TAG, "validateTop...: top stack has pausing activity r=" + r +
+                            " state=" + state);
+                    }
+                    if (state != ActivityState.INITIALIZING && state != ActivityState.RESUMED) {
+                        Slog.e(TAG, "validateTop...: activity in front not resumed r=" + r +
+                                " state=" + state);
+                    }
+                }
+            } else {
+                final ActivityRecord resumed = stack.mResumedActivity;
+                if (resumed != null && resumed == r) {
+                    Slog.e(TAG, "validateTop...: back stack has resumed activity r=" + r +
+                        " state=" + state);
+                }
+                if (r != null && (state == ActivityState.INITIALIZING
+                        || state == ActivityState.RESUMED)) {
+                    Slog.e(TAG, "validateTop...: activity in back resumed r=" + r +
+                            " state=" + state);
+                }
+            }
+        }
+*/
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity=");
+                pw.println(mDismissKeyguardOnNextActivity);
+        pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack);
+                pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack);
+        pw.print(prefix); pw.println("mSleepTimeout=" + mSleepTimeout);
+        pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId);
+        pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
+    }
+
+    ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) {
+        return getFocusedStack().getDumpActivitiesLocked(name);
+    }
+
+    static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
+            boolean needSep, String prefix) {
+        if (activity != null) {
+            if (dumpPackage == null || dumpPackage.equals(activity.packageName)) {
+                if (needSep) {
+                    pw.println();
+                }
+                pw.print(prefix);
+                pw.println(activity);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
+            boolean dumpClient, String dumpPackage) {
+        boolean printed = false;
+        boolean needSep = false;
+        for (int displayNdx = 0; displayNdx < mDisplayInfos.size(); ++displayNdx) {
+            ActivityDisplayInfo info = mDisplayInfos.valueAt(displayNdx);
+            pw.print("Display #"); pw.println(info.mDisplayId);
+            ArrayList<ActivityStack> stacks = info.stacks;
+            final int numStacks = stacks.size();
+            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                StringBuilder stackHeader = new StringBuilder(128);
+                stackHeader.append("  Stack #");
+                stackHeader.append(stack.mStackId);
+                stackHeader.append(":");
+                printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
+                        needSep, stackHeader.toString());
+                printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, "    ", "Run", false,
+                        !dumpAll, false, dumpPackage, true,
+                        "    Running activities (most recent first):", null);
+
+                needSep = printed;
+                boolean pr = printThisActivity(pw, stack.mPausingActivity, dumpPackage, needSep,
+                        "    mPausingActivity: ");
+                if (pr) {
+                    printed = true;
+                    needSep = false;
+                }
+                pr = printThisActivity(pw, stack.mResumedActivity, dumpPackage, needSep,
+                        "    mResumedActivity: ");
+                if (pr) {
+                    printed = true;
+                    needSep = false;
+                }
+                if (dumpAll) {
+                    pr = printThisActivity(pw, stack.mLastPausedActivity, dumpPackage, needSep,
+                            "    mLastPausedActivity: ");
+                    if (pr) {
+                        printed = true;
+                        needSep = true;
+                    }
+                    printed |= printThisActivity(pw, stack.mLastNoHistoryActivity, dumpPackage,
+                            needSep, "    mLastNoHistoryActivity: ");
+                }
+                needSep = printed;
+            }
+        }
+
+        printed |= dumpHistoryList(fd, pw, mFinishingActivities, "  ", "Fin", false, !dumpAll,
+                false, dumpPackage, true, "  Activities waiting to finish:", null);
+        printed |= dumpHistoryList(fd, pw, mStoppingActivities, "  ", "Stop", false, !dumpAll,
+                false, dumpPackage, true, "  Activities waiting to stop:", null);
+        printed |= dumpHistoryList(fd, pw, mWaitingVisibleActivities, "  ", "Wait", false, !dumpAll,
+                false, dumpPackage, true, "  Activities waiting for another to become visible:",
+                null);
+        printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, "  ", "Sleep", false, !dumpAll,
+                false, dumpPackage, true, "  Activities waiting to sleep:", null);
+        printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, "  ", "Sleep", false, !dumpAll,
+                false, dumpPackage, true, "  Activities waiting to sleep:", null);
+
+        return printed;
+    }
+
+    static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list,
+            String prefix, String label, boolean complete, boolean brief, boolean client,
+            String dumpPackage, boolean needNL, String header1, String header2) {
+        TaskRecord lastTask = null;
+        String innerPrefix = null;
+        String[] args = null;
+        boolean printed = false;
+        for (int i=list.size()-1; i>=0; i--) {
+            final ActivityRecord r = list.get(i);
+            if (dumpPackage != null && !dumpPackage.equals(r.packageName)) {
+                continue;
+            }
+            if (innerPrefix == null) {
+                innerPrefix = prefix + "      ";
+                args = new String[0];
+            }
+            printed = true;
+            final boolean full = !brief && (complete || !r.isInHistory());
+            if (needNL) {
+                pw.println("");
+                needNL = false;
+            }
+            if (header1 != null) {
+                pw.println(header1);
+                header1 = null;
+            }
+            if (header2 != null) {
+                pw.println(header2);
+                header2 = null;
+            }
+            if (lastTask != r.task) {
+                lastTask = r.task;
+                pw.print(prefix);
+                pw.print(full ? "* " : "  ");
+                pw.println(lastTask);
+                if (full) {
+                    lastTask.dump(pw, prefix + "  ");
+                } else if (complete) {
+                    // Complete + brief == give a summary.  Isn't that obvious?!?
+                    if (lastTask.intent != null) {
+                        pw.print(prefix); pw.print("  ");
+                                pw.println(lastTask.intent.toInsecureStringWithClip());
+                    }
+                }
+            }
+            pw.print(prefix); pw.print(full ? "  * " : "    "); pw.print(label);
+            pw.print(" #"); pw.print(i); pw.print(": ");
+            pw.println(r);
+            if (full) {
+                r.dump(pw, innerPrefix);
+            } else if (complete) {
+                // Complete + brief == give a summary.  Isn't that obvious?!?
+                pw.print(innerPrefix); pw.println(r.intent.toInsecureString());
+                if (r.app != null) {
+                    pw.print(innerPrefix); pw.println(r.app);
+                }
+            }
+            if (client && r.app != null && r.app.thread != null) {
+                // flush anything that is already in the PrintWriter since the thread is going
+                // to write to the file descriptor directly
+                pw.flush();
+                try {
+                    TransferPipe tp = new TransferPipe();
+                    try {
+                        r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(),
+                                r.appToken, innerPrefix, args);
+                        // Short timeout, since blocking here can
+                        // deadlock with the application.
+                        tp.go(fd, 2000);
+                    } finally {
+                        tp.kill();
+                    }
+                } catch (IOException e) {
+                    pw.println(innerPrefix + "Failure while dumping the activity: " + e);
+                } catch (RemoteException e) {
+                    pw.println(innerPrefix + "Got a RemoteException while dumping the activity");
+                }
+                needNL = true;
+            }
+        }
+        return printed;
+    }
+
+    void scheduleIdleTimeoutLocked(ActivityRecord next) {
+        if (DEBUG_IDLE) Slog.d(TAG, "scheduleIdleTimeoutLocked: Callers=" + Debug.getCallers(4));
+        Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next);
+        mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);
+    }
+
+    final void scheduleIdleLocked() {
+        mHandler.sendEmptyMessage(IDLE_NOW_MSG);
+    }
+
+    void removeTimeoutsForActivityLocked(ActivityRecord r) {
+        if (DEBUG_IDLE) Slog.d(TAG, "removeTimeoutsForActivity: Callers=" + Debug.getCallers(4));
+        mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
+    }
+
+    final void scheduleResumeTopActivities() {
+        mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG);
+    }
+
+    void removeSleepTimeouts() {
+        mSleepTimeout = false;
+        mHandler.removeMessages(SLEEP_TIMEOUT_MSG);
+    }
+
+    final void scheduleSleepTimeout() {
+        removeSleepTimeouts();
+        mHandler.sendEmptyMessageDelayed(SLEEP_TIMEOUT_MSG, SLEEP_TIMEOUT);
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_ADDED, displayId, 0));
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_REMOVED, displayId, 0));
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        mHandler.sendMessage(mHandler.obtainMessage(HANDLE_DISPLAY_CHANGED, displayId, 0));
+    }
+
+    public void handleDisplayAddedLocked(int displayId) {
+        synchronized (mService) {
+            ActivityDisplayInfo info = new ActivityDisplayInfo(displayId);
+            mDisplayInfos.put(displayId, info);
+        }
+        mWindowManager.onDisplayAdded(displayId);
+    }
+
+    public void handleDisplayRemovedLocked(int displayId) {
+        synchronized (mService) {
+            ActivityDisplayInfo info = mDisplayInfos.get(displayId);
+            if (info != null) {
+                ArrayList<ActivityStack> stacks = info.stacks;
+                for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
+                    info.detachActivities(stacks.get(stackNdx));
+                }
+                mDisplayInfos.remove(displayId);
+            }
+        }
+        mWindowManager.onDisplayRemoved(displayId);
+    }
+
+    public void handleDisplayChangedLocked(int displayId) {
+        synchronized (mService) {
+            ActivityDisplayInfo info = mDisplayInfos.get(displayId);
+            if (info != null) {
+                // TODO: Update the bounds.
+            }
+        }
+        mWindowManager.onDisplayChanged(displayId);
+    }
+
+    StackInfo getStackInfo(ActivityStack stack) {
+        StackInfo info = new StackInfo();
+        mWindowManager.getStackBounds(stack.mStackId, info.bounds);
+        info.displayId = Display.DEFAULT_DISPLAY;
+        info.stackId = stack.mStackId;
+
+        ArrayList<TaskRecord> tasks = stack.getAllTasks();
+        final int numTasks = tasks.size();
+        int[] taskIds = new int[numTasks];
+        String[] taskNames = new String[numTasks];
+        for (int i = 0; i < numTasks; ++i) {
+            final TaskRecord task = tasks.get(i);
+            taskIds[i] = task.taskId;
+            taskNames[i] = task.origActivity != null ? task.origActivity.flattenToString()
+                    : task.realActivity != null ? task.realActivity.flattenToString()
+                    : task.getTopActivity() != null ? task.getTopActivity().packageName
+                    : "unknown";
+        }
+        info.taskIds = taskIds;
+        info.taskNames = taskNames;
+        return info;
+    }
+
+    StackInfo getStackInfoLocked(int stackId) {
+        ActivityStack stack = getStack(stackId);
+        if (stack != null) {
+            return getStackInfo(stack);
+        }
+        return null;
+    }
+
+    ArrayList<StackInfo> getAllStackInfosLocked() {
+        ArrayList<StackInfo> list = new ArrayList<StackInfo>();
+        for (int displayNdx = 0; displayNdx < mDisplayInfos.size(); ++displayNdx) {
+            ArrayList<ActivityStack> stacks = mDisplayInfos.valueAt(displayNdx).stacks;
+            for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
+                list.add(getStackInfo(stacks.get(ndx)));
+            }
+        }
+        return list;
+    }
+
+    private final class ActivityStackSupervisorHandler extends Handler {
+
+        public ActivityStackSupervisorHandler(Looper looper) {
+            super(looper);
+        }
+
+        void activityIdleInternal(ActivityRecord r) {
+            synchronized (mService) {
+                activityIdleInternalLocked(r != null ? r.appToken : null, true, null);
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case IDLE_TIMEOUT_MSG: {
+                    if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj);
+                    if (mService.mDidDexOpt) {
+                        mService.mDidDexOpt = false;
+                        Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);
+                        nmsg.obj = msg.obj;
+                        mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT);
+                        return;
+                    }
+                    // We don't at this point know if the activity is fullscreen,
+                    // so we need to be conservative and assume it isn't.
+                    activityIdleInternal((ActivityRecord)msg.obj);
+                } break;
+                case IDLE_NOW_MSG: {
+                    if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_NOW_MSG: r=" + msg.obj);
+                    activityIdleInternal((ActivityRecord)msg.obj);
+                } break;
+                case RESUME_TOP_ACTIVITY_MSG: {
+                    synchronized (mService) {
+                        resumeTopActivitiesLocked();
+                    }
+                } break;
+                case SLEEP_TIMEOUT_MSG: {
+                    synchronized (mService) {
+                        if (mService.isSleepingOrShuttingDown()) {
+                            Slog.w(TAG, "Sleep timeout!  Sleeping now.");
+                            mSleepTimeout = true;
+                            checkReadyForSleepLocked();
+                        }
+                    }
+                } break;
+                case LAUNCH_TIMEOUT_MSG: {
+                    if (mService.mDidDexOpt) {
+                        mService.mDidDexOpt = false;
+                        mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT);
+                        return;
+                    }
+                    synchronized (mService) {
+                        if (mLaunchingActivity.isHeld()) {
+                            Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
+                            if (VALIDATE_WAKE_LOCK_CALLER
+                                    && Binder.getCallingUid() != Process.myUid()) {
+                                throw new IllegalStateException("Calling must be system uid");
+                            }
+                            mLaunchingActivity.release();
+                        }
+                    }
+                } break;
+                case HANDLE_DISPLAY_ADDED: {
+                    handleDisplayAddedLocked(msg.arg1);
+                } break;
+                case HANDLE_DISPLAY_CHANGED: {
+                    handleDisplayChangedLocked(msg.arg1);
+                } break;
+                case HANDLE_DISPLAY_REMOVED: {
+                    handleDisplayRemovedLocked(msg.arg1);
+                } break;
+            }
+        }
+    }
+
+    class ActivityContainer extends IActivityContainer.Stub {
+        final int mStackId;
+        final IActivityContainerCallback mCallback;
+        final ActivityStack mStack;
+        final ActivityRecord mParentActivity;
+
+        /** Display this ActivityStack is currently on. Null if not attached to a Display. */
+        ActivityDisplayInfo mActivityDisplayInfo;
+
+        ActivityContainer(ActivityRecord parentActivity, int stackId,
+                IActivityContainerCallback callback) {
+            synchronized (mService) {
+                mStackId = stackId;
+                mStack = new ActivityStack(this);
+                mParentActivity = parentActivity;
+                mCallback = callback;
+            }
+        }
+
+        void attachToDisplayLocked(ActivityDisplayInfo displayInfo) {
+            mActivityDisplayInfo = displayInfo;
+            displayInfo.attachActivities(mStack);
+            mWindowManager.createStack(mStackId, displayInfo.mDisplayId);
+        }
+
+        @Override
+        public void attachToDisplay(int displayId) throws RemoteException {
+            synchronized (mService) {
+                ActivityDisplayInfo displayInfo = mDisplayInfos.get(displayId);
+                if (displayInfo == null) {
+                    return;
+                }
+                attachToDisplayLocked(displayInfo);
+            }
+        }
+
+        @Override
+        public int getStackId() throws RemoteException {
+            return mStack.mStackId;
+        }
+
+        void detachLocked() {
+            if (mActivityDisplayInfo != null) {
+                mActivityDisplayInfo.detachActivities(mStack);
+                mActivityDisplayInfo = null;
+            }
+        }
+
+        @Override
+        public void detachFromDisplay() throws RemoteException {
+            synchronized (mService) {
+                detachLocked();
+            }
+        }
+
+        @Override
+        public void startActivity(Intent intent) throws RemoteException {
+
+        }
+
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+
+        ActivityStackSupervisor getOuter() {
+            return ActivityStackSupervisor.this;
+        }
+
+        boolean isAttached() {
+            return mActivityDisplayInfo != null;
+        }
+
+        void getBounds(Point outBounds) {
+            if (mActivityDisplayInfo != null) {
+                mActivityDisplayInfo.getBounds(outBounds);
+            } else {
+                outBounds.set(0, 0);
+            }
+        }
+    }
+
+    /** Exactly one of these classes per Display in the system. Capable of holding zero or more
+     * attached {@link ActivityStack}s */
+    final class ActivityDisplayInfo {
+        /** Actual Display this object tracks. */
+        final int mDisplayId;
+        final Display mDisplay;
+        final DisplayInfo mDisplayInfo = new DisplayInfo();
+
+        /** All of the stacks on this display. Order matters, topmost stack is in front of all other
+         * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
+        final ArrayList<ActivityStack> stacks = new ArrayList<ActivityStack>();
+
+        ActivityDisplayInfo(int displayId) {
+            mDisplayId = displayId;
+            mDisplay = mDisplayManager.getDisplay(displayId);
+            mDisplay.getDisplayInfo(mDisplayInfo);
+        }
+
+        void attachActivities(ActivityStack stack) {
+            stacks.add(stack);
+        }
+
+        void detachActivities(ActivityStack stack) {
+            stacks.remove(stack);
+        }
+
+        void getBounds(Point bounds) {
+            mDisplay.getDisplayInfo(mDisplayInfo);
+            bounds.x = mDisplayInfo.appWidth;
+            bounds.y = mDisplayInfo.appHeight;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppBindRecord.java b/services/core/java/com/android/server/am/AppBindRecord.java
new file mode 100644
index 0000000..06265fd
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppBindRecord.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * An association between a service and one of its client applications.
+ */
+final class AppBindRecord {
+    final ServiceRecord service;    // The running service.
+    final IntentBindRecord intent;  // The intent we are bound to.
+    final ProcessRecord client;     // Who has started/bound the service.
+
+    final HashSet<ConnectionRecord> connections = new HashSet<ConnectionRecord>();
+                                    // All ConnectionRecord for this client.
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "service=" + service);
+        pw.println(prefix + "client=" + client);
+        dumpInIntentBind(pw, prefix);
+    }
+
+    void dumpInIntentBind(PrintWriter pw, String prefix) {
+        if (connections.size() > 0) {
+            pw.println(prefix + "Per-process Connections:");
+            Iterator<ConnectionRecord> it = connections.iterator();
+            while (it.hasNext()) {
+                ConnectionRecord c = it.next();
+                pw.println(prefix + "  " + c);
+            }
+        }
+    }
+
+    AppBindRecord(ServiceRecord _service, IntentBindRecord _intent,
+            ProcessRecord _client) {
+        service = _service;
+        intent = _intent;
+        client = _client;
+    }
+
+    public String toString() {
+        return "AppBindRecord{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + service.shortName + ":" + client.processName + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
new file mode 100644
index 0000000..0ba62c5
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.view.WindowManager;
+
+final class AppErrorDialog extends BaseErrorDialog {
+    private final ActivityManagerService mService;
+    private final AppErrorResult mResult;
+    private final ProcessRecord mProc;
+
+    // Event 'what' codes
+    static final int FORCE_QUIT = 0;
+    static final int FORCE_QUIT_AND_REPORT = 1;
+
+    // 5-minute timeout, then we automatically dismiss the crash dialog
+    static final long DISMISS_TIMEOUT = 1000 * 60 * 5;
+    
+    public AppErrorDialog(Context context, ActivityManagerService service,
+            AppErrorResult result, ProcessRecord app) {
+        super(context);
+        
+        Resources res = context.getResources();
+        
+        mService = service;
+        mProc = app;
+        mResult = result;
+        CharSequence name;
+        if ((app.pkgList.size() == 1) &&
+                (name=context.getPackageManager().getApplicationLabel(app.info)) != null) {
+            setMessage(res.getString(
+                    com.android.internal.R.string.aerr_application,
+                    name.toString(), app.info.processName));
+        } else {
+            name = app.processName;
+            setMessage(res.getString(
+                    com.android.internal.R.string.aerr_process,
+                    name.toString()));
+        }
+
+        setCancelable(false);
+
+        setButton(DialogInterface.BUTTON_POSITIVE,
+                res.getText(com.android.internal.R.string.force_close),
+                mHandler.obtainMessage(FORCE_QUIT));
+
+        if (app.errorReportReceiver != null) {
+            setButton(DialogInterface.BUTTON_NEGATIVE,
+                    res.getText(com.android.internal.R.string.report),
+                    mHandler.obtainMessage(FORCE_QUIT_AND_REPORT));
+        }
+
+        setTitle(res.getText(com.android.internal.R.string.aerr_title));
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.setTitle("Application Error: " + app.info.processName);
+        attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
+                | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        getWindow().setAttributes(attrs);
+        if (app.persistent) {
+            getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+        }
+
+        // After the timeout, pretend the user clicked the quit button
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(FORCE_QUIT),
+                DISMISS_TIMEOUT);
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            synchronized (mService) {
+                if (mProc != null && mProc.crashDialog == AppErrorDialog.this) {
+                    mProc.crashDialog = null;
+                }
+            }
+            mResult.set(msg.what);
+
+            // If this is a timeout we won't be automatically closed, so go
+            // ahead and explicitly dismiss ourselves just in case.
+            dismiss();
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/am/AppErrorResult.java b/services/core/java/com/android/server/am/AppErrorResult.java
new file mode 100644
index 0000000..c6a5720
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppErrorResult.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+final class AppErrorResult {
+    public void set(int res) {
+        synchronized (this) {
+            mHasResult = true;
+            mResult = res;
+            notifyAll();
+        }
+    }
+
+    public int get() {
+        synchronized (this) {
+            while (!mHasResult) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        return mResult;
+    }
+
+    boolean mHasResult = false;
+    int mResult;
+}
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
new file mode 100644
index 0000000..f4c1664
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Slog;
+import android.view.WindowManager;
+
+final class AppNotRespondingDialog extends BaseErrorDialog {
+    private static final String TAG = "AppNotRespondingDialog";
+
+    // Event 'what' codes
+    static final int FORCE_CLOSE = 1;
+    static final int WAIT = 2;
+    static final int WAIT_AND_REPORT = 3;
+
+    private final ActivityManagerService mService;
+    private final ProcessRecord mProc;
+    
+    public AppNotRespondingDialog(ActivityManagerService service, Context context,
+            ProcessRecord app, ActivityRecord activity, boolean aboveSystem) {
+        super(context);
+        
+        mService = service;
+        mProc = app;
+        Resources res = context.getResources();
+        
+        setCancelable(false);
+
+        int resid;
+        CharSequence name1 = activity != null
+                ? activity.info.loadLabel(context.getPackageManager())
+                : null;
+        CharSequence name2 = null;
+        if ((app.pkgList.size() == 1) &&
+                (name2=context.getPackageManager().getApplicationLabel(app.info)) != null) {
+            if (name1 != null) {
+                resid = com.android.internal.R.string.anr_activity_application;
+            } else {
+                name1 = name2;
+                name2 = app.processName;
+                resid = com.android.internal.R.string.anr_application_process;
+            }
+        } else {
+            if (name1 != null) {
+                name2 = app.processName;
+                resid = com.android.internal.R.string.anr_activity_process;
+            } else {
+                name1 = app.processName;
+                resid = com.android.internal.R.string.anr_process;
+            }
+        }
+
+        setMessage(name2 != null
+                ? res.getString(resid, name1.toString(), name2.toString())
+                : res.getString(resid, name1.toString()));
+
+        setButton(DialogInterface.BUTTON_POSITIVE,
+                res.getText(com.android.internal.R.string.force_close),
+                mHandler.obtainMessage(FORCE_CLOSE));
+        setButton(DialogInterface.BUTTON_NEGATIVE,
+                res.getText(com.android.internal.R.string.wait),
+                mHandler.obtainMessage(WAIT));
+
+        if (app.errorReportReceiver != null) {
+            setButton(DialogInterface.BUTTON_NEUTRAL,
+                    res.getText(com.android.internal.R.string.report),
+                    mHandler.obtainMessage(WAIT_AND_REPORT));
+        }
+
+        setTitle(res.getText(com.android.internal.R.string.anr_title));
+        if (aboveSystem) {
+            getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+        }
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.setTitle("Application Not Responding: " + app.info.processName);
+        attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
+                WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        getWindow().setAttributes(attrs);
+    }
+
+    public void onStop() {
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            Intent appErrorIntent = null;
+            switch (msg.what) {
+                case FORCE_CLOSE:
+                    // Kill the application.
+                    mService.killAppAtUsersRequest(mProc, AppNotRespondingDialog.this);
+                    break;
+                case WAIT_AND_REPORT:
+                case WAIT:
+                    // Continue waiting for the application.
+                    synchronized (mService) {
+                        ProcessRecord app = mProc;
+
+                        if (msg.what == WAIT_AND_REPORT) {
+                            appErrorIntent = mService.createAppErrorIntentLocked(app,
+                                    System.currentTimeMillis(), null);
+                        }
+
+                        app.notResponding = false;
+                        app.notRespondingReport = null;
+                        if (app.anrDialog == AppNotRespondingDialog.this) {
+                            app.anrDialog = null;
+                        }
+                        mService.mServices.scheduleServiceTimeoutLocked(app);
+                    }
+                    break;
+            }
+
+            if (appErrorIntent != null) {
+                try {
+                    getContext().startActivity(appErrorIntent);
+                } catch (ActivityNotFoundException e) {
+                    Slog.w(TAG, "bug report receiver dissappeared", e);
+                }
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
new file mode 100644
index 0000000..27865a8
--- /dev/null
+++ b/services/core/java/com/android/server/am/AppWaitingForDebuggerDialog.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.os.Message;
+import android.view.WindowManager;
+
+final class AppWaitingForDebuggerDialog extends BaseErrorDialog {
+    final ActivityManagerService mService;
+    final ProcessRecord mProc;
+    private CharSequence mAppName;
+    
+    public AppWaitingForDebuggerDialog(ActivityManagerService service,
+            Context context, ProcessRecord app) {
+        super(context);
+        mService = service;
+        mProc = app;
+        mAppName = context.getPackageManager().getApplicationLabel(app.info);
+
+        setCancelable(false);
+
+        StringBuilder text = new StringBuilder();
+        if (mAppName != null && mAppName.length() > 0) {
+            text.append("Application ");
+            text.append(mAppName);
+            text.append(" (process ");
+            text.append(app.processName);
+            text.append(")");
+        } else {
+            text.append("Process ");
+            text.append(app.processName);
+        }
+
+        text.append(" is waiting for the debugger to attach.");
+
+        setMessage(text.toString());
+        setButton(DialogInterface.BUTTON_POSITIVE, "Force Close", mHandler.obtainMessage(1, app));
+        setTitle("Waiting For Debugger");
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.setTitle("Waiting For Debugger: " + app.info.processName);
+        getWindow().setAttributes(attrs);
+    }
+    
+    public void onStop() {
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case 1:
+                    // Kill the application.
+                    mService.killAppAtUsersRequest(mProc, AppWaitingForDebuggerDialog.this);
+                    break;
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/am/BackupRecord.java b/services/core/java/com/android/server/am/BackupRecord.java
new file mode 100644
index 0000000..5fa7e6a
--- /dev/null
+++ b/services/core/java/com/android/server/am/BackupRecord.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import com.android.internal.os.BatteryStatsImpl;
+
+import android.content.pm.ApplicationInfo;
+
+/** @hide */
+final class BackupRecord {
+    // backup/restore modes
+    public static final int BACKUP_NORMAL = 0;
+    public static final int BACKUP_FULL = 1;
+    public static final int RESTORE = 2;
+    public static final int RESTORE_FULL = 3;
+    
+    final BatteryStatsImpl.Uid.Pkg.Serv stats;
+    String stringName;                     // cached toString() output
+    final ApplicationInfo appInfo;         // information about BackupAgent's app
+    final int backupMode;                  // full backup / incremental / restore
+    ProcessRecord app;                     // where this agent is running or null
+
+    // ----- Implementation -----
+
+    BackupRecord(BatteryStatsImpl.Uid.Pkg.Serv _agentStats, ApplicationInfo _appInfo,
+            int _backupMode) {
+        stats = _agentStats;
+        appInfo = _appInfo;
+        backupMode = _backupMode;
+    }
+
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("BackupRecord{")
+            .append(Integer.toHexString(System.identityHashCode(this)))
+            .append(' ').append(appInfo.packageName)
+            .append(' ').append(appInfo.name)
+            .append(' ').append(appInfo.backupAgentName).append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/BaseErrorDialog.java b/services/core/java/com/android/server/am/BaseErrorDialog.java
new file mode 100644
index 0000000..6ede8f8
--- /dev/null
+++ b/services/core/java/com/android/server/am/BaseErrorDialog.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import com.android.internal.R;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.widget.Button;
+
+class BaseErrorDialog extends AlertDialog {
+    public BaseErrorDialog(Context context) {
+        super(context, com.android.internal.R.style.Theme_Dialog_AppError);
+
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.setTitle("Error Dialog");
+        getWindow().setAttributes(attrs);
+        setIconAttribute(R.attr.alertDialogIcon);
+    }
+
+    public void onStart() {
+        super.onStart();
+        setEnabled(false);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(0), 1000);
+    }
+
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (mConsuming) {
+            //Slog.i(TAG, "Consuming: " + event);
+            return true;
+        }
+        //Slog.i(TAG, "Dispatching: " + event);
+        return super.dispatchKeyEvent(event);
+    }
+
+    private void setEnabled(boolean enabled) {
+        Button b = (Button)findViewById(R.id.button1);
+        if (b != null) {
+            b.setEnabled(enabled);
+        }
+        b = (Button)findViewById(R.id.button2);
+        if (b != null) {
+            b.setEnabled(enabled);
+        }
+        b = (Button)findViewById(R.id.button3);
+        if (b != null) {
+            b.setEnabled(enabled);
+        }
+    }
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == 0) {
+                mConsuming = false;
+                setEnabled(true);
+            }
+        }
+    };
+
+    private boolean mConsuming = true;
+}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
new file mode 100644
index 0000000..2d59678
--- /dev/null
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2006-2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.internal.os.PowerProfile;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * All information we are collecting about things that can happen that impact
+ * battery life.
+ */
+public final class BatteryStatsService extends IBatteryStats.Stub {
+    static IBatteryStats sService;
+    
+    final BatteryStatsImpl mStats;
+    Context mContext;
+    private boolean mBluetoothPendingStats;
+    private BluetoothHeadset mBluetoothHeadset;
+
+    BatteryStatsService(String filename) {
+        mStats = new BatteryStatsImpl(filename);
+    }
+    
+    public void publish(Context context) {
+        mContext = context;
+        ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder());
+        mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps());
+        mStats.setRadioScanningTimeout(mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_radioScanningTimeout)
+                * 1000L);
+    }
+    
+    public void shutdown() {
+        Slog.w("BatteryStats", "Writing battery stats before shutdown...");
+        synchronized (mStats) {
+            mStats.shutdownLocked();
+        }
+    }
+    
+    public static IBatteryStats getService() {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService(BatteryStats.SERVICE_NAME);
+        sService = asInterface(b);
+        return sService;
+    }
+    
+    /**
+     * @return the current statistics object, which may be modified
+     * to reflect events that affect battery usage.  You must lock the
+     * stats object before doing anything with it.
+     */
+    public BatteryStatsImpl getActiveStatistics() {
+        return mStats;
+    }
+    
+    public byte[] getStatistics() {
+        mContext.enforceCallingPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        //Slog.i("foo", "SENDING BATTERY INFO:");
+        //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
+        Parcel out = Parcel.obtain();
+        mStats.writeToParcel(out, 0);
+        byte[] data = out.marshall();
+        out.recycle();
+        return data;
+    }
+    
+    public void noteStartWakelock(int uid, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartWakeLocked(uid, pid, name, type);
+        }
+    }
+
+    public void noteStopWakelock(int uid, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopWakeLocked(uid, pid, name, type);
+        }
+    }
+
+    public void noteStartWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
+    public void noteStopWakelockFromSource(WorkSource ws, int pid, String name, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopWakeFromSourceLocked(ws, pid, name, type);
+        }
+    }
+
+    public void noteStartSensor(int uid, int sensor) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartSensorLocked(uid, sensor);
+        }
+    }
+    
+    public void noteStopSensor(int uid, int sensor) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopSensorLocked(uid, sensor);
+        }
+    }
+    
+    public void noteVibratorOn(int uid, long durationMillis) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVibratorOnLocked(uid, durationMillis);
+        }
+    }
+
+    public void noteVibratorOff(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVibratorOffLocked(uid);
+        }
+    }
+
+    public void noteStartGps(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStartGpsLocked(uid);
+        }
+    }
+    
+    public void noteStopGps(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteStopGpsLocked(uid);
+        }
+    }
+        
+    public void noteScreenOn() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScreenOnLocked();
+        }
+    }
+    
+    public void noteScreenBrightness(int brightness) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScreenBrightnessLocked(brightness);
+        }
+    }
+    
+    public void noteScreenOff() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteScreenOffLocked();
+        }
+    }
+
+    public void noteInputEvent() {
+        enforceCallingPermission();
+        mStats.noteInputEventAtomic();
+    }
+    
+    public void noteUserActivity(int uid, int event) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteUserActivityLocked(uid, event);
+        }
+    }
+    
+    public void notePhoneOn() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePhoneOnLocked();
+        }
+    }
+    
+    public void notePhoneOff() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePhoneOffLocked();
+        }
+    }
+    
+    public void notePhoneSignalStrength(SignalStrength signalStrength) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePhoneSignalStrengthLocked(signalStrength);
+        }
+    }
+    
+    public void notePhoneDataConnectionState(int dataType, boolean hasData) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.notePhoneDataConnectionStateLocked(dataType, hasData);
+        }
+    }
+
+    public void notePhoneState(int state) {
+        enforceCallingPermission();
+        int simState = TelephonyManager.getDefault().getSimState();
+        synchronized (mStats) {
+            mStats.notePhoneStateLocked(state, simState);
+        }
+    }
+
+    public void noteWifiOn() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiOnLocked();
+        }
+    }
+    
+    public void noteWifiOff() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiOffLocked();
+        }
+    }
+
+    public void noteStartAudio(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteAudioOnLocked(uid);
+        }
+    }
+
+    public void noteStopAudio(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteAudioOffLocked(uid);
+        }
+    }
+
+    public void noteStartVideo(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVideoOnLocked(uid);
+        }
+    }
+
+    public void noteStopVideo(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteVideoOffLocked(uid);
+        }
+    }
+
+    public void noteWifiRunning(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiRunningLocked(ws);
+        }
+    }
+
+    public void noteWifiRunningChanged(WorkSource oldWs, WorkSource newWs) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiRunningChangedLocked(oldWs, newWs);
+        }
+    }
+
+    public void noteWifiStopped(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiStoppedLocked(ws);
+        }
+    }
+
+    public void noteBluetoothOn() {
+        enforceCallingPermission();
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
+        synchronized (mStats) {
+            if (mBluetoothHeadset != null) {
+                mStats.noteBluetoothOnLocked();
+                mStats.setBtHeadset(mBluetoothHeadset);
+            } else {
+                mBluetoothPendingStats = true;
+            }
+        }
+    }
+
+    private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            synchronized (mStats) {
+                if (mBluetoothPendingStats) {
+                    mStats.noteBluetoothOnLocked();
+                    mStats.setBtHeadset(mBluetoothHeadset);
+                    mBluetoothPendingStats = false;
+                }
+            }
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mBluetoothHeadset = null;
+        }
+    };
+
+    public void noteBluetoothOff() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mBluetoothPendingStats = false;
+            mStats.noteBluetoothOffLocked();
+        }
+    }
+    
+    public void noteFullWifiLockAcquired(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockAcquiredLocked(uid);
+        }
+    }
+    
+    public void noteFullWifiLockReleased(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockReleasedLocked(uid);
+        }
+    }
+
+    public void noteWifiScanStarted(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiScanStartedLocked(uid);
+        }
+    }
+
+    public void noteWifiScanStopped(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiScanStoppedLocked(uid);
+        }
+    }
+
+    public void noteWifiMulticastEnabled(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastEnabledLocked(uid);
+        }
+    }
+
+    public void noteWifiMulticastDisabled(int uid) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastDisabledLocked(uid);
+        }
+    }
+
+    public void noteFullWifiLockAcquiredFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockAcquiredFromSourceLocked(ws);
+        }
+    }
+
+    public void noteFullWifiLockReleasedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteFullWifiLockReleasedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiScanStartedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiScanStartedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiScanStoppedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiScanStoppedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph);
+        }
+    }
+
+    public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastEnabledFromSourceLocked(ws);
+        }
+    }
+
+    public void noteWifiMulticastDisabledFromSource(WorkSource ws) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteWifiMulticastDisabledFromSourceLocked(ws);
+        }
+    }
+
+    @Override
+    public void noteNetworkInterfaceType(String iface, int type) {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteNetworkInterfaceTypeLocked(iface, type);
+        }
+    }
+
+    @Override
+    public void noteNetworkStatsEnabled() {
+        enforceCallingPermission();
+        synchronized (mStats) {
+            mStats.noteNetworkStatsEnabledLocked();
+        }
+    }
+
+    public boolean isOnBattery() {
+        return mStats.isOnBattery();
+    }
+    
+    public void setBatteryState(int status, int health, int plugType, int level,
+            int temp, int volt) {
+        enforceCallingPermission();
+        mStats.setBatteryState(status, health, plugType, level, temp, volt);
+    }
+    
+    public long getAwakeTimeBattery() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        return mStats.getAwakeTimeBattery();
+    }
+
+    public long getAwakeTimePlugged() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BATTERY_STATS, null);
+        return mStats.getAwakeTimePlugged();
+    }
+
+    public void enforceCallingPermission() {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+    
+    private void dumpHelp(PrintWriter pw) {
+        pw.println("Battery stats (batterystats) dump options:");
+        pw.println("  [--checkin] [-c] [--unplugged] [--reset] [--write] [-h] [<package.name>]");
+        pw.println("  --checkin: format output for a checkin report.");
+        pw.println("  --unplugged: only output data since last unplugged.");
+        pw.println("  --reset: reset the stats, clearing all current data.");
+        pw.println("  --write: force write current collected stats to disk.");
+        pw.println("  -h: print this help text.");
+        pw.println("  <package.name>: optional name of package to filter output by.");
+    }
+
+    @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 BatteryStats from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        boolean isCheckin = false;
+        boolean includeHistory = false;
+        boolean isUnpluggedOnly = false;
+        boolean noOutput = false;
+        int reqUid = -1;
+        if (args != null) {
+            for (String arg : args) {
+                if ("--checkin".equals(arg)) {
+                    isCheckin = true;
+                } else if ("-c".equals(arg)) {
+                    isCheckin = true;
+                    includeHistory = true;
+                } else if ("--unplugged".equals(arg)) {
+                    isUnpluggedOnly = true;
+                } else if ("--reset".equals(arg)) {
+                    synchronized (mStats) {
+                        mStats.resetAllStatsLocked();
+                        pw.println("Battery stats reset.");
+                        noOutput = true;
+                    }
+                } else if ("--write".equals(arg)) {
+                    synchronized (mStats) {
+                        mStats.writeSyncLocked();
+                        pw.println("Battery stats written.");
+                        noOutput = true;
+                    }
+                } else if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    // fall through
+                } else if (arg.length() > 0 && arg.charAt(0) == '-'){
+                    pw.println("Unknown option: " + arg);
+                    dumpHelp(pw);
+                    return;
+                } else {
+                    // Not an option, last argument must be a package name.
+                    try {
+                        reqUid = mContext.getPackageManager().getPackageUid(arg,
+                                UserHandle.getCallingUserId());
+                    } catch (PackageManager.NameNotFoundException e) {
+                        pw.println("Unknown package: " + arg);
+                        dumpHelp(pw);
+                        return;
+                    }
+                }
+            }
+        }
+        if (noOutput) {
+            return;
+        }
+        if (isCheckin) {
+            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
+            synchronized (mStats) {
+                mStats.dumpCheckinLocked(pw, apps, isUnpluggedOnly, includeHistory);
+            }
+        } else {
+            synchronized (mStats) {
+                mStats.dumpLocked(pw, isUnpluggedOnly, reqUid);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastFilter.java b/services/core/java/com/android/server/am/BroadcastFilter.java
new file mode 100644
index 0000000..986b8ea
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastFilter.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.IntentFilter;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+
+import java.io.PrintWriter;
+
+final class BroadcastFilter extends IntentFilter {
+    // Back-pointer to the list this filter is in.
+    final ReceiverList receiverList;
+    final String packageName;
+    final String requiredPermission;
+    final int owningUid;
+    final int owningUserId;
+
+    BroadcastFilter(IntentFilter _filter, ReceiverList _receiverList,
+            String _packageName, String _requiredPermission, int _owningUid, int _userId) {
+        super(_filter);
+        receiverList = _receiverList;
+        packageName = _packageName;
+        requiredPermission = _requiredPermission;
+        owningUid = _owningUid;
+        owningUserId = _userId;
+    }
+    
+    public void dump(PrintWriter pw, String prefix) {
+        dumpInReceiverList(pw, new PrintWriterPrinter(pw), prefix);
+        receiverList.dumpLocal(pw, prefix);
+    }
+    
+    public void dumpBrief(PrintWriter pw, String prefix) {
+        dumpBroadcastFilterState(pw, prefix);
+    }
+    
+    public void dumpInReceiverList(PrintWriter pw, Printer pr, String prefix) {
+        super.dump(pr, prefix);
+        dumpBroadcastFilterState(pw, prefix);
+    }
+    
+    void dumpBroadcastFilterState(PrintWriter pw, String prefix) {
+        if (requiredPermission != null) {
+            pw.print(prefix); pw.print("requiredPermission="); pw.println(requiredPermission);
+        }
+    }
+    
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("BroadcastFilter{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" u");
+        sb.append(owningUserId);
+        sb.append(' ');
+        sb.append(receiverList);
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
new file mode 100644
index 0000000..bfb667f
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -0,0 +1,1219 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * BROADCASTS
+ *
+ * We keep two broadcast queues and associated bookkeeping, one for those at
+ * foreground priority, and one for normal (background-priority) broadcasts.
+ */
+public final class BroadcastQueue {
+    static final String TAG = "BroadcastQueue";
+    static final String TAG_MU = ActivityManagerService.TAG_MU;
+    static final boolean DEBUG_BROADCAST = ActivityManagerService.DEBUG_BROADCAST;
+    static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT;
+    static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU;
+
+    static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 50;
+    static final int MAX_BROADCAST_SUMMARY_HISTORY
+            = ActivityManager.isLowRamDeviceStatic() ? 25 : 300;
+
+    final ActivityManagerService mService;
+
+    /**
+     * Recognizable moniker for this queue
+     */
+    final String mQueueName;
+
+    /**
+     * Timeout period for this queue's broadcasts
+     */
+    final long mTimeoutPeriod;
+
+    /**
+     * If true, we can delay broadcasts while waiting services to finish in the previous
+     * receiver's process.
+     */
+    final boolean mDelayBehindServices;
+
+    /**
+     * Lists of all active broadcasts that are to be executed immediately
+     * (without waiting for another broadcast to finish).  Currently this only
+     * contains broadcasts to registered receivers, to avoid spinning up
+     * a bunch of processes to execute IntentReceiver components.  Background-
+     * and foreground-priority broadcasts are queued separately.
+     */
+    final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<BroadcastRecord>();
+
+    /**
+     * List of all active broadcasts that are to be executed one at a time.
+     * The object at the top of the list is the currently activity broadcasts;
+     * those after it are waiting for the top to finish.  As with parallel
+     * broadcasts, separate background- and foreground-priority queues are
+     * maintained.
+     */
+    final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<BroadcastRecord>();
+
+    /**
+     * Historical data of past broadcasts, for debugging.
+     */
+    final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY];
+
+    /**
+     * Summary of historical data of past broadcasts, for debugging.
+     */
+    final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY];
+
+    /**
+     * Set when we current have a BROADCAST_INTENT_MSG in flight.
+     */
+    boolean mBroadcastsScheduled = false;
+
+    /**
+     * True if we have a pending unexpired BROADCAST_TIMEOUT_MSG posted to our handler.
+     */
+    boolean mPendingBroadcastTimeoutMessage;
+
+    /**
+     * Intent broadcasts that we have tried to start, but are
+     * waiting for the application's process to be created.  We only
+     * need one per scheduling class (instead of a list) because we always
+     * process broadcasts one at a time, so no others can be started while
+     * waiting for this one.
+     */
+    BroadcastRecord mPendingBroadcast = null;
+
+    /**
+     * The receiver index that is pending, to restart the broadcast if needed.
+     */
+    int mPendingBroadcastRecvIndex;
+
+    static final int BROADCAST_INTENT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG;
+    static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1;
+
+    final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case BROADCAST_INTENT_MSG: {
+                    if (DEBUG_BROADCAST) Slog.v(
+                            TAG, "Received BROADCAST_INTENT_MSG");
+                    processNextBroadcast(true);
+                } break;
+                case BROADCAST_TIMEOUT_MSG: {
+                    synchronized (mService) {
+                        broadcastTimeoutLocked(true);
+                    }
+                } break;
+            }
+        }
+    };
+
+    private final class AppNotResponding implements Runnable {
+        private final ProcessRecord mApp;
+        private final String mAnnotation;
+
+        public AppNotResponding(ProcessRecord app, String annotation) {
+            mApp = app;
+            mAnnotation = annotation;
+        }
+
+        @Override
+        public void run() {
+            mService.appNotResponding(mApp, null, null, false, mAnnotation);
+        }
+    }
+
+    BroadcastQueue(ActivityManagerService service, String name, long timeoutPeriod,
+            boolean allowDelayBehindServices) {
+        mService = service;
+        mQueueName = name;
+        mTimeoutPeriod = timeoutPeriod;
+        mDelayBehindServices = allowDelayBehindServices;
+    }
+
+    public boolean isPendingBroadcastProcessLocked(int pid) {
+        return mPendingBroadcast != null && mPendingBroadcast.curApp.pid == pid;
+    }
+
+    public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
+        mParallelBroadcasts.add(r);
+    }
+
+    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
+        mOrderedBroadcasts.add(r);
+    }
+
+    public final boolean replaceParallelBroadcastLocked(BroadcastRecord r) {
+        for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+            if (r.intent.filterEquals(mParallelBroadcasts.get(i).intent)) {
+                if (DEBUG_BROADCAST) Slog.v(TAG,
+                        "***** DROPPING PARALLEL ["
+                + mQueueName + "]: " + r.intent);
+                mParallelBroadcasts.set(i, r);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public final boolean replaceOrderedBroadcastLocked(BroadcastRecord r) {
+        for (int i=mOrderedBroadcasts.size()-1; i>0; i--) {
+            if (r.intent.filterEquals(mOrderedBroadcasts.get(i).intent)) {
+                if (DEBUG_BROADCAST) Slog.v(TAG,
+                        "***** DROPPING ORDERED ["
+                        + mQueueName + "]: " + r.intent);
+                mOrderedBroadcasts.set(i, r);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private final void processCurBroadcastLocked(BroadcastRecord r,
+            ProcessRecord app) throws RemoteException {
+        if (DEBUG_BROADCAST)  Slog.v(TAG,
+                "Process cur broadcast " + r + " for app " + app);
+        if (app.thread == null) {
+            throw new RemoteException();
+        }
+        r.receiver = app.thread.asBinder();
+        r.curApp = app;
+        app.curReceiver = r;
+        app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
+        mService.updateLruProcessLocked(app, false, null);
+        mService.updateOomAdjLocked();
+
+        // Tell the application to launch this receiver.
+        r.intent.setComponent(r.curComponent);
+
+        boolean started = false;
+        try {
+            if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG,
+                    "Delivering to component " + r.curComponent
+                    + ": " + r);
+            mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName());
+            app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
+                    mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo),
+                    r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
+                    app.repProcState);
+            if (DEBUG_BROADCAST)  Slog.v(TAG,
+                    "Process cur broadcast " + r + " DELIVERED for app " + app);
+            started = true;
+        } finally {
+            if (!started) {
+                if (DEBUG_BROADCAST)  Slog.v(TAG,
+                        "Process cur broadcast " + r + ": NOT STARTED!");
+                r.receiver = null;
+                r.curApp = null;
+                app.curReceiver = null;
+            }
+        }
+    }
+
+    public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
+        boolean didSomething = false;
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.pid == app.pid) {
+            try {
+                mPendingBroadcast = null;
+                processCurBroadcastLocked(br, app);
+                didSomething = true;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception in new application when starting receiver "
+                        + br.curComponent.flattenToShortString(), e);
+                logBroadcastReceiverDiscardLocked(br);
+                finishReceiverLocked(br, br.resultCode, br.resultData,
+                        br.resultExtras, br.resultAbort, false);
+                scheduleBroadcastsLocked();
+                // We need to reset the state if we failed to start the receiver.
+                br.state = BroadcastRecord.IDLE;
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+        return didSomething;
+    }
+
+    public void skipPendingBroadcastLocked(int pid) {
+        final BroadcastRecord br = mPendingBroadcast;
+        if (br != null && br.curApp.pid == pid) {
+            br.state = BroadcastRecord.IDLE;
+            br.nextReceiver = mPendingBroadcastRecvIndex;
+            mPendingBroadcast = null;
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    public void skipCurrentReceiverLocked(ProcessRecord app) {
+        boolean reschedule = false;
+        BroadcastRecord r = app.curReceiver;
+        if (r != null) {
+            // The current broadcast is waiting for this app's receiver
+            // to be finished.  Looks like that's not going to happen, so
+            // let the broadcast continue.
+            logBroadcastReceiverDiscardLocked(r);
+            finishReceiverLocked(r, r.resultCode, r.resultData,
+                    r.resultExtras, r.resultAbort, false);
+            reschedule = true;
+        }
+
+        r = mPendingBroadcast;
+        if (r != null && r.curApp == app) {
+            if (DEBUG_BROADCAST) Slog.v(TAG,
+                    "[" + mQueueName + "] skip & discard pending app " + r);
+            logBroadcastReceiverDiscardLocked(r);
+            finishReceiverLocked(r, r.resultCode, r.resultData,
+                    r.resultExtras, r.resultAbort, false);
+            reschedule = true;
+        }
+        if (reschedule) {
+            scheduleBroadcastsLocked();
+        }
+    }
+
+    public void scheduleBroadcastsLocked() {
+        if (DEBUG_BROADCAST) Slog.v(TAG, "Schedule broadcasts ["
+                + mQueueName + "]: current="
+                + mBroadcastsScheduled);
+
+        if (mBroadcastsScheduled) {
+            return;
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
+        mBroadcastsScheduled = true;
+    }
+
+    public BroadcastRecord getMatchingOrderedReceiver(IBinder receiver) {
+        if (mOrderedBroadcasts.size() > 0) {
+            final BroadcastRecord r = mOrderedBroadcasts.get(0);
+            if (r != null && r.receiver == receiver) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
+            String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
+        final int state = r.state;
+        final ActivityInfo receiver = r.curReceiver;
+        r.state = BroadcastRecord.IDLE;
+        if (state == BroadcastRecord.IDLE) {
+            Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE");
+        }
+        r.receiver = null;
+        r.intent.setComponent(null);
+        if (r.curApp != null) {
+            r.curApp.curReceiver = null;
+        }
+        if (r.curFilter != null) {
+            r.curFilter.receiverList.curBroadcast = null;
+        }
+        r.curFilter = null;
+        r.curReceiver = null;
+        r.curApp = null;
+        mPendingBroadcast = null;
+
+        r.resultCode = resultCode;
+        r.resultData = resultData;
+        r.resultExtras = resultExtras;
+        if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) {
+            r.resultAbort = resultAbort;
+        } else {
+            r.resultAbort = false;
+        }
+
+        if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices
+                && r.queue.mOrderedBroadcasts.size() > 0
+                && r.queue.mOrderedBroadcasts.get(0) == r) {
+            ActivityInfo nextReceiver;
+            if (r.nextReceiver < r.receivers.size()) {
+                Object obj = r.receivers.get(r.nextReceiver);
+                nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null;
+            } else {
+                nextReceiver = null;
+            }
+            // Don't do this if the next receive is in the same process as the current one.
+            if (receiver == null || nextReceiver == null
+                    || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid
+                    || !receiver.processName.equals(nextReceiver.processName)) {
+                // In this case, we are ready to process the next receiver for the current broadcast,
+                // but are on a queue that would like to wait for services to finish before moving
+                // on.  If there are background services currently starting, then we will go into a
+                // special state where we hold off on continuing this broadcast until they are done.
+                if (mService.mServices.hasBackgroundServices(r.userId)) {
+                    Slog.i(ActivityManagerService.TAG, "Delay finish: "
+                            + r.curComponent.flattenToShortString());
+                    r.state = BroadcastRecord.WAITING_SERVICES;
+                    return false;
+                }
+            }
+        }
+
+        r.curComponent = null;
+
+        // We will process the next receiver right now if this is finishing
+        // an app receiver (which is always asynchronous) or after we have
+        // come back from calling a receiver.
+        return state == BroadcastRecord.APP_RECEIVE
+                || state == BroadcastRecord.CALL_DONE_RECEIVE;
+    }
+
+    public void backgroundServicesFinishedLocked(int userId) {
+        if (mOrderedBroadcasts.size() > 0) {
+            BroadcastRecord br = mOrderedBroadcasts.get(0);
+            if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) {
+                Slog.i(ActivityManagerService.TAG, "Resuming delayed broadcast");
+                br.curComponent = null;
+                br.state = BroadcastRecord.IDLE;
+                processNextBroadcast(false);
+            }
+        }
+    }
+
+    private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
+            Intent intent, int resultCode, String data, Bundle extras,
+            boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
+        // Send the intent to the receiver asynchronously using one-way binder calls.
+        if (app != null && app.thread != null) {
+            // If we have an app thread, do the call through that so it is
+            // correctly ordered with other one-way calls.
+            app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
+                    data, extras, ordered, sticky, sendingUser, app.repProcState);
+        } else {
+            receiver.performReceive(intent, resultCode, data, extras, ordered,
+                    sticky, sendingUser);
+        }
+    }
+
+    private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,
+            BroadcastFilter filter, boolean ordered) {
+        boolean skip = false;
+        if (filter.requiredPermission != null) {
+            int perm = mService.checkComponentPermission(filter.requiredPermission,
+                    r.callingPid, r.callingUid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: broadcasting "
+                        + r.intent.toString()
+                        + " from " + r.callerPackage + " (pid="
+                        + r.callingPid + ", uid=" + r.callingUid + ")"
+                        + " requires " + filter.requiredPermission
+                        + " due to registered receiver " + filter);
+                skip = true;
+            }
+        }
+        if (!skip && r.requiredPermission != null) {
+            int perm = mService.checkComponentPermission(r.requiredPermission,
+                    filter.receiverList.pid, filter.receiverList.uid, -1, true);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                Slog.w(TAG, "Permission Denial: receiving "
+                        + r.intent.toString()
+                        + " to " + filter.receiverList.app
+                        + " (pid=" + filter.receiverList.pid
+                        + ", uid=" + filter.receiverList.uid + ")"
+                        + " requires " + r.requiredPermission
+                        + " due to sender " + r.callerPackage
+                        + " (uid " + r.callingUid + ")");
+                skip = true;
+            }
+        }
+        if (r.appOp != AppOpsManager.OP_NONE) {
+            int mode = mService.mAppOpsService.noteOperation(r.appOp,
+                    filter.receiverList.uid, filter.packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                if (DEBUG_BROADCAST)  Slog.v(TAG,
+                        "App op " + r.appOp + " not allowed for broadcast to uid "
+                        + filter.receiverList.uid + " pkg " + filter.packageName);
+                skip = true;
+            }
+        }
+        if (!skip) {
+            skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                    r.callingPid, r.resolvedType, filter.receiverList.uid);
+        }
+
+        if (filter.receiverList.app == null || filter.receiverList.app.crashing) {
+            Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
+                    + " to " + filter.receiverList + ": process crashing");
+            skip = true;
+        }
+
+        if (!skip) {
+            // If this is not being sent as an ordered broadcast, then we
+            // don't want to touch the fields that keep track of the current
+            // state of ordered broadcasts.
+            if (ordered) {
+                r.receiver = filter.receiverList.receiver.asBinder();
+                r.curFilter = filter;
+                filter.receiverList.curBroadcast = r;
+                r.state = BroadcastRecord.CALL_IN_RECEIVE;
+                if (filter.receiverList.app != null) {
+                    // Bump hosting application to no longer be in background
+                    // scheduling class.  Note that we can't do that if there
+                    // isn't an app...  but we can only be in that case for
+                    // things that directly call the IActivityManager API, which
+                    // are already core system stuff so don't matter for this.
+                    r.curApp = filter.receiverList.app;
+                    filter.receiverList.app.curReceiver = r;
+                    mService.updateOomAdjLocked(r.curApp, true);
+                }
+            }
+            try {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    int seq = r.intent.getIntExtra("seq", -1);
+                    Slog.i(TAG, "Delivering to " + filter
+                            + " (seq=" + seq + "): " + r);
+                }
+                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
+                    new Intent(r.intent), r.resultCode, r.resultData,
+                    r.resultExtras, r.ordered, r.initialSticky, r.userId);
+                if (ordered) {
+                    r.state = BroadcastRecord.CALL_DONE_RECEIVE;
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failure sending broadcast " + r.intent, e);
+                if (ordered) {
+                    r.receiver = null;
+                    r.curFilter = null;
+                    filter.receiverList.curBroadcast = null;
+                    if (filter.receiverList.app != null) {
+                        filter.receiverList.app.curReceiver = null;
+                    }
+                }
+            }
+        }
+    }
+
+    final void processNextBroadcast(boolean fromMsg) {
+        synchronized(mService) {
+            BroadcastRecord r;
+
+            if (DEBUG_BROADCAST) Slog.v(TAG, "processNextBroadcast ["
+                    + mQueueName + "]: "
+                    + mParallelBroadcasts.size() + " broadcasts, "
+                    + mOrderedBroadcasts.size() + " ordered broadcasts");
+
+            mService.updateCpuStats();
+
+            if (fromMsg) {
+                mBroadcastsScheduled = false;
+            }
+
+            // First, deliver any non-serialized broadcasts right away.
+            while (mParallelBroadcasts.size() > 0) {
+                r = mParallelBroadcasts.remove(0);
+                r.dispatchTime = SystemClock.uptimeMillis();
+                r.dispatchClockTime = System.currentTimeMillis();
+                final int N = r.receivers.size();
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing parallel broadcast ["
+                        + mQueueName + "] " + r);
+                for (int i=0; i<N; i++) {
+                    Object target = r.receivers.get(i);
+                    if (DEBUG_BROADCAST)  Slog.v(TAG,
+                            "Delivering non-ordered on [" + mQueueName + "] to registered "
+                            + target + ": " + r);
+                    deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
+                }
+                addBroadcastToHistoryLocked(r);
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Done with parallel broadcast ["
+                        + mQueueName + "] " + r);
+            }
+
+            // Now take care of the next serialized one...
+
+            // If we are waiting for a process to come up to handle the next
+            // broadcast, then do nothing at this point.  Just in case, we
+            // check that the process we're waiting for still exists.
+            if (mPendingBroadcast != null) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.v(TAG, "processNextBroadcast ["
+                            + mQueueName + "]: waiting for "
+                            + mPendingBroadcast.curApp);
+                }
+
+                boolean isDead;
+                synchronized (mService.mPidsSelfLocked) {
+                    ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
+                    isDead = proc == null || proc.crashing;
+                }
+                if (!isDead) {
+                    // It's still alive, so keep waiting
+                    return;
+                } else {
+                    Slog.w(TAG, "pending app  ["
+                            + mQueueName + "]" + mPendingBroadcast.curApp
+                            + " died before responding to broadcast");
+                    mPendingBroadcast.state = BroadcastRecord.IDLE;
+                    mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;
+                    mPendingBroadcast = null;
+                }
+            }
+
+            boolean looped = false;
+            
+            do {
+                if (mOrderedBroadcasts.size() == 0) {
+                    // No more broadcasts pending, so all done!
+                    mService.scheduleAppGcsLocked();
+                    if (looped) {
+                        // If we had finished the last ordered broadcast, then
+                        // make sure all processes have correct oom and sched
+                        // adjustments.
+                        mService.updateOomAdjLocked();
+                    }
+                    return;
+                }
+                r = mOrderedBroadcasts.get(0);
+                boolean forceReceive = false;
+
+                // Ensure that even if something goes awry with the timeout
+                // detection, we catch "hung" broadcasts here, discard them,
+                // and continue to make progress.
+                //
+                // This is only done if the system is ready so that PRE_BOOT_COMPLETED
+                // receivers don't get executed with timeouts. They're intended for
+                // one time heavy lifting after system upgrades and can take
+                // significant amounts of time.
+                int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
+                if (mService.mProcessesReady && r.dispatchTime > 0) {
+                    long now = SystemClock.uptimeMillis();
+                    if ((numReceivers > 0) &&
+                            (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
+                        Slog.w(TAG, "Hung broadcast ["
+                                + mQueueName + "] discarded after timeout failure:"
+                                + " now=" + now
+                                + " dispatchTime=" + r.dispatchTime
+                                + " startTime=" + r.receiverTime
+                                + " intent=" + r.intent
+                                + " numReceivers=" + numReceivers
+                                + " nextReceiver=" + r.nextReceiver
+                                + " state=" + r.state);
+                        broadcastTimeoutLocked(false); // forcibly finish this broadcast
+                        forceReceive = true;
+                        r.state = BroadcastRecord.IDLE;
+                    }
+                }
+
+                if (r.state != BroadcastRecord.IDLE) {
+                    if (DEBUG_BROADCAST) Slog.d(TAG,
+                            "processNextBroadcast("
+                            + mQueueName + ") called when not idle (state="
+                            + r.state + ")");
+                    return;
+                }
+
+                if (r.receivers == null || r.nextReceiver >= numReceivers
+                        || r.resultAbort || forceReceive) {
+                    // No more receivers for this broadcast!  Send the final
+                    // result if requested...
+                    if (r.resultTo != null) {
+                        try {
+                            if (DEBUG_BROADCAST) {
+                                int seq = r.intent.getIntExtra("seq", -1);
+                                Slog.i(TAG, "Finishing broadcast ["
+                                        + mQueueName + "] " + r.intent.getAction()
+                                        + " seq=" + seq + " app=" + r.callerApp);
+                            }
+                            performReceiveLocked(r.callerApp, r.resultTo,
+                                new Intent(r.intent), r.resultCode,
+                                r.resultData, r.resultExtras, false, false, r.userId);
+                            // Set this to null so that the reference
+                            // (local and remote) isn't kept in the mBroadcastHistory.
+                            r.resultTo = null;
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failure ["
+                                    + mQueueName + "] sending broadcast result of "
+                                    + r.intent, e);
+                        }
+                    }
+
+                    if (DEBUG_BROADCAST) Slog.v(TAG, "Cancelling BROADCAST_TIMEOUT_MSG");
+                    cancelBroadcastTimeoutLocked();
+
+                    if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Finished with ordered broadcast "
+                            + r);
+
+                    // ... and on to the next...
+                    addBroadcastToHistoryLocked(r);
+                    mOrderedBroadcasts.remove(0);
+                    r = null;
+                    looped = true;
+                    continue;
+                }
+            } while (r == null);
+
+            // Get the next receiver...
+            int recIdx = r.nextReceiver++;
+
+            // Keep track of when this receiver started, and make sure there
+            // is a timeout message pending to kill it if need be.
+            r.receiverTime = SystemClock.uptimeMillis();
+            if (recIdx == 0) {
+                r.dispatchTime = r.receiverTime;
+                r.dispatchClockTime = System.currentTimeMillis();
+                if (DEBUG_BROADCAST_LIGHT) Slog.v(TAG, "Processing ordered broadcast ["
+                        + mQueueName + "] " + r);
+            }
+            if (! mPendingBroadcastTimeoutMessage) {
+                long timeoutTime = r.receiverTime + mTimeoutPeriod;
+                if (DEBUG_BROADCAST) Slog.v(TAG,
+                        "Submitting BROADCAST_TIMEOUT_MSG ["
+                        + mQueueName + "] for " + r + " at " + timeoutTime);
+                setBroadcastTimeoutLocked(timeoutTime);
+            }
+
+            Object nextReceiver = r.receivers.get(recIdx);
+            if (nextReceiver instanceof BroadcastFilter) {
+                // Simple case: this is a registered receiver who gets
+                // a direct call.
+                BroadcastFilter filter = (BroadcastFilter)nextReceiver;
+                if (DEBUG_BROADCAST)  Slog.v(TAG,
+                        "Delivering ordered ["
+                        + mQueueName + "] to registered "
+                        + filter + ": " + r);
+                deliverToRegisteredReceiverLocked(r, filter, r.ordered);
+                if (r.receiver == null || !r.ordered) {
+                    // The receiver has already finished, so schedule to
+                    // process the next one.
+                    if (DEBUG_BROADCAST) Slog.v(TAG, "Quick finishing ["
+                            + mQueueName + "]: ordered="
+                            + r.ordered + " receiver=" + r.receiver);
+                    r.state = BroadcastRecord.IDLE;
+                    scheduleBroadcastsLocked();
+                }
+                return;
+            }
+
+            // Hard case: need to instantiate the receiver, possibly
+            // starting its application process to host it.
+
+            ResolveInfo info =
+                (ResolveInfo)nextReceiver;
+            ComponentName component = new ComponentName(
+                    info.activityInfo.applicationInfo.packageName,
+                    info.activityInfo.name);
+
+            boolean skip = false;
+            int perm = mService.checkComponentPermission(info.activityInfo.permission,
+                    r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
+                    info.activityInfo.exported);
+            if (perm != PackageManager.PERMISSION_GRANTED) {
+                if (!info.activityInfo.exported) {
+                    Slog.w(TAG, "Permission Denial: broadcasting "
+                            + r.intent.toString()
+                            + " from " + r.callerPackage + " (pid=" + r.callingPid
+                            + ", uid=" + r.callingUid + ")"
+                            + " is not exported from uid " + info.activityInfo.applicationInfo.uid
+                            + " due to receiver " + component.flattenToShortString());
+                } else {
+                    Slog.w(TAG, "Permission Denial: broadcasting "
+                            + r.intent.toString()
+                            + " from " + r.callerPackage + " (pid=" + r.callingPid
+                            + ", uid=" + r.callingUid + ")"
+                            + " requires " + info.activityInfo.permission
+                            + " due to receiver " + component.flattenToShortString());
+                }
+                skip = true;
+            }
+            if (info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&
+                r.requiredPermission != null) {
+                try {
+                    perm = AppGlobals.getPackageManager().
+                            checkPermission(r.requiredPermission,
+                                    info.activityInfo.applicationInfo.packageName);
+                } catch (RemoteException e) {
+                    perm = PackageManager.PERMISSION_DENIED;
+                }
+                if (perm != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: receiving "
+                            + r.intent + " to "
+                            + component.flattenToShortString()
+                            + " requires " + r.requiredPermission
+                            + " due to sender " + r.callerPackage
+                            + " (uid " + r.callingUid + ")");
+                    skip = true;
+                }
+            }
+            if (r.appOp != AppOpsManager.OP_NONE) {
+                int mode = mService.mAppOpsService.noteOperation(r.appOp,
+                        info.activityInfo.applicationInfo.uid, info.activityInfo.packageName);
+                if (mode != AppOpsManager.MODE_ALLOWED) {
+                    if (DEBUG_BROADCAST)  Slog.v(TAG,
+                            "App op " + r.appOp + " not allowed for broadcast to uid "
+                            + info.activityInfo.applicationInfo.uid + " pkg "
+                            + info.activityInfo.packageName);
+                    skip = true;
+                }
+            }
+            if (!skip) {
+                skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
+                        r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid);
+            }
+            boolean isSingleton = false;
+            try {
+                isSingleton = mService.isSingleton(info.activityInfo.processName,
+                        info.activityInfo.applicationInfo,
+                        info.activityInfo.name, info.activityInfo.flags);
+            } catch (SecurityException e) {
+                Slog.w(TAG, e.getMessage());
+                skip = true;
+            }
+            if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                if (ActivityManager.checkUidPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS,
+                        info.activityInfo.applicationInfo.uid)
+                                != PackageManager.PERMISSION_GRANTED) {
+                    Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+                            + " requests FLAG_SINGLE_USER, but app does not hold "
+                            + android.Manifest.permission.INTERACT_ACROSS_USERS);
+                    skip = true;
+                }
+            }
+            if (r.curApp != null && r.curApp.crashing) {
+                // If the target process is crashing, just skip it.
+                Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r
+                        + " to " + r.curApp + ": process crashing");
+                skip = true;
+            }
+            if (!skip) {
+                boolean isAvailable = false;
+                try {
+                    isAvailable = AppGlobals.getPackageManager().isPackageAvailable(
+                            info.activityInfo.packageName,
+                            UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
+                } catch (Exception e) {
+                    // all such failures mean we skip this receiver
+                    Slog.w(TAG, "Exception getting recipient info for "
+                            + info.activityInfo.packageName, e);
+                }
+                if (!isAvailable) {
+                    if (DEBUG_BROADCAST) {
+                        Slog.v(TAG, "Skipping delivery to " + info.activityInfo.packageName
+                                + " / " + info.activityInfo.applicationInfo.uid
+                                + " : package no longer available");
+                    }
+                    skip = true;
+                }
+            }
+
+            if (skip) {
+                if (DEBUG_BROADCAST)  Slog.v(TAG,
+                        "Skipping delivery of ordered ["
+                        + mQueueName + "] " + r + " for whatever reason");
+                r.receiver = null;
+                r.curFilter = null;
+                r.state = BroadcastRecord.IDLE;
+                scheduleBroadcastsLocked();
+                return;
+            }
+
+            r.state = BroadcastRecord.APP_RECEIVE;
+            String targetProcess = info.activityInfo.processName;
+            r.curComponent = component;
+            if (r.callingUid != Process.SYSTEM_UID && isSingleton) {
+                info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0);
+            }
+            r.curReceiver = info.activityInfo;
+            if (DEBUG_MU && r.callingUid > UserHandle.PER_USER_RANGE) {
+                Slog.v(TAG_MU, "Updated broadcast record activity info for secondary user, "
+                        + info.activityInfo + ", callingUid = " + r.callingUid + ", uid = "
+                        + info.activityInfo.applicationInfo.uid);
+            }
+
+            // Broadcast is being executed, its package can't be stopped.
+            try {
+                AppGlobals.getPackageManager().setPackageStoppedState(
+                        r.curComponent.getPackageName(), false, UserHandle.getUserId(r.callingUid));
+            } catch (RemoteException e) {
+            } catch (IllegalArgumentException e) {
+                Slog.w(TAG, "Failed trying to unstop package "
+                        + r.curComponent.getPackageName() + ": " + e);
+            }
+
+            // Is this receiver's application already running?
+            ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
+                    info.activityInfo.applicationInfo.uid, false);
+            if (app != null && app.thread != null) {
+                try {
+                    app.addPackage(info.activityInfo.packageName, mService.mProcessStats);
+                    processCurBroadcastLocked(r, app);
+                    return;
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Exception when sending broadcast to "
+                          + r.curComponent, e);
+                } catch (RuntimeException e) {
+                    Log.wtf(TAG, "Failed sending broadcast to "
+                            + r.curComponent + " with " + r.intent, e);
+                    // If some unexpected exception happened, just skip
+                    // this broadcast.  At this point we are not in the call
+                    // from a client, so throwing an exception out from here
+                    // will crash the entire system instead of just whoever
+                    // sent the broadcast.
+                    logBroadcastReceiverDiscardLocked(r);
+                    finishReceiverLocked(r, r.resultCode, r.resultData,
+                            r.resultExtras, r.resultAbort, false);
+                    scheduleBroadcastsLocked();
+                    // We need to reset the state if we failed to start the receiver.
+                    r.state = BroadcastRecord.IDLE;
+                    return;
+                }
+
+                // If a dead object exception was thrown -- fall through to
+                // restart the application.
+            }
+
+            // Not running -- get it started, to be executed when the app comes up.
+            if (DEBUG_BROADCAST)  Slog.v(TAG,
+                    "Need to start app ["
+                    + mQueueName + "] " + targetProcess + " for broadcast " + r);
+            if ((r.curApp=mService.startProcessLocked(targetProcess,
+                    info.activityInfo.applicationInfo, true,
+                    r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
+                    "broadcast", r.curComponent,
+                    (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
+                            == null) {
+                // Ah, this recipient is unavailable.  Finish it if necessary,
+                // and mark the broadcast record as ready for the next.
+                Slog.w(TAG, "Unable to launch app "
+                        + info.activityInfo.applicationInfo.packageName + "/"
+                        + info.activityInfo.applicationInfo.uid + " for broadcast "
+                        + r.intent + ": process is bad");
+                logBroadcastReceiverDiscardLocked(r);
+                finishReceiverLocked(r, r.resultCode, r.resultData,
+                        r.resultExtras, r.resultAbort, false);
+                scheduleBroadcastsLocked();
+                r.state = BroadcastRecord.IDLE;
+                return;
+            }
+
+            mPendingBroadcast = r;
+            mPendingBroadcastRecvIndex = recIdx;
+        }
+    }
+
+    final void setBroadcastTimeoutLocked(long timeoutTime) {
+        if (! mPendingBroadcastTimeoutMessage) {
+            Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this);
+            mHandler.sendMessageAtTime(msg, timeoutTime);
+            mPendingBroadcastTimeoutMessage = true;
+        }
+    }
+
+    final void cancelBroadcastTimeoutLocked() {
+        if (mPendingBroadcastTimeoutMessage) {
+            mHandler.removeMessages(BROADCAST_TIMEOUT_MSG, this);
+            mPendingBroadcastTimeoutMessage = false;
+        }
+    }
+
+    final void broadcastTimeoutLocked(boolean fromMsg) {
+        if (fromMsg) {
+            mPendingBroadcastTimeoutMessage = false;
+        }
+
+        if (mOrderedBroadcasts.size() == 0) {
+            return;
+        }
+
+        long now = SystemClock.uptimeMillis();
+        BroadcastRecord r = mOrderedBroadcasts.get(0);
+        if (fromMsg) {
+            if (mService.mDidDexOpt) {
+                // Delay timeouts until dexopt finishes.
+                mService.mDidDexOpt = false;
+                long timeoutTime = SystemClock.uptimeMillis() + mTimeoutPeriod;
+                setBroadcastTimeoutLocked(timeoutTime);
+                return;
+            }
+            if (!mService.mProcessesReady) {
+                // Only process broadcast timeouts if the system is ready. That way
+                // PRE_BOOT_COMPLETED broadcasts can't timeout as they are intended
+                // to do heavy lifting for system up.
+                return;
+            }
+
+            long timeoutTime = r.receiverTime + mTimeoutPeriod;
+            if (timeoutTime > now) {
+                // We can observe premature timeouts because we do not cancel and reset the
+                // broadcast timeout message after each receiver finishes.  Instead, we set up
+                // an initial timeout then kick it down the road a little further as needed
+                // when it expires.
+                if (DEBUG_BROADCAST) Slog.v(TAG,
+                        "Premature timeout ["
+                        + mQueueName + "] @ " + now + ": resetting BROADCAST_TIMEOUT_MSG for "
+                        + timeoutTime);
+                setBroadcastTimeoutLocked(timeoutTime);
+                return;
+            }
+        }
+
+        BroadcastRecord br = mOrderedBroadcasts.get(0);
+        if (br.state == BroadcastRecord.WAITING_SERVICES) {
+            // In this case the broadcast had already finished, but we had decided to wait
+            // for started services to finish as well before going on.  So if we have actually
+            // waited long enough time timeout the broadcast, let's give up on the whole thing
+            // and just move on to the next.
+            Slog.i(ActivityManagerService.TAG, "Waited long enough for: " + (br.curComponent != null
+                    ? br.curComponent.flattenToShortString() : "(null)"));
+            br.curComponent = null;
+            br.state = BroadcastRecord.IDLE;
+            processNextBroadcast(false);
+            return;
+        }
+
+        Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r. receiver
+                + ", started " + (now - r.receiverTime) + "ms ago");
+        r.receiverTime = now;
+        r.anrCount++;
+
+        // Current receiver has passed its expiration date.
+        if (r.nextReceiver <= 0) {
+            Slog.w(TAG, "Timeout on receiver with nextReceiver <= 0");
+            return;
+        }
+
+        ProcessRecord app = null;
+        String anrMessage = null;
+
+        Object curReceiver = r.receivers.get(r.nextReceiver-1);
+        Slog.w(TAG, "Receiver during timeout: " + curReceiver);
+        logBroadcastReceiverDiscardLocked(r);
+        if (curReceiver instanceof BroadcastFilter) {
+            BroadcastFilter bf = (BroadcastFilter)curReceiver;
+            if (bf.receiverList.pid != 0
+                    && bf.receiverList.pid != ActivityManagerService.MY_PID) {
+                synchronized (mService.mPidsSelfLocked) {
+                    app = mService.mPidsSelfLocked.get(
+                            bf.receiverList.pid);
+                }
+            }
+        } else {
+            app = r.curApp;
+        }
+
+        if (app != null) {
+            anrMessage = "Broadcast of " + r.intent.toString();
+        }
+
+        if (mPendingBroadcast == r) {
+            mPendingBroadcast = null;
+        }
+
+        // Move on to the next receiver.
+        finishReceiverLocked(r, r.resultCode, r.resultData,
+                r.resultExtras, r.resultAbort, false);
+        scheduleBroadcastsLocked();
+
+        if (anrMessage != null) {
+            // Post the ANR to the handler since we do not want to process ANRs while
+            // potentially holding our lock.
+            mHandler.post(new AppNotResponding(app, anrMessage));
+        }
+    }
+
+    private final void addBroadcastToHistoryLocked(BroadcastRecord r) {
+        if (r.callingUid < 0) {
+            // This was from a registerReceiver() call; ignore it.
+            return;
+        }
+        System.arraycopy(mBroadcastHistory, 0, mBroadcastHistory, 1,
+                MAX_BROADCAST_HISTORY-1);
+        r.finishTime = SystemClock.uptimeMillis();
+        mBroadcastHistory[0] = r;
+        System.arraycopy(mBroadcastSummaryHistory, 0, mBroadcastSummaryHistory, 1,
+                MAX_BROADCAST_SUMMARY_HISTORY-1);
+        mBroadcastSummaryHistory[0] = r.intent;
+    }
+
+    final void logBroadcastReceiverDiscardLocked(BroadcastRecord r) {
+        if (r.nextReceiver > 0) {
+            Object curReceiver = r.receivers.get(r.nextReceiver-1);
+            if (curReceiver instanceof BroadcastFilter) {
+                BroadcastFilter bf = (BroadcastFilter) curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_FILTER,
+                        bf.owningUserId, System.identityHashCode(r),
+                        r.intent.getAction(),
+                        r.nextReceiver - 1,
+                        System.identityHashCode(bf));
+            } else {
+                ResolveInfo ri = (ResolveInfo)curReceiver;
+                EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                        UserHandle.getUserId(ri.activityInfo.applicationInfo.uid),
+                        System.identityHashCode(r), r.intent.getAction(),
+                        r.nextReceiver - 1, ri.toString());
+            }
+        } else {
+            Slog.w(TAG, "Discarding broadcast before first receiver is invoked: "
+                    + r);
+            EventLog.writeEvent(EventLogTags.AM_BROADCAST_DISCARD_APP,
+                    -1, System.identityHashCode(r),
+                    r.intent.getAction(),
+                    r.nextReceiver,
+                    "NONE");
+        }
+    }
+
+    final boolean dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll, String dumpPackage, boolean needSep) {
+        if (mParallelBroadcasts.size() > 0 || mOrderedBroadcasts.size() > 0
+                || mPendingBroadcast != null) {
+            boolean printed = false;
+            for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {
+                BroadcastRecord br = mParallelBroadcasts.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printed = true;
+                    pw.println("  Active broadcasts [" + mQueueName + "]:");
+                }
+                pw.println("  Active Broadcast " + mQueueName + " #" + i + ":");
+                br.dump(pw, "    ");
+            }
+            printed = false;
+            needSep = true;
+            for (int i=mOrderedBroadcasts.size()-1; i>=0; i--) {
+                BroadcastRecord br = mOrderedBroadcasts.get(i);
+                if (dumpPackage != null && !dumpPackage.equals(br.callerPackage)) {
+                    continue;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    printed = true;
+                    pw.println("  Active ordered broadcasts [" + mQueueName + "]:");
+                }
+                pw.println("  Active Ordered Broadcast " + mQueueName + " #" + i + ":");
+                mOrderedBroadcasts.get(i).dump(pw, "    ");
+            }
+            if (dumpPackage == null || (mPendingBroadcast != null
+                    && dumpPackage.equals(mPendingBroadcast.callerPackage))) {
+                if (needSep) {
+                    pw.println();
+                }
+                pw.println("  Pending broadcast [" + mQueueName + "]:");
+                if (mPendingBroadcast != null) {
+                    mPendingBroadcast.dump(pw, "    ");
+                } else {
+                    pw.println("    (null)");
+                }
+                needSep = true;
+            }
+        }
+
+        int i;
+        boolean printed = false;
+        for (i=0; i<MAX_BROADCAST_HISTORY; i++) {
+            BroadcastRecord r = mBroadcastHistory[i];
+            if (r == null) {
+                break;
+            }
+            if (dumpPackage != null && !dumpPackage.equals(r.callerPackage)) {
+                continue;
+            }
+            if (!printed) {
+                if (needSep) {
+                    pw.println();
+                }
+                needSep = true;
+                pw.println("  Historical broadcasts [" + mQueueName + "]:");
+                printed = true;
+            }
+            if (dumpAll) {
+                pw.print("  Historical Broadcast " + mQueueName + " #");
+                        pw.print(i); pw.println(":");
+                r.dump(pw, "    ");
+            } else {
+                pw.print("  #"); pw.print(i); pw.print(": "); pw.println(r);
+                pw.print("    ");
+                pw.println(r.intent.toShortString(false, true, true, false));
+                if (r.targetComp != null && r.targetComp != r.intent.getComponent()) {
+                    pw.print("    targetComp: "); pw.println(r.targetComp.toShortString());
+                }
+                Bundle bundle = r.intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            }
+        }
+
+        if (dumpPackage == null) {
+            if (dumpAll) {
+                i = 0;
+                printed = false;
+            }
+            for (; i<MAX_BROADCAST_SUMMARY_HISTORY; i++) {
+                Intent intent = mBroadcastSummaryHistory[i];
+                if (intent == null) {
+                    break;
+                }
+                if (!printed) {
+                    if (needSep) {
+                        pw.println();
+                    }
+                    needSep = true;
+                    pw.println("  Historical broadcasts summary [" + mQueueName + "]:");
+                    printed = true;
+                }
+                if (!dumpAll && i >= 50) {
+                    pw.println("  ...");
+                    break;
+                }
+                pw.print("  #"); pw.print(i); pw.print(": ");
+                pw.println(intent.toShortString(false, true, true, false));
+                Bundle bundle = intent.getExtras();
+                if (bundle != null) {
+                    pw.print("    extras: "); pw.println(bundle.toString());
+                }
+            }
+        }
+
+        return needSep;
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
new file mode 100644
index 0000000..b2cfd7a
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.AppOpsManager;
+import android.content.IIntentReceiver;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.SystemClock;
+import android.util.PrintWriterPrinter;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An active intent broadcast.
+ */
+final class BroadcastRecord extends Binder {
+    final Intent intent;    // the original intent that generated us
+    final ComponentName targetComp; // original component name set on the intent
+    final ProcessRecord callerApp; // process that sent this
+    final String callerPackage; // who sent this
+    final int callingPid;   // the pid of who sent this
+    final int callingUid;   // the uid of who sent this
+    final boolean ordered;  // serialize the send to receivers?
+    final boolean sticky;   // originated from existing sticky data?
+    final boolean initialSticky; // initial broadcast from register to sticky?
+    final int userId;       // user id this broadcast was for
+    final String resolvedType; // the resolved data type
+    final String requiredPermission; // a permission the caller has required
+    final int appOp;        // an app op that is associated with this broadcast
+    final List receivers;   // contains BroadcastFilter and ResolveInfo
+    IIntentReceiver resultTo; // who receives final result if non-null
+    long dispatchTime;      // when dispatch started on this set of receivers
+    long dispatchClockTime; // the clock time the dispatch started
+    long receiverTime;      // when current receiver started for timeouts.
+    long finishTime;        // when we finished the broadcast.
+    int resultCode;         // current result code value.
+    String resultData;      // current result data value.
+    Bundle resultExtras;    // current result extra data values.
+    boolean resultAbort;    // current result abortBroadcast value.
+    int nextReceiver;       // next receiver to be executed.
+    IBinder receiver;       // who is currently running, null if none.
+    int state;
+    int anrCount;           // has this broadcast record hit any ANRs?
+    BroadcastQueue queue;   // the outbound queue handling this broadcast
+
+    static final int IDLE = 0;
+    static final int APP_RECEIVE = 1;
+    static final int CALL_IN_RECEIVE = 2;
+    static final int CALL_DONE_RECEIVE = 3;
+    static final int WAITING_SERVICES = 4;
+
+    // The following are set when we are calling a receiver (one that
+    // was found in our list of registered receivers).
+    BroadcastFilter curFilter;
+
+    // The following are set only when we are launching a receiver (one
+    // that was found by querying the package manager).
+    ProcessRecord curApp;       // hosting application of current receiver.
+    ComponentName curComponent; // the receiver class that is currently running.
+    ActivityInfo curReceiver;   // info about the receiver that is currently running.
+
+    void dump(PrintWriter pw, String prefix) {
+        final long now = SystemClock.uptimeMillis();
+
+        pw.print(prefix); pw.print(this); pw.print(" to user "); pw.println(userId);
+        pw.print(prefix); pw.println(intent.toInsecureString());
+        if (targetComp != null && targetComp != intent.getComponent()) {
+            pw.print(prefix); pw.print("  targetComp: "); pw.println(targetComp.toShortString());
+        }
+        Bundle bundle = intent.getExtras();
+        if (bundle != null) {
+            pw.print(prefix); pw.print("  extras: "); pw.println(bundle.toString());
+        }
+        pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" ");
+                pw.print(callerApp != null ? callerApp.toShortString() : "null");
+                pw.print(" pid="); pw.print(callingPid);
+                pw.print(" uid="); pw.println(callingUid);
+        if (requiredPermission != null || appOp != AppOpsManager.OP_NONE) {
+            pw.print(prefix); pw.print("requiredPermission="); pw.print(requiredPermission);
+                    pw.print("  appOp="); pw.println(appOp);
+        }
+        pw.print(prefix); pw.print("dispatchClockTime=");
+                pw.println(new Date(dispatchClockTime));
+        pw.print(prefix); pw.print("dispatchTime=");
+                TimeUtils.formatDuration(dispatchTime, now, pw);
+        if (finishTime != 0) {
+            pw.print(" finishTime="); TimeUtils.formatDuration(finishTime, now, pw);
+        } else {
+            pw.print(" receiverTime="); TimeUtils.formatDuration(receiverTime, now, pw);
+        }
+        pw.println("");
+        if (anrCount != 0) {
+            pw.print(prefix); pw.print("anrCount="); pw.println(anrCount);
+        }
+        if (resultTo != null || resultCode != -1 || resultData != null) {
+            pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
+                    pw.print(" resultCode="); pw.print(resultCode);
+                    pw.print(" resultData="); pw.println(resultData);
+        }
+        if (resultExtras != null) {
+            pw.print(prefix); pw.print("resultExtras="); pw.println(resultExtras);
+        }
+        if (resultAbort || ordered || sticky || initialSticky) {
+            pw.print(prefix); pw.print("resultAbort="); pw.print(resultAbort);
+                    pw.print(" ordered="); pw.print(ordered);
+                    pw.print(" sticky="); pw.print(sticky);
+                    pw.print(" initialSticky="); pw.println(initialSticky);
+        }
+        if (nextReceiver != 0 || receiver != null) {
+            pw.print(prefix); pw.print("nextReceiver="); pw.print(nextReceiver);
+                    pw.print(" receiver="); pw.println(receiver);
+        }
+        if (curFilter != null) {
+            pw.print(prefix); pw.print("curFilter="); pw.println(curFilter);
+        }
+        if (curReceiver != null) {
+            pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
+        }
+        if (curApp != null) {
+            pw.print(prefix); pw.print("curApp="); pw.println(curApp);
+            pw.print(prefix); pw.print("curComponent=");
+                    pw.println((curComponent != null ? curComponent.toShortString() : "--"));
+            if (curReceiver != null && curReceiver.applicationInfo != null) {
+                pw.print(prefix); pw.print("curSourceDir=");
+                        pw.println(curReceiver.applicationInfo.sourceDir);
+            }
+        }
+        if (state != IDLE) {
+            String stateStr = " (?)";
+            switch (state) {
+                case APP_RECEIVE:       stateStr=" (APP_RECEIVE)"; break;
+                case CALL_IN_RECEIVE:   stateStr=" (CALL_IN_RECEIVE)"; break;
+                case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break;
+                case WAITING_SERVICES:  stateStr=" (WAITING_SERVICES)"; break;
+            }
+            pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr);
+        }
+        final int N = receivers != null ? receivers.size() : 0;
+        String p2 = prefix + "  ";
+        PrintWriterPrinter printer = new PrintWriterPrinter(pw);
+        for (int i=0; i<N; i++) {
+            Object o = receivers.get(i);
+            pw.print(prefix); pw.print("Receiver #"); pw.print(i);
+                    pw.print(": "); pw.println(o);
+            if (o instanceof BroadcastFilter)
+                ((BroadcastFilter)o).dumpBrief(pw, p2);
+            else if (o instanceof ResolveInfo)
+                ((ResolveInfo)o).dump(printer, p2);
+        }
+    }
+
+    BroadcastRecord(BroadcastQueue _queue,
+            Intent _intent, ProcessRecord _callerApp, String _callerPackage,
+            int _callingPid, int _callingUid, String _resolvedType, String _requiredPermission,
+            int _appOp, List _receivers, IIntentReceiver _resultTo, int _resultCode,
+            String _resultData, Bundle _resultExtras, boolean _serialized,
+            boolean _sticky, boolean _initialSticky,
+            int _userId) {
+        queue = _queue;
+        intent = _intent;
+        targetComp = _intent.getComponent();
+        callerApp = _callerApp;
+        callerPackage = _callerPackage;
+        callingPid = _callingPid;
+        callingUid = _callingUid;
+        resolvedType = _resolvedType;
+        requiredPermission = _requiredPermission;
+        appOp = _appOp;
+        receivers = _receivers;
+        resultTo = _resultTo;
+        resultCode = _resultCode;
+        resultData = _resultData;
+        resultExtras = _resultExtras;
+        ordered = _serialized;
+        sticky = _sticky;
+        initialSticky = _initialSticky;
+        userId = _userId;
+        nextReceiver = 0;
+        state = IDLE;
+    }
+
+    public String toString() {
+        return "BroadcastRecord{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " u" + userId + " " + intent.getAction() + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/am/CompatModeDialog.java b/services/core/java/com/android/server/am/CompatModeDialog.java
new file mode 100644
index 0000000..202cc7c
--- /dev/null
+++ b/services/core/java/com/android/server/am/CompatModeDialog.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.view.Gravity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+
+public final class CompatModeDialog extends Dialog {
+    final ActivityManagerService mService;
+    final ApplicationInfo mAppInfo;
+
+    final Switch mCompatEnabled;
+    final CheckBox mAlwaysShow;
+    final View mHint;
+
+    public CompatModeDialog(ActivityManagerService service, Context context,
+            ApplicationInfo appInfo) {
+        super(context, com.android.internal.R.style.Theme_Holo_Dialog_MinWidth);
+        setCancelable(true);
+        setCanceledOnTouchOutside(true);
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+        getWindow().setType(WindowManager.LayoutParams.TYPE_PHONE);
+        getWindow().setGravity(Gravity.BOTTOM|Gravity.CENTER_HORIZONTAL);
+        mService = service;
+        mAppInfo = appInfo;
+
+        setContentView(com.android.internal.R.layout.am_compat_mode_dialog);
+        mCompatEnabled = (Switch)findViewById(com.android.internal.R.id.compat_checkbox);
+        mCompatEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                synchronized (mService) {
+                    mService.mCompatModePackages.setPackageScreenCompatModeLocked(
+                            mAppInfo.packageName,
+                            mCompatEnabled.isChecked() ? ActivityManager.COMPAT_MODE_ENABLED
+                                    : ActivityManager.COMPAT_MODE_DISABLED);
+                    updateControls();
+                }
+            }
+        });
+        mAlwaysShow = (CheckBox)findViewById(com.android.internal.R.id.ask_checkbox);
+        mAlwaysShow.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                synchronized (mService) {
+                    mService.mCompatModePackages.setPackageAskCompatModeLocked(
+                            mAppInfo.packageName, mAlwaysShow.isChecked());
+                    updateControls();
+                }
+            }
+        });
+        mHint = findViewById(com.android.internal.R.id.reask_hint);
+
+        updateControls();
+    }
+
+    void updateControls() {
+        synchronized (mService) {
+            int mode = mService.mCompatModePackages.computeCompatModeLocked(mAppInfo);
+            mCompatEnabled.setChecked(mode == ActivityManager.COMPAT_MODE_ENABLED);
+            boolean ask = mService.mCompatModePackages.getPackageAskCompatModeLocked(
+                    mAppInfo.packageName);
+            mAlwaysShow.setChecked(ask);
+            mHint.setVisibility(ask ? View.INVISIBLE : View.VISIBLE);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
new file mode 100644
index 0000000..59e6787
--- /dev/null
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -0,0 +1,385 @@
+package com.android.server.am;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.res.CompatibilityInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+public final class CompatModePackages {
+    private final String TAG = ActivityManagerService.TAG;
+    private final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION;
+
+    private final ActivityManagerService mService;
+    private final AtomicFile mFile;
+
+    // Compatibility state: no longer ask user to select the mode.
+    public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
+    // Compatibility state: compatibility mode is enabled.
+    public static final int COMPAT_FLAG_ENABLED = 1<<1;
+
+    private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
+
+    private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+    private final Handler mHandler = new Handler() {
+        @Override public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_WRITE:
+                    saveCompatModes();
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+            }
+        }
+    };
+
+    public CompatModePackages(ActivityManagerService service, File systemDir) {
+        mService = service;
+        mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"));
+
+        FileInputStream fis = null;
+        try {
+            fis = mFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG) {
+                eventType = parser.next();
+            }
+            String tagName = parser.getName();
+            if ("compat-packages".equals(tagName)) {
+                eventType = parser.next();
+                do {
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = parser.getName();
+                        if (parser.getDepth() == 2) {
+                            if ("pkg".equals(tagName)) {
+                                String pkg = parser.getAttributeValue(null, "name");
+                                if (pkg != null) {
+                                    String mode = parser.getAttributeValue(null, "mode");
+                                    int modeInt = 0;
+                                    if (mode != null) {
+                                        try {
+                                            modeInt = Integer.parseInt(mode);
+                                        } catch (NumberFormatException e) {
+                                        }
+                                    }
+                                    mPackages.put(pkg, modeInt);
+                                }
+                            }
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Error reading compat-packages", e);
+        } catch (java.io.IOException e) {
+            if (fis != null) Slog.w(TAG, "Error reading compat-packages", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {
+                }
+            }
+        }
+    }
+
+    public HashMap<String, Integer> getPackages() {
+        return mPackages;
+    }
+
+    private int getPackageFlags(String packageName) {
+        Integer flags = mPackages.get(packageName);
+        return flags != null ? flags : 0;
+    }
+
+    public void handlePackageAddedLocked(String packageName, boolean updated) {
+        ApplicationInfo ai = null;
+        try {
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
+        } catch (RemoteException e) {
+        }
+        if (ai == null) {
+            return;
+        }
+        CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
+        final boolean mayCompat = !ci.alwaysSupportsScreen()
+                && !ci.neverSupportsScreen();
+
+        if (updated) {
+            // Update -- if the app no longer can run in compat mode, clear
+            // any current settings for it.
+            if (!mayCompat && mPackages.containsKey(packageName)) {
+                mPackages.remove(packageName);
+                mHandler.removeMessages(MSG_WRITE);
+                Message msg = mHandler.obtainMessage(MSG_WRITE);
+                mHandler.sendMessageDelayed(msg, 10000);
+            }
+        }
+    }
+
+    public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
+        CompatibilityInfo ci = new CompatibilityInfo(ai, mService.mConfiguration.screenLayout,
+                mService.mConfiguration.smallestScreenWidthDp,
+                (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0);
+        //Slog.i(TAG, "*********** COMPAT FOR PKG " + ai.packageName + ": " + ci);
+        return ci;
+    }
+
+    public int computeCompatModeLocked(ApplicationInfo ai) {
+        boolean enabled = (getPackageFlags(ai.packageName)&COMPAT_FLAG_ENABLED) != 0;
+        CompatibilityInfo info = new CompatibilityInfo(ai,
+                mService.mConfiguration.screenLayout,
+                mService.mConfiguration.smallestScreenWidthDp, enabled);
+        if (info.alwaysSupportsScreen()) {
+            return ActivityManager.COMPAT_MODE_NEVER;
+        }
+        if (info.neverSupportsScreen()) {
+            return ActivityManager.COMPAT_MODE_ALWAYS;
+        }
+        return enabled ? ActivityManager.COMPAT_MODE_ENABLED
+                : ActivityManager.COMPAT_MODE_DISABLED;
+    }
+
+    public boolean getFrontActivityAskCompatModeLocked() {
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        if (r == null) {
+            return false;
+        }
+        return getPackageAskCompatModeLocked(r.packageName);
+    }
+
+    public boolean getPackageAskCompatModeLocked(String packageName) {
+        return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
+    }
+
+    public void setFrontActivityAskCompatModeLocked(boolean ask) {
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        if (r != null) {
+            setPackageAskCompatModeLocked(r.packageName, ask);
+        }
+    }
+
+    public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
+        int curFlags = getPackageFlags(packageName);
+        int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
+        if (curFlags != newFlags) {
+            if (newFlags != 0) {
+                mPackages.put(packageName, newFlags);
+            } else {
+                mPackages.remove(packageName);
+            }
+            mHandler.removeMessages(MSG_WRITE);
+            Message msg = mHandler.obtainMessage(MSG_WRITE);
+            mHandler.sendMessageDelayed(msg, 10000);
+        }
+    }
+
+    public int getFrontActivityScreenCompatModeLocked() {
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        if (r == null) {
+            return ActivityManager.COMPAT_MODE_UNKNOWN;
+        }
+        return computeCompatModeLocked(r.info.applicationInfo);
+    }
+
+    public void setFrontActivityScreenCompatModeLocked(int mode) {
+        ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null);
+        if (r == null) {
+            Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity");
+            return;
+        }
+        setPackageScreenCompatModeLocked(r.info.applicationInfo, mode);
+    }
+
+    public int getPackageScreenCompatModeLocked(String packageName) {
+        ApplicationInfo ai = null;
+        try {
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
+        } catch (RemoteException e) {
+        }
+        if (ai == null) {
+            return ActivityManager.COMPAT_MODE_UNKNOWN;
+        }
+        return computeCompatModeLocked(ai);
+    }
+
+    public void setPackageScreenCompatModeLocked(String packageName, int mode) {
+        ApplicationInfo ai = null;
+        try {
+            ai = AppGlobals.getPackageManager().getApplicationInfo(packageName, 0, 0);
+        } catch (RemoteException e) {
+        }
+        if (ai == null) {
+            Slog.w(TAG, "setPackageScreenCompatMode failed: unknown package " + packageName);
+            return;
+        }
+        setPackageScreenCompatModeLocked(ai, mode);
+    }
+
+    private void setPackageScreenCompatModeLocked(ApplicationInfo ai, int mode) {
+        final String packageName = ai.packageName;
+
+        int curFlags = getPackageFlags(packageName);
+
+        boolean enable;
+        switch (mode) {
+            case ActivityManager.COMPAT_MODE_DISABLED:
+                enable = false;
+                break;
+            case ActivityManager.COMPAT_MODE_ENABLED:
+                enable = true;
+                break;
+            case ActivityManager.COMPAT_MODE_TOGGLE:
+                enable = (curFlags&COMPAT_FLAG_ENABLED) == 0;
+                break;
+            default:
+                Slog.w(TAG, "Unknown screen compat mode req #" + mode + "; ignoring");
+                return;
+        }
+
+        int newFlags = curFlags;
+        if (enable) {
+            newFlags |= COMPAT_FLAG_ENABLED;
+        } else {
+            newFlags &= ~COMPAT_FLAG_ENABLED;
+        }
+
+        CompatibilityInfo ci = compatibilityInfoForPackageLocked(ai);
+        if (ci.alwaysSupportsScreen()) {
+            Slog.w(TAG, "Ignoring compat mode change of " + packageName
+                    + "; compatibility never needed");
+            newFlags = 0;
+        }
+        if (ci.neverSupportsScreen()) {
+            Slog.w(TAG, "Ignoring compat mode change of " + packageName
+                    + "; compatibility always needed");
+            newFlags = 0;
+        }
+
+        if (newFlags != curFlags) {
+            if (newFlags != 0) {
+                mPackages.put(packageName, newFlags);
+            } else {
+                mPackages.remove(packageName);
+            }
+
+            // Need to get compatibility info in new state.
+            ci = compatibilityInfoForPackageLocked(ai);
+
+            mHandler.removeMessages(MSG_WRITE);
+            Message msg = mHandler.obtainMessage(MSG_WRITE);
+            mHandler.sendMessageDelayed(msg, 10000);
+
+            final ActivityStack stack = mService.getFocusedStack();
+            ActivityRecord starting = stack.restartPackage(packageName);
+
+            // Tell all processes that loaded this package about the change.
+            for (int i=mService.mLruProcesses.size()-1; i>=0; i--) {
+                ProcessRecord app = mService.mLruProcesses.get(i);
+                if (!app.pkgList.containsKey(packageName)) {
+                    continue;
+                }
+                try {
+                    if (app.thread != null) {
+                        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc "
+                                + app.processName + " new compat " + ci);
+                        app.thread.updatePackageCompatibilityInfo(packageName, ci);
+                    }
+                } catch (Exception e) {
+                }
+            }
+
+            if (starting != null) {
+                stack.ensureActivityConfigurationLocked(starting, 0);
+                // And we need to make sure at this point that all other activities
+                // are made visible with the correct configuration.
+                stack.ensureActivitiesVisibleLocked(starting, 0);
+            }
+        }
+    }
+
+    void saveCompatModes() {
+        HashMap<String, Integer> pkgs;
+        synchronized (mService) {
+            pkgs = new HashMap<String, Integer>(mPackages);
+        }
+
+        FileOutputStream fos = null;
+
+        try {
+            fos = mFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            out.startTag(null, "compat-packages");
+
+            final IPackageManager pm = AppGlobals.getPackageManager();
+            final int screenLayout = mService.mConfiguration.screenLayout;
+            final int smallestScreenWidthDp = mService.mConfiguration.smallestScreenWidthDp;
+            final Iterator<Map.Entry<String, Integer>> it = pkgs.entrySet().iterator();
+            while (it.hasNext()) {
+                Map.Entry<String, Integer> entry = it.next();
+                String pkg = entry.getKey();
+                int mode = entry.getValue();
+                if (mode == 0) {
+                    continue;
+                }
+                ApplicationInfo ai = null;
+                try {
+                    ai = pm.getApplicationInfo(pkg, 0, 0);
+                } catch (RemoteException e) {
+                }
+                if (ai == null) {
+                    continue;
+                }
+                CompatibilityInfo info = new CompatibilityInfo(ai, screenLayout,
+                        smallestScreenWidthDp, false);
+                if (info.alwaysSupportsScreen()) {
+                    continue;
+                }
+                if (info.neverSupportsScreen()) {
+                    continue;
+                }
+                out.startTag(null, "pkg");
+                out.attribute(null, "name", pkg);
+                out.attribute(null, "mode", Integer.toString(mode));
+                out.endTag(null, "pkg");
+            }
+
+            out.endTag(null, "compat-packages");
+            out.endDocument();
+
+            mFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Slog.w(TAG, "Error writing compat packages", e1);
+            if (fos != null) {
+                mFile.failWrite(fos);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
new file mode 100644
index 0000000..423e540
--- /dev/null
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.IServiceConnection;
+import android.app.PendingIntent;
+import android.content.Context;
+
+import java.io.PrintWriter;
+
+/**
+ * Description of a single binding to a service.
+ */
+final class ConnectionRecord {
+    final AppBindRecord binding;    // The application/service binding.
+    final ActivityRecord activity;  // If non-null, the owning activity.
+    final IServiceConnection conn;  // The client connection.
+    final int flags;                // Binding options.
+    final int clientLabel;          // String resource labeling this client.
+    final PendingIntent clientIntent; // How to launch the client.
+    String stringName;              // Caching of toString.
+    boolean serviceDead;            // Well is it?
+    
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "binding=" + binding);
+        if (activity != null) {
+            pw.println(prefix + "activity=" + activity);
+        }
+        pw.println(prefix + "conn=" + conn.asBinder()
+                + " flags=0x" + Integer.toHexString(flags));
+    }
+    
+    ConnectionRecord(AppBindRecord _binding, ActivityRecord _activity,
+               IServiceConnection _conn, int _flags,
+               int _clientLabel, PendingIntent _clientIntent) {
+        binding = _binding;
+        activity = _activity;
+        conn = _conn;
+        flags = _flags;
+        clientLabel = _clientLabel;
+        clientIntent = _clientIntent;
+    }
+
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ConnectionRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" u");
+        sb.append(binding.client.userId);
+        sb.append(' ');
+        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+            sb.append("CR ");
+        }
+        if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
+            sb.append("DBG ");
+        }
+        if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
+            sb.append("!FG ");
+        }
+        if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
+            sb.append("ABCLT ");
+        }
+        if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+            sb.append("OOM ");
+        }
+        if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
+            sb.append("WPRI ");
+        }
+        if ((flags&Context.BIND_IMPORTANT) != 0) {
+            sb.append("IMP ");
+        }
+        if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+            sb.append("ACT ");
+        }
+        if ((flags&Context.BIND_VISIBLE) != 0) {
+            sb.append("VIS ");
+        }
+        if ((flags&Context.BIND_SHOWING_UI) != 0) {
+            sb.append("UI ");
+        }
+        if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
+            sb.append("!VIS ");
+        }
+        if (serviceDead) {
+            sb.append("DEAD ");
+        }
+        sb.append(binding.service.shortName);
+        sb.append(":@");
+        sb.append(Integer.toHexString(System.identityHashCode(conn.asBinder())));
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
new file mode 100644
index 0000000..f2c9e2f
--- /dev/null
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.Binder;
+import android.os.SystemClock;
+import android.util.TimeUtils;
+
+/**
+ * Represents a link between a content provider and client.
+ */
+public final class ContentProviderConnection extends Binder {
+    public final ContentProviderRecord provider;
+    public final ProcessRecord client;
+    public final long createTime;
+    public int stableCount;
+    public int unstableCount;
+    // The client of this connection is currently waiting for the provider to appear.
+    // Protected by the provider lock.
+    public boolean waiting;
+    // The provider of this connection is now dead.
+    public boolean dead;
+
+    // For debugging.
+    public int numStableIncs;
+    public int numUnstableIncs;
+
+    public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client) {
+        provider = _provider;
+        client = _client;
+        createTime = SystemClock.elapsedRealtime();
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ContentProviderConnection{");
+        toShortString(sb);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public String toShortString() {
+        StringBuilder sb = new StringBuilder(128);
+        toShortString(sb);
+        return sb.toString();
+    }
+
+    public String toClientString() {
+        StringBuilder sb = new StringBuilder(128);
+        toClientString(sb);
+        return sb.toString();
+    }
+
+    public void toShortString(StringBuilder sb) {
+        sb.append(provider.toShortString());
+        sb.append("->");
+        toClientString(sb);
+    }
+
+    public void toClientString(StringBuilder sb) {
+        sb.append(client.toShortString());
+        sb.append(" s");
+        sb.append(stableCount);
+        sb.append("/");
+        sb.append(numStableIncs);
+        sb.append(" u");
+        sb.append(unstableCount);
+        sb.append("/");
+        sb.append(numUnstableIncs);
+        if (waiting) {
+            sb.append(" WAITING");
+        }
+        if (dead) {
+            sb.append(" DEAD");
+        }
+        long nowReal = SystemClock.elapsedRealtime();
+        sb.append(" ");
+        TimeUtils.formatDuration(nowReal-createTime, sb);
+    }
+}
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
new file mode 100644
index 0000000..646b7d2
--- /dev/null
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.IActivityManager.ContentProviderHolder;
+import android.content.ComponentName;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ProviderInfo;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+final class ContentProviderRecord {
+    final ActivityManagerService service;
+    public final ProviderInfo info;
+    final int uid;
+    final ApplicationInfo appInfo;
+    final ComponentName name;
+    final boolean singleton;
+    public IContentProvider provider;
+    public boolean noReleaseNeeded;
+    // All attached clients
+    final ArrayList<ContentProviderConnection> connections
+            = new ArrayList<ContentProviderConnection>();
+    //final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
+    // Handles for non-framework processes supported by this provider
+    HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
+    // Count for external process for which we have no handles.
+    int externalProcessNoHandleCount;
+    ProcessRecord proc; // if non-null, hosting process.
+    ProcessRecord launchingApp; // if non-null, waiting for this app to be launched.
+    String stringName;
+    String shortStringName;
+
+    public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
+            ApplicationInfo ai, ComponentName _name, boolean _singleton) {
+        service = _service;
+        info = _info;
+        uid = ai.uid;
+        appInfo = ai;
+        name = _name;
+        singleton = _singleton;
+        noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID;
+    }
+
+    public ContentProviderRecord(ContentProviderRecord cpr) {
+        service = cpr.service;
+        info = cpr.info;
+        uid = cpr.uid;
+        appInfo = cpr.appInfo;
+        name = cpr.name;
+        singleton = cpr.singleton;
+        noReleaseNeeded = cpr.noReleaseNeeded;
+    }
+
+    public ContentProviderHolder newHolder(ContentProviderConnection conn) {
+        ContentProviderHolder holder = new ContentProviderHolder(info);
+        holder.provider = provider;
+        holder.noReleaseNeeded = noReleaseNeeded;
+        holder.connection = conn;
+        return holder;
+    }
+
+    public boolean canRunHere(ProcessRecord app) {
+        return (info.multiprocess || info.processName.equals(app.processName))
+                && uid == app.info.uid;
+    }
+
+    public void addExternalProcessHandleLocked(IBinder token) {
+        if (token == null) {
+            externalProcessNoHandleCount++;
+        } else {
+            if (externalProcessTokenToHandle == null) {
+                externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>();
+            }
+            ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+            if (handle == null) {
+                handle = new ExternalProcessHandle(token);
+                externalProcessTokenToHandle.put(token, handle);
+            }
+            handle.mAcquisitionCount++;
+        }
+    }
+
+    public boolean removeExternalProcessHandleLocked(IBinder token) {
+        if (hasExternalProcessHandles()) {
+            boolean hasHandle = false;
+            if (externalProcessTokenToHandle != null) {
+                ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+                if (handle != null) {
+                    hasHandle = true;
+                    handle.mAcquisitionCount--;
+                    if (handle.mAcquisitionCount == 0) {
+                        removeExternalProcessHandleInternalLocked(token);
+                        return true;
+                    }
+                }
+            }
+            if (!hasHandle) {
+                externalProcessNoHandleCount--;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void removeExternalProcessHandleInternalLocked(IBinder token) {
+        ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
+        handle.unlinkFromOwnDeathLocked();
+        externalProcessTokenToHandle.remove(token);
+        if (externalProcessTokenToHandle.size() == 0) {
+            externalProcessTokenToHandle = null;
+        }
+    }
+
+    public boolean hasExternalProcessHandles() {
+        return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
+    }
+
+    void dump(PrintWriter pw, String prefix, boolean full) {
+        if (full) {
+            pw.print(prefix); pw.print("package=");
+                    pw.print(info.applicationInfo.packageName);
+                    pw.print(" process="); pw.println(info.processName);
+        }
+        pw.print(prefix); pw.print("proc="); pw.println(proc);
+        if (launchingApp != null) {
+            pw.print(prefix); pw.print("launchingApp="); pw.println(launchingApp);
+        }
+        if (full) {
+            pw.print(prefix); pw.print("uid="); pw.print(uid);
+                    pw.print(" provider="); pw.println(provider);
+        }
+        if (singleton) {
+            pw.print(prefix); pw.print("singleton="); pw.println(singleton);
+        }
+        pw.print(prefix); pw.print("authority="); pw.println(info.authority);
+        if (full) {
+            if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
+                pw.print(prefix); pw.print("isSyncable="); pw.print(info.isSyncable);
+                        pw.print(" multiprocess="); pw.print(info.multiprocess);
+                        pw.print(" initOrder="); pw.println(info.initOrder);
+            }
+        }
+        if (full) {
+            if (hasExternalProcessHandles()) {
+                pw.print(prefix); pw.print("externals=");
+                        pw.println(externalProcessTokenToHandle.size());
+            }
+        } else {
+            if (connections.size() > 0 || externalProcessNoHandleCount > 0) {
+                pw.print(prefix); pw.print(connections.size());
+                        pw.print(" connections, "); pw.print(externalProcessNoHandleCount);
+                        pw.println(" external handles");
+            }
+        }
+        if (connections.size() > 0) {
+            if (full) {
+                pw.print(prefix); pw.println("Connections:");
+            }
+            for (int i=0; i<connections.size(); i++) {
+                ContentProviderConnection conn = connections.get(i);
+                pw.print(prefix); pw.print("  -> "); pw.println(conn.toClientString());
+                if (conn.provider != this) {
+                    pw.print(prefix); pw.print("    *** WRONG PROVIDER: ");
+                            pw.println(conn.provider);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ContentProviderRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" u");
+        sb.append(UserHandle.getUserId(uid));
+        sb.append(' ');
+        sb.append(name.flattenToShortString());
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+
+    public String toShortString() {
+        if (shortStringName != null) {
+            return shortStringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append('/');
+        sb.append(name.flattenToShortString());
+        return shortStringName = sb.toString();
+    }
+
+    // This class represents a handle from an external process to a provider.
+    private class ExternalProcessHandle implements DeathRecipient {
+        private static final String LOG_TAG = "ExternalProcessHanldle";
+
+        private final IBinder mToken;
+        private int mAcquisitionCount;
+
+        public ExternalProcessHandle(IBinder token) {
+            mToken = token;
+            try {
+                token.linkToDeath(this, 0);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Couldn't register for death for token: " + mToken, re);
+            }
+        }
+
+        public void unlinkFromOwnDeathLocked() {
+            mToken.unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (service) {
+                if (hasExternalProcessHandles() &&
+                        externalProcessTokenToHandle.get(mToken) != null) {
+                    removeExternalProcessHandleInternalLocked(mToken);
+                }                        
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
new file mode 100644
index 0000000..10ea67c
--- /dev/null
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2006-2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Helper class for watching a set of core settings which the framework
+ * propagates to application processes to avoid multiple lookups and potentially
+ * disk I/O operations. Note: This class assumes that all core settings reside
+ * in {@link Settings.Secure}.
+ */
+final class CoreSettingsObserver extends ContentObserver {
+    private static final String LOG_TAG = CoreSettingsObserver.class.getSimpleName();
+
+    // mapping form property name to its type
+    private static final Map<String, Class<?>> sCoreSettingToTypeMap = new HashMap<
+            String, Class<?>>();
+    static {
+        sCoreSettingToTypeMap.put(Settings.Secure.LONG_PRESS_TIMEOUT, int.class);
+        // add other core settings here...
+    }
+
+    private final Bundle mCoreSettings = new Bundle();
+
+    private final ActivityManagerService mActivityManagerService;
+
+    public CoreSettingsObserver(ActivityManagerService activityManagerService) {
+        super(activityManagerService.mHandler);
+        mActivityManagerService = activityManagerService;
+        beginObserveCoreSettings();
+        sendCoreSettings();
+    }
+
+    public Bundle getCoreSettingsLocked() {
+        return (Bundle) mCoreSettings.clone();
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        synchronized (mActivityManagerService) {
+            sendCoreSettings();
+        }
+    }
+
+    private void sendCoreSettings() {
+        populateCoreSettings(mCoreSettings);
+        mActivityManagerService.onCoreSettingsChange(mCoreSettings);
+    }
+
+    private void beginObserveCoreSettings() {
+        for (String setting : sCoreSettingToTypeMap.keySet()) {
+            Uri uri = Settings.Secure.getUriFor(setting);
+            mActivityManagerService.mContext.getContentResolver().registerContentObserver(
+                    uri, false, this);
+        }
+    }
+
+    private void populateCoreSettings(Bundle snapshot) {
+        Context context = mActivityManagerService.mContext;
+        for (Map.Entry<String, Class<?>> entry : sCoreSettingToTypeMap.entrySet()) {
+            String setting = entry.getKey();
+            Class<?> type = entry.getValue();
+            try {
+                if (type == String.class) {
+                    String value = Settings.Secure.getString(context.getContentResolver(),
+                            setting);
+                    snapshot.putString(setting, value);
+                } else if (type == int.class) {
+                    int value = Settings.Secure.getInt(context.getContentResolver(),
+                            setting);
+                    snapshot.putInt(setting, value);
+                } else if (type == float.class) {
+                    float value = Settings.Secure.getFloat(context.getContentResolver(),
+                            setting);
+                    snapshot.putFloat(setting, value);
+                } else if (type == long.class) {
+                    long value = Settings.Secure.getLong(context.getContentResolver(),
+                            setting);
+                    snapshot.putLong(setting, value);
+                }
+            } catch (SettingNotFoundException snfe) {
+                Log.w(LOG_TAG, "Cannot find setting \"" + setting + "\"", snfe);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
new file mode 100644
index 0000000..d3cc56b
--- /dev/null
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -0,0 +1,91 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.server.am
+
+2719 configuration_changed (config mask|1|5)
+2721 cpu (total|1|6),(user|1|6),(system|1|6),(iowait|1|6),(irq|1|6),(softirq|1|6)
+
+# ActivityManagerService.systemReady() starts:
+3040 boot_progress_ams_ready (time|2|3)
+# ActivityManagerService calls enableScreenAfterBoot():
+3050 boot_progress_enable_screen (time|2|3)
+
+# Do not change these names without updating the checkin_events setting in
+# google3/googledata/wireless/android/provisioning/gservices.config !!
+#
+# An activity is being finished:
+30001 am_finish_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
+# A task is being brought to the front of the screen:
+30002 am_task_to_front (User|1|5),(Task|1|5)
+# An existing activity is being given a new intent:
+30003 am_new_intent (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+# A new task is being created:
+30004 am_create_task (User|1|5),(Task ID|1|5)
+# A new activity is being created in an existing task:
+30005 am_create_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Action|3),(MIME Type|3),(URI|3),(Flags|1|5)
+# An activity has been resumed into the foreground but was not already running:
+30006 am_restart_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+# An activity has been resumed and is now in the foreground:
+30007 am_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+# Application Not Responding
+30008 am_anr (User|1|5),(pid|1|5),(Package Name|3),(Flags|1|5),(reason|3)
+# Activity launch time
+30009 am_activity_launch_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
+# Application process bound to work
+30010 am_proc_bound (User|1|5),(PID|1|5),(Process Name|3)
+# Application process died
+30011 am_proc_died (User|1|5),(PID|1|5),(Process Name|3)
+# The Activity Manager failed to pause the given activity.
+30012 am_failed_to_pause (User|1|5),(Token|1|5),(Wanting to pause|3),(Currently pausing|3)
+# Attempting to pause the current activity
+30013 am_pause_activity (User|1|5),(Token|1|5),(Component Name|3)
+# Application process has been started
+30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)
+# An application process has been marked as bad
+30015 am_proc_bad (User|1|5),(UID|1|5),(Process Name|3)
+# An application process that was bad is now marked as good
+30016 am_proc_good (User|1|5),(UID|1|5),(Process Name|3)
+# Reporting to applications that memory is low
+30017 am_low_memory (Num Processes|1|1)
+# An activity is being destroyed:
+30018 am_destroy_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3),(Reason|3)
+# An activity has been relaunched, resumed, and is now in the foreground:
+30019 am_relaunch_resume_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+# An activity has been relaunched:
+30020 am_relaunch_activity (User|1|5),(Token|1|5),(Task ID|1|5),(Component Name|3)
+# The activity's onPause has been called.
+30021 am_on_paused_called (User|1|5),(Component Name|3)
+# The activity's onResume has been called.
+30022 am_on_resume_called (User|1|5),(Component Name|3)
+# Kill a process to reclaim memory.
+30023 am_kill (User|1|5),(PID|1|5),(Process Name|3),(OomAdj|1|5),(Reason|3)
+# Discard an undelivered serialized broadcast (timeout/ANR/crash)
+30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5)
+30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3)
+# A service is being created
+30030 am_create_service (User|1|5),(Service Record|1|5),(Name|3),(UID|1|5),(PID|1|5)
+# A service is being destroyed
+30031 am_destroy_service (User|1|5),(Service Record|1|5),(PID|1|5)
+# A process has crashed too many times, it is being cleared
+30032 am_process_crashed_too_much (User|1|5),(Name|3),(PID|1|5)
+# An unknown process is trying to attach to the activity manager
+30033 am_drop_process (PID|1|5)
+# A service has crashed too many times, it is being stopped
+30034 am_service_crashed_too_much (User|1|5),(Crash Count|1|1),(Component Name|3),(PID|1|5)
+# A service is going to be restarted after its process went away
+30035 am_schedule_service_restart (User|1|5),(Component Name|3),(Time|2|3)
+# A client was waiting for a content provider, but its process was lost
+30036 am_provider_lost_process (User|1|5),(Package Name|3),(UID|1|5),(Name|3)
+# The activity manager gave up on a new process taking too long to start
+30037 am_process_start_timeout (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3)
+
+# Unhandled exception
+30039 am_crash (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Exception|3),(Message|3),(File|3),(Line|1|5)
+# Log.wtf() called
+30040 am_wtf (User|1|5),(PID|1|5),(Process Name|3),(Flags|1|5),(Tag|3),(Message|3)
+
+# User switched
+30041 am_switch_user (id|1|5)
+
+# Activity fully drawn time
+30042 am_activity_fully_drawn_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3)
diff --git a/services/core/java/com/android/server/am/FactoryErrorDialog.java b/services/core/java/com/android/server/am/FactoryErrorDialog.java
new file mode 100644
index 0000000..f4632c1
--- /dev/null
+++ b/services/core/java/com/android/server/am/FactoryErrorDialog.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Handler;
+import android.os.Message;
+import android.view.WindowManager;
+
+final class FactoryErrorDialog extends BaseErrorDialog {
+    public FactoryErrorDialog(Context context, CharSequence msg) {
+        super(context);
+        setCancelable(false);
+        setTitle(context.getText(com.android.internal.R.string.factorytest_failed));
+        setMessage(msg);
+        setButton(DialogInterface.BUTTON_POSITIVE,
+                context.getText(com.android.internal.R.string.factorytest_reboot),
+                mHandler.obtainMessage(0));
+        WindowManager.LayoutParams attrs = getWindow().getAttributes();
+        attrs.setTitle("Factory Error");
+        getWindow().setAttributes(attrs);
+    }
+    
+    public void onStop() {
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            throw new RuntimeException("Rebooting from failed factory test");
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/am/IntentBindRecord.java b/services/core/java/com/android/server/am/IntentBindRecord.java
new file mode 100644
index 0000000..21cf266
--- /dev/null
+++ b/services/core/java/com/android/server/am/IntentBindRecord.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.ArrayMap;
+
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * A particular Intent that has been bound to a Service.
+ */
+final class IntentBindRecord {
+    /** The running service. */
+    final ServiceRecord service;
+    /** The intent that is bound.*/
+    final Intent.FilterComparison intent; // 
+    /** All apps that have bound to this Intent. */
+    final ArrayMap<ProcessRecord, AppBindRecord> apps
+            = new ArrayMap<ProcessRecord, AppBindRecord>();
+    /** Binder published from service. */
+    IBinder binder;
+    /** Set when we have initiated a request for this binder. */
+    boolean requested;
+    /** Set when we have received the requested binder. */
+    boolean received;
+    /** Set when we still need to tell the service all clients are unbound. */
+    boolean hasBound;
+    /** Set when the service's onUnbind() has asked to be told about new clients. */
+    boolean doRebind;
+    
+    String stringName;      // caching of toString
+    
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("service="); pw.println(service);
+        dumpInService(pw, prefix);
+    }
+
+    void dumpInService(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("intent={");
+                pw.print(intent.getIntent().toShortString(false, true, false, false));
+                pw.println('}');
+        pw.print(prefix); pw.print("binder="); pw.println(binder);
+        pw.print(prefix); pw.print("requested="); pw.print(requested);
+                pw.print(" received="); pw.print(received);
+                pw.print(" hasBound="); pw.print(hasBound);
+                pw.print(" doRebind="); pw.println(doRebind);
+        for (int i=0; i<apps.size(); i++) {
+            AppBindRecord a = apps.valueAt(i);
+            pw.print(prefix); pw.print("* Client AppBindRecord{");
+                    pw.print(Integer.toHexString(System.identityHashCode(a)));
+                    pw.print(' '); pw.print(a.client); pw.println('}');
+            a.dumpInIntentBind(pw, prefix + "  ");
+        }
+    }
+
+    IntentBindRecord(ServiceRecord _service, Intent.FilterComparison _intent) {
+        service = _service;
+        intent = _intent;
+    }
+
+    int collectFlags() {
+        int flags = 0;
+        for (int i=apps.size()-1; i>=0; i--) {
+            AppBindRecord app = apps.valueAt(i);
+            if (app.connections.size() > 0) {
+                for (ConnectionRecord conn : app.connections) {
+                    flags |= conn.flags;
+                }
+            }
+        }
+        return flags;
+    }
+
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("IntentBindRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        if ((collectFlags()&Context.BIND_AUTO_CREATE) != 0) {
+            sb.append("CR ");
+        }
+        sb.append(service.shortName);
+        sb.append(':');
+        if (intent != null) {
+            intent.getIntent().toShortString(sb, false, false, false, false);
+        }
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/LaunchWarningWindow.java b/services/core/java/com/android/server/am/LaunchWarningWindow.java
new file mode 100644
index 0000000..30c3066
--- /dev/null
+++ b/services/core/java/com/android/server/am/LaunchWarningWindow.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import com.android.internal.R;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.util.TypedValue;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+public final class LaunchWarningWindow extends Dialog {
+    public LaunchWarningWindow(Context context, ActivityRecord cur, ActivityRecord next) {
+        super(context, R.style.Theme_Toast);
+
+        requestWindowFeature(Window.FEATURE_LEFT_ICON);
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+        
+        setContentView(R.layout.launch_warning);
+        setTitle(context.getText(R.string.launch_warning_title));
+
+        TypedValue out = new TypedValue();
+        getContext().getTheme().resolveAttribute(android.R.attr.alertDialogIcon, out, true);
+        getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, out.resourceId);
+
+        ImageView icon = (ImageView)findViewById(R.id.replace_app_icon);
+        icon.setImageDrawable(next.info.applicationInfo.loadIcon(context.getPackageManager()));
+        TextView text = (TextView)findViewById(R.id.replace_message);
+        text.setText(context.getResources().getString(R.string.launch_warning_replace,
+                next.info.applicationInfo.loadLabel(context.getPackageManager()).toString()));
+        icon = (ImageView)findViewById(R.id.original_app_icon);
+        icon.setImageDrawable(cur.info.applicationInfo.loadIcon(context.getPackageManager()));
+        text = (TextView)findViewById(R.id.original_message);
+        text.setText(context.getResources().getString(R.string.launch_warning_original,
+                cur.info.applicationInfo.loadLabel(context.getPackageManager()).toString()));
+    }
+}
diff --git a/services/core/java/com/android/server/am/NativeCrashListener.java b/services/core/java/com/android/server/am/NativeCrashListener.java
new file mode 100644
index 0000000..2c7f1f1
--- /dev/null
+++ b/services/core/java/com/android/server/am/NativeCrashListener.java
@@ -0,0 +1,285 @@
+/*
+ * 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.am;
+
+import android.app.ApplicationErrorReport.CrashInfo;
+import android.util.Slog;
+
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.StructTimeval;
+import libcore.io.StructUcred;
+
+import static libcore.io.OsConstants.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.net.InetSocketAddress;
+import java.net.InetUnixAddress;
+
+/**
+ * Set up a Unix domain socket that debuggerd will connect() to in
+ * order to write a description of a native crash.  The crash info is
+ * then parsed and forwarded to the ActivityManagerService's normal
+ * crash handling code.
+ *
+ * Note that this component runs in a separate thread.
+ */
+final class NativeCrashListener extends Thread {
+    static final String TAG = "NativeCrashListener";
+    static final boolean DEBUG = false;
+    static final boolean MORE_DEBUG = DEBUG && false;
+
+    // Must match the path defined in debuggerd.c.
+    static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";
+
+    // Use a short timeout on socket operations and abandon the connection
+    // on hard errors
+    static final long SOCKET_TIMEOUT_MILLIS = 2000;  // 2 seconds
+
+    final ActivityManagerService mAm;
+
+    /*
+     * Spin the actual work of handling a debuggerd crash report into a
+     * separate thread so that the listener can go immediately back to
+     * accepting incoming connections.
+     */
+    class NativeCrashReporter extends Thread {
+        ProcessRecord mApp;
+        int mSignal;
+        String mCrashReport;
+
+        NativeCrashReporter(ProcessRecord app, int signal, String report) {
+            super("NativeCrashReport");
+            mApp = app;
+            mSignal = signal;
+            mCrashReport = report;
+        }
+
+        @Override
+        public void run() {
+            try {
+                CrashInfo ci = new CrashInfo();
+                ci.exceptionClassName = "Native crash";
+                ci.exceptionMessage = Libcore.os.strsignal(mSignal);
+                ci.throwFileName = "unknown";
+                ci.throwClassName = "unknown";
+                ci.throwMethodName = "unknown";
+                ci.stackTrace = mCrashReport;
+
+                if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
+                mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
+                if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");
+            } catch (Exception e) {
+                Slog.e(TAG, "Unable to report native crash", e);
+            }
+        }
+    }
+
+    /*
+     * Daemon thread that accept()s incoming domain socket connections from debuggerd
+     * and processes the crash dump that is passed through.
+     */
+    NativeCrashListener() {
+        mAm = ActivityManagerService.self();
+    }
+
+    @Override
+    public void run() {
+        final byte[] ackSignal = new byte[1];
+
+        if (DEBUG) Slog.i(TAG, "Starting up");
+
+        // The file system entity for this socket is created with 0700 perms, owned
+        // by system:system.  debuggerd runs as root, so is capable of connecting to
+        // it, but 3rd party apps cannot.
+        {
+            File socketFile = new File(DEBUGGERD_SOCKET_PATH);
+            if (socketFile.exists()) {
+                socketFile.delete();
+            }
+        }
+
+        try {
+            FileDescriptor serverFd = Libcore.os.socket(AF_UNIX, SOCK_STREAM, 0);
+            final InetUnixAddress sockAddr = new InetUnixAddress(DEBUGGERD_SOCKET_PATH);
+            Libcore.os.bind(serverFd, sockAddr, 0);
+            Libcore.os.listen(serverFd, 1);
+
+            while (true) {
+                InetSocketAddress peer = new InetSocketAddress();
+                FileDescriptor peerFd = null;
+                try {
+                    if (MORE_DEBUG) Slog.v(TAG, "Waiting for debuggerd connection");
+                    peerFd = Libcore.os.accept(serverFd, peer);
+                    if (MORE_DEBUG) Slog.v(TAG, "Got debuggerd socket " + peerFd);
+                    if (peerFd != null) {
+                        // Only the superuser is allowed to talk to us over this socket
+                        StructUcred credentials =
+                                Libcore.os.getsockoptUcred(peerFd, SOL_SOCKET, SO_PEERCRED);
+                        if (credentials.uid == 0) {
+                            // the reporting thread may take responsibility for
+                            // acking the debugger; make sure we play along.
+                            consumeNativeCrashData(peerFd);
+                        }
+                    }
+                } catch (Exception e) {
+                    Slog.w(TAG, "Error handling connection", e);
+                } finally {
+                    // Always ack debuggerd's connection to us.  The actual
+                    // byte written is irrelevant.
+                    if (peerFd != null) {
+                        try {
+                            Libcore.os.write(peerFd, ackSignal, 0, 1);
+                        } catch (Exception e) {
+                            /* we don't care about failures here */
+                            if (MORE_DEBUG) {
+                                Slog.d(TAG, "Exception writing ack: " + e.getMessage());
+                            }
+                        }
+                        try {
+                            Libcore.os.close(peerFd);
+                        } catch (ErrnoException e) {
+                            if (MORE_DEBUG) {
+                                Slog.d(TAG, "Exception closing socket: " + e.getMessage());
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Unable to init native debug socket!", e);
+        }
+    }
+
+    static int unpackInt(byte[] buf, int offset) {
+        int b0, b1, b2, b3;
+
+        b0 = ((int) buf[offset]) & 0xFF; // mask against sign extension
+        b1 = ((int) buf[offset+1]) & 0xFF;
+        b2 = ((int) buf[offset+2]) & 0xFF;
+        b3 = ((int) buf[offset+3]) & 0xFF;
+        return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
+    }
+
+    static int readExactly(FileDescriptor fd, byte[] buffer, int offset, int numBytes)
+            throws ErrnoException {
+        int totalRead = 0;
+        while (numBytes > 0) {
+            int n = Libcore.os.read(fd, buffer, offset + totalRead, numBytes);
+            if (n <= 0) {
+                if (DEBUG) {
+                    Slog.w(TAG, "Needed " + numBytes + " but saw " + n);
+                }
+                return -1;  // premature EOF or timeout
+            }
+            numBytes -= n;
+            totalRead += n;
+        }
+        return totalRead;
+    }
+
+    // Read the crash report from the debuggerd connection
+    void consumeNativeCrashData(FileDescriptor fd) {
+        if (MORE_DEBUG) Slog.i(TAG, "debuggerd connected");
+        final byte[] buf = new byte[4096];
+        final ByteArrayOutputStream os = new ByteArrayOutputStream(4096);
+
+        try {
+            StructTimeval timeout = StructTimeval.fromMillis(SOCKET_TIMEOUT_MILLIS);
+            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, timeout);
+            Libcore.os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, timeout);
+
+            // first, the pid and signal number
+            int headerBytes = readExactly(fd, buf, 0, 8);
+            if (headerBytes != 8) {
+                // protocol failure; give up
+                Slog.e(TAG, "Unable to read from debuggerd");
+                return;
+            }
+
+            int pid = unpackInt(buf, 0);
+            int signal = unpackInt(buf, 4);
+            if (DEBUG) {
+                Slog.v(TAG, "Read pid=" + pid + " signal=" + signal);
+            }
+
+            // now the text of the dump
+            if (pid > 0) {
+                final ProcessRecord pr;
+                synchronized (mAm.mPidsSelfLocked) {
+                    pr = mAm.mPidsSelfLocked.get(pid);
+                }
+                if (pr != null) {
+                    // Don't attempt crash reporting for persistent apps
+                    if (pr.persistent) {
+                        if (DEBUG) {
+                            Slog.v(TAG, "Skipping report for persistent app " + pr);
+                        }
+                        return;
+                    }
+
+                    int bytes;
+                    do {
+                        // get some data
+                        bytes = Libcore.os.read(fd, buf, 0, buf.length);
+                        if (bytes > 0) {
+                            if (MORE_DEBUG) {
+                                String s = new String(buf, 0, bytes, "UTF-8");
+                                Slog.v(TAG, "READ=" + bytes + "> " + s);
+                            }
+                            // did we just get the EOD null byte?
+                            if (buf[bytes-1] == 0) {
+                                os.write(buf, 0, bytes-1);  // exclude the EOD token
+                                break;
+                            }
+                            // no EOD, so collect it and read more
+                            os.write(buf, 0, bytes);
+                        }
+                    } while (bytes > 0);
+
+                    // Okay, we've got the report.
+                    if (DEBUG) Slog.v(TAG, "processing");
+
+                    // Mark the process record as being a native crash so that the
+                    // cleanup mechanism knows we're still submitting the report
+                    // even though the process will vanish as soon as we let
+                    // debuggerd proceed.
+                    synchronized (mAm) {
+                        pr.crashing = true;
+                        pr.forceCrashReport = true;
+                    }
+
+                    // Crash reporting is synchronous but we want to let debuggerd
+                    // go about it business right away, so we spin off the actual
+                    // reporting logic on a thread and let it take it's time.
+                    final String reportString = new String(os.toByteArray(), "UTF-8");
+                    (new NativeCrashReporter(pr, signal, reportString)).start();
+                } else {
+                    Slog.w(TAG, "Couldn't find ProcessRecord for pid " + pid);
+                }
+            } else {
+                Slog.e(TAG, "Bogus pid!");
+            }
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception dealing with report", e);
+            // ugh, fail.
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
new file mode 100644
index 0000000..17f24a9
--- /dev/null
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.ActivityManager;
+import android.content.IIntentSender;
+import android.content.IIntentReceiver;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+final class PendingIntentRecord extends IIntentSender.Stub {
+    final ActivityManagerService owner;
+    final Key key;
+    final int uid;
+    final WeakReference<PendingIntentRecord> ref;
+    boolean sent = false;
+    boolean canceled = false;
+
+    String stringName;
+    
+    final static class Key {
+        final int type;
+        final String packageName;
+        final ActivityRecord activity;
+        final String who;
+        final int requestCode;
+        final Intent requestIntent;
+        final String requestResolvedType;
+        final Bundle options;
+        Intent[] allIntents;
+        String[] allResolvedTypes;
+        final int flags;
+        final int hashCode;
+        final int userId;
+        
+        private static final int ODD_PRIME_NUMBER = 37;
+        
+        Key(int _t, String _p, ActivityRecord _a, String _w,
+                int _r, Intent[] _i, String[] _it, int _f, Bundle _o, int _userId) {
+            type = _t;
+            packageName = _p;
+            activity = _a;
+            who = _w;
+            requestCode = _r;
+            requestIntent = _i != null ? _i[_i.length-1] : null;
+            requestResolvedType = _it != null ? _it[_it.length-1] : null;
+            allIntents = _i;
+            allResolvedTypes = _it;
+            flags = _f;
+            options = _o;
+            userId = _userId;
+
+            int hash = 23;
+            hash = (ODD_PRIME_NUMBER*hash) + _f;
+            hash = (ODD_PRIME_NUMBER*hash) + _r;
+            hash = (ODD_PRIME_NUMBER*hash) + _userId;
+            if (_w != null) {
+                hash = (ODD_PRIME_NUMBER*hash) + _w.hashCode();
+            }
+            if (_a != null) {
+                hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode();
+            }
+            if (requestIntent != null) {
+                hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode();
+            }
+            if (requestResolvedType != null) {
+                hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode();
+            }
+            hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode();
+            hash = (ODD_PRIME_NUMBER*hash) + _t;
+            hashCode = hash;
+            //Slog.i(ActivityManagerService.TAG, this + " hashCode=0x"
+            //        + Integer.toHexString(hashCode));
+        }
+        
+        public boolean equals(Object otherObj) {
+            if (otherObj == null) {
+                return false;
+            }
+            try {
+                Key other = (Key)otherObj;
+                if (type != other.type) {
+                    return false;
+                }
+                if (userId != other.userId){
+                    return false;
+                }
+                if (!packageName.equals(other.packageName)) {
+                    return false;
+                }
+                if (activity != other.activity) {
+                    return false;
+                }
+                if (who != other.who) {
+                    if (who != null) {
+                        if (!who.equals(other.who)) {
+                            return false;
+                        }
+                    } else if (other.who != null) {
+                        return false;
+                    }
+                }
+                if (requestCode != other.requestCode) {
+                    return false;
+                }
+                if (requestIntent != other.requestIntent) {
+                    if (requestIntent != null) {
+                        if (!requestIntent.filterEquals(other.requestIntent)) {
+                            return false;
+                        }
+                    } else if (other.requestIntent != null) {
+                        return false;
+                    }
+                }
+                if (requestResolvedType != other.requestResolvedType) {
+                    if (requestResolvedType != null) {
+                        if (!requestResolvedType.equals(other.requestResolvedType)) {
+                            return false;
+                        }
+                    } else if (other.requestResolvedType != null) {
+                        return false;
+                    }
+                }
+                if (flags != other.flags) {
+                    return false;
+                }
+                return true;
+            } catch (ClassCastException e) {
+            }
+            return false;
+        }
+
+        public int hashCode() {
+            return hashCode;
+        }
+        
+        public String toString() {
+            return "Key{" + typeName() + " pkg=" + packageName
+                + " intent="
+                + (requestIntent != null
+                        ? requestIntent.toShortString(false, true, false, false) : "<null>")
+                + " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
+        }
+        
+        String typeName() {
+            switch (type) {
+                case ActivityManager.INTENT_SENDER_ACTIVITY:
+                    return "startActivity";
+                case ActivityManager.INTENT_SENDER_BROADCAST:
+                    return "broadcastIntent";
+                case ActivityManager.INTENT_SENDER_SERVICE:
+                    return "startService";
+                case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
+                    return "activityResult";
+            }
+            return Integer.toString(type);
+        }
+    }
+    
+    PendingIntentRecord(ActivityManagerService _owner, Key _k, int _u) {
+        owner = _owner;
+        key = _k;
+        uid = _u;
+        ref = new WeakReference<PendingIntentRecord>(this);
+    }
+
+    public int send(int code, Intent intent, String resolvedType,
+            IIntentReceiver finishedReceiver, String requiredPermission) {
+        return sendInner(code, intent, resolvedType, finishedReceiver,
+                requiredPermission, null, null, 0, 0, 0, null);
+    }
+    
+    int sendInner(int code, Intent intent, String resolvedType,
+            IIntentReceiver finishedReceiver, String requiredPermission,
+            IBinder resultTo, String resultWho, int requestCode,
+            int flagsMask, int flagsValues, Bundle options) {
+        synchronized(owner) {
+            if (!canceled) {
+                sent = true;
+                if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) {
+                    owner.cancelIntentSenderLocked(this, true);
+                    canceled = true;
+                }
+                Intent finalIntent = key.requestIntent != null
+                        ? new Intent(key.requestIntent) : new Intent();
+                if (intent != null) {
+                    int changes = finalIntent.fillIn(intent, key.flags);
+                    if ((changes&Intent.FILL_IN_DATA) == 0) {
+                        resolvedType = key.requestResolvedType;
+                    }
+                } else {
+                    resolvedType = key.requestResolvedType;
+                }
+                flagsMask &= ~Intent.IMMUTABLE_FLAGS;
+                flagsValues &= flagsMask;
+                finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues);
+                
+                final long origId = Binder.clearCallingIdentity();
+                
+                boolean sendFinish = finishedReceiver != null;
+                int userId = key.userId;
+                if (userId == UserHandle.USER_CURRENT) {
+                    userId = owner.getCurrentUserIdLocked();
+                }
+                switch (key.type) {
+                    case ActivityManager.INTENT_SENDER_ACTIVITY:
+                        if (options == null) {
+                            options = key.options;
+                        } else if (key.options != null) {
+                            Bundle opts = new Bundle(key.options);
+                            opts.putAll(options);
+                            options = opts;
+                        }
+                        try {
+                            if (key.allIntents != null && key.allIntents.length > 1) {
+                                Intent[] allIntents = new Intent[key.allIntents.length];
+                                String[] allResolvedTypes = new String[key.allIntents.length];
+                                System.arraycopy(key.allIntents, 0, allIntents, 0,
+                                        key.allIntents.length);
+                                if (key.allResolvedTypes != null) {
+                                    System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0,
+                                            key.allResolvedTypes.length);
+                                }
+                                allIntents[allIntents.length-1] = finalIntent;
+                                allResolvedTypes[allResolvedTypes.length-1] = resolvedType;
+                                owner.startActivitiesInPackage(uid, key.packageName, allIntents,
+                                        allResolvedTypes, resultTo, options, userId);
+                            } else {
+                                owner.startActivityInPackage(uid, key.packageName, finalIntent,
+                                        resolvedType, resultTo, resultWho, requestCode, 0,
+                                        options, userId);
+                            }
+                        } catch (RuntimeException e) {
+                            Slog.w(ActivityManagerService.TAG,
+                                    "Unable to send startActivity intent", e);
+                        }
+                        break;
+                    case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
+                        key.activity.task.stack.sendActivityResultLocked(-1, key.activity,
+                                key.who, key.requestCode, code, finalIntent);
+                        break;
+                    case ActivityManager.INTENT_SENDER_BROADCAST:
+                        try {
+                            // If a completion callback has been requested, require
+                            // that the broadcast be delivered synchronously
+                            owner.broadcastIntentInPackage(key.packageName, uid,
+                                    finalIntent, resolvedType,
+                                    finishedReceiver, code, null, null,
+                                requiredPermission, (finishedReceiver != null), false, userId);
+                            sendFinish = false;
+                        } catch (RuntimeException e) {
+                            Slog.w(ActivityManagerService.TAG,
+                                    "Unable to send startActivity intent", e);
+                        }
+                        break;
+                    case ActivityManager.INTENT_SENDER_SERVICE:
+                        try {
+                            owner.startServiceInPackage(uid,
+                                    finalIntent, resolvedType, userId);
+                        } catch (RuntimeException e) {
+                            Slog.w(ActivityManagerService.TAG,
+                                    "Unable to send startService intent", e);
+                        }
+                        break;
+                }
+                
+                if (sendFinish) {
+                    try {
+                        finishedReceiver.performReceive(new Intent(finalIntent), 0,
+                                null, null, false, false, key.userId);
+                    } catch (RemoteException e) {
+                    }
+                }
+                
+                Binder.restoreCallingIdentity(origId);
+                
+                return 0;
+            }
+        }
+        return ActivityManager.START_CANCELED;
+    }
+    
+    protected void finalize() throws Throwable {
+        try {
+            if (!canceled) {
+                owner.mHandler.sendMessage(owner.mHandler.obtainMessage(
+                        ActivityManagerService.FINALIZE_PENDING_INTENT_MSG, this));
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    public void completeFinalize() {
+        synchronized(owner) {
+            WeakReference<PendingIntentRecord> current =
+                    owner.mIntentSenderRecords.get(key);
+            if (current == ref) {
+                owner.mIntentSenderRecords.remove(key);
+            }
+        }
+    }
+    
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("uid="); pw.print(uid);
+                pw.print(" packageName="); pw.print(key.packageName);
+                pw.print(" type="); pw.print(key.typeName());
+                pw.print(" flags=0x"); pw.println(Integer.toHexString(key.flags));
+        if (key.activity != null || key.who != null) {
+            pw.print(prefix); pw.print("activity="); pw.print(key.activity);
+                    pw.print(" who="); pw.println(key.who);
+        }
+        if (key.requestCode != 0 || key.requestResolvedType != null) {
+            pw.print(prefix); pw.print("requestCode="); pw.print(key.requestCode);
+                    pw.print(" requestResolvedType="); pw.println(key.requestResolvedType);
+        }
+        if (key.requestIntent != null) {
+            pw.print(prefix); pw.print("requestIntent=");
+                    pw.println(key.requestIntent.toShortString(false, true, true, true));
+        }
+        if (sent || canceled) {
+            pw.print(prefix); pw.print("sent="); pw.print(sent);
+                    pw.print(" canceled="); pw.println(canceled);
+        }
+    }
+
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("PendingIntentRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        sb.append(key.packageName);
+        sb.append(' ');
+        sb.append(key.typeName());
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/PendingThumbnailsRecord.java b/services/core/java/com/android/server/am/PendingThumbnailsRecord.java
new file mode 100644
index 0000000..e4eb4d0
--- /dev/null
+++ b/services/core/java/com/android/server/am/PendingThumbnailsRecord.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.app.IThumbnailReceiver;
+
+import java.util.HashSet;
+
+/**
+ * This class keeps track of calls to getTasks() that are still
+ * waiting for thumbnail images.
+ */
+final class PendingThumbnailsRecord
+{
+    final IThumbnailReceiver receiver;   // who is waiting.
+    final HashSet<ActivityRecord> pendingRecords; // HistoryRecord objects we still wait for.
+    boolean finished;       // Is pendingRecords empty?
+
+    PendingThumbnailsRecord(IThumbnailReceiver _receiver)
+    {
+        receiver = _receiver;
+        pendingRecords = new HashSet<ActivityRecord>();
+        finished = false;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
new file mode 100644
index 0000000..d3777c7
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.app.ActivityManager;
+import com.android.internal.util.MemInfoReader;
+import com.android.server.wm.WindowManagerService;
+
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.SystemProperties;
+import android.util.Slog;
+import android.view.Display;
+
+/**
+ * Activity manager code dealing with processes.
+ */
+final class ProcessList {
+    // The minimum time we allow between crashes, for us to consider this
+    // application to be bad and stop and its services and reject broadcasts.
+    static final int MIN_CRASH_INTERVAL = 60*1000;
+
+    // OOM adjustments for processes in various states:
+
+    // Adjustment used in certain places where we don't know it yet.
+    // (Generally this is something that is going to be cached, but we
+    // don't know the exact value in the cached range to assign yet.)
+    static final int UNKNOWN_ADJ = 16;
+
+    // This is a process only hosting activities that are not visible,
+    // so it can be killed without any disruption.
+    static final int CACHED_APP_MAX_ADJ = 15;
+    static final int CACHED_APP_MIN_ADJ = 9;
+
+    // The B list of SERVICE_ADJ -- these are the old and decrepit
+    // services that aren't as shiny and interesting as the ones in the A list.
+    static final int SERVICE_B_ADJ = 8;
+
+    // This is the process of the previous application that the user was in.
+    // This process is kept above other things, because it is very common to
+    // switch back to the previous app.  This is important both for recent
+    // task switch (toggling between the two top recent apps) as well as normal
+    // UI flow such as clicking on a URI in the e-mail app to view in the browser,
+    // and then pressing back to return to e-mail.
+    static final int PREVIOUS_APP_ADJ = 7;
+
+    // This is a process holding the home application -- we want to try
+    // avoiding killing it, even if it would normally be in the background,
+    // because the user interacts with it so much.
+    static final int HOME_APP_ADJ = 6;
+
+    // This is a process holding an application service -- killing it will not
+    // have much of an impact as far as the user is concerned.
+    static final int SERVICE_ADJ = 5;
+
+    // This is a process with a heavy-weight application.  It is in the
+    // background, but we want to try to avoid killing it.  Value set in
+    // system/rootdir/init.rc on startup.
+    static final int HEAVY_WEIGHT_APP_ADJ = 4;
+
+    // This is a process currently hosting a backup operation.  Killing it
+    // is not entirely fatal but is generally a bad idea.
+    static final int BACKUP_APP_ADJ = 3;
+
+    // This is a process only hosting components that are perceptible to the
+    // user, and we really want to avoid killing them, but they are not
+    // immediately visible. An example is background music playback.
+    static final int PERCEPTIBLE_APP_ADJ = 2;
+
+    // This is a process only hosting activities that are visible to the
+    // user, so we'd prefer they don't disappear.
+    static final int VISIBLE_APP_ADJ = 1;
+
+    // This is the process running the current foreground app.  We'd really
+    // rather not kill it!
+    static final int FOREGROUND_APP_ADJ = 0;
+
+    // This is a system persistent process, such as telephony.  Definitely
+    // don't want to kill it, but doing so is not completely fatal.
+    static final int PERSISTENT_PROC_ADJ = -12;
+
+    // The system process runs at the default adjustment.
+    static final int SYSTEM_ADJ = -16;
+
+    // Special code for native processes that are not being managed by the system (so
+    // don't have an oom adj assigned by the system).
+    static final int NATIVE_ADJ = -17;
+
+    // Memory pages are 4K.
+    static final int PAGE_SIZE = 4*1024;
+
+    // The minimum number of cached apps we want to be able to keep around,
+    // without empty apps being able to push them out of memory.
+    static final int MIN_CACHED_APPS = 2;
+
+    // The maximum number of cached processes we will keep around before killing them.
+    // NOTE: this constant is *only* a control to not let us go too crazy with
+    // keeping around processes on devices with large amounts of RAM.  For devices that
+    // are tighter on RAM, the out of memory killer is responsible for killing background
+    // processes as RAM is needed, and we should *never* be relying on this limit to
+    // kill them.  Also note that this limit only applies to cached background processes;
+    // we have no limit on the number of service, visible, foreground, or other such
+    // processes and the number of those processes does not count against the cached
+    // process limit.
+    static final int MAX_CACHED_APPS = 24;
+
+    // We allow empty processes to stick around for at most 30 minutes.
+    static final long MAX_EMPTY_TIME = 30*60*1000;
+
+    // The maximum number of empty app processes we will let sit around.
+    private static final int MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS);
+
+    // The number of empty apps at which we don't consider it necessary to do
+    // memory trimming.
+    static final int TRIM_EMPTY_APPS = MAX_EMPTY_APPS/2;
+
+    // The number of cached at which we don't consider it necessary to do
+    // memory trimming.
+    static final int TRIM_CACHED_APPS = ((MAX_CACHED_APPS-MAX_EMPTY_APPS)*2)/3;
+
+    // Threshold of number of cached+empty where we consider memory critical.
+    static final int TRIM_CRITICAL_THRESHOLD = 3;
+
+    // Threshold of number of cached+empty where we consider memory critical.
+    static final int TRIM_LOW_THRESHOLD = 5;
+
+    // These are the various interesting memory levels that we will give to
+    // the OOM killer.  Note that the OOM killer only supports 6 slots, so we
+    // can't give it a different value for every possible kind of process.
+    private final int[] mOomAdj = new int[] {
+            FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
+            BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
+    };
+    // These are the low-end OOM level limits.  This is appropriate for an
+    // HVGA or smaller phone with less than 512MB.  Values are in KB.
+    private final long[] mOomMinFreeLow = new long[] {
+            8192, 12288, 16384,
+            24576, 28672, 32768
+    };
+    // These are the high-end OOM level limits.  This is appropriate for a
+    // 1280x800 or larger screen with around 1GB RAM.  Values are in KB.
+    private final long[] mOomMinFreeHigh = new long[] {
+            49152, 61440, 73728,
+            86016, 98304, 122880
+    };
+    // The actual OOM killer memory levels we are using.
+    private final long[] mOomMinFree = new long[mOomAdj.length];
+
+    private final long mTotalMemMb;
+
+    private long mCachedRestoreLevel;
+
+    private boolean mHaveDisplaySize;
+
+    ProcessList() {
+        MemInfoReader minfo = new MemInfoReader();
+        minfo.readMemInfo();
+        mTotalMemMb = minfo.getTotalSize()/(1024*1024);
+        updateOomLevels(0, 0, false);
+    }
+
+    void applyDisplaySize(WindowManagerService wm) {
+        if (!mHaveDisplaySize) {
+            Point p = new Point();
+            wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p);
+            if (p.x != 0 && p.y != 0) {
+                updateOomLevels(p.x, p.y, true);
+                mHaveDisplaySize = true;
+            }
+        }
+    }
+
+    private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
+        // Scale buckets from avail memory: at 300MB we use the lowest values to
+        // 700MB or more for the top values.
+        float scaleMem = ((float)(mTotalMemMb-300))/(700-300);
+
+        // Scale buckets from screen size.
+        int minSize = 480*800;  //  384000
+        int maxSize = 1280*800; // 1024000  230400 870400  .264
+        float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
+        if (false) {
+            Slog.i("XXXXXX", "scaleMem=" + scaleMem);
+            Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
+                    + " dh=" + displayHeight);
+        }
+
+        StringBuilder adjString = new StringBuilder();
+        StringBuilder memString = new StringBuilder();
+
+        float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
+        if (scale < 0) scale = 0;
+        else if (scale > 1) scale = 1;
+        int minfree_adj = Resources.getSystem().getInteger(
+                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
+        int minfree_abs = Resources.getSystem().getInteger(
+                com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
+        if (false) {
+            Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
+        }
+
+        for (int i=0; i<mOomAdj.length; i++) {
+            long low = mOomMinFreeLow[i];
+            long high = mOomMinFreeHigh[i];
+            mOomMinFree[i] = (long)(low + ((high-low)*scale));
+        }
+
+        if (minfree_abs >= 0) {
+            for (int i=0; i<mOomAdj.length; i++) {
+                mOomMinFree[i] = (long)((float)minfree_abs * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1]);
+            }
+        }
+
+        if (minfree_adj != 0) {
+            for (int i=0; i<mOomAdj.length; i++) {
+                mOomMinFree[i] += (long)((float)minfree_adj * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1]);
+                if (mOomMinFree[i] < 0) {
+                    mOomMinFree[i] = 0;
+                }
+            }
+        }
+
+        // The maximum size we will restore a process from cached to background, when under
+        // memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
+        // before killing background processes.
+        mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3;
+
+        for (int i=0; i<mOomAdj.length; i++) {
+            if (i > 0) {
+                adjString.append(',');
+                memString.append(',');
+            }
+            adjString.append(mOomAdj[i]);
+            memString.append((mOomMinFree[i]*1024)/PAGE_SIZE);
+        }
+
+        // Ask the kernel to try to keep enough memory free to allocate 3 full
+        // screen 32bpp buffers without entering direct reclaim.
+        int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
+        int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
+        int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
+
+        if (reserve_abs >= 0) {
+            reserve = reserve_abs;
+        }
+
+        if (reserve_adj != 0) {
+            reserve += reserve_adj;
+            if (reserve < 0) {
+                reserve = 0;
+            }
+        }
+
+        //Slog.i("XXXXXXX", "******************************* MINFREE: " + memString);
+        if (write) {
+            writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString());
+            writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString());
+            SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
+        }
+        // GB: 2048,3072,4096,6144,7168,8192
+        // HC: 8192,10240,12288,14336,16384,20480
+    }
+
+    public static int computeEmptyProcessLimit(int totalProcessLimit) {
+        return (totalProcessLimit*2)/3;
+    }
+
+    private static String buildOomTag(String prefix, String space, int val, int base) {
+        if (val == base) {
+            if (space == null) return prefix;
+            return prefix + "  ";
+        }
+        return prefix + "+" + Integer.toString(val-base);
+    }
+
+    public static String makeOomAdjString(int setAdj) {
+        if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+            return buildOomTag("cch", "  ", setAdj, ProcessList.CACHED_APP_MIN_ADJ);
+        } else if (setAdj >= ProcessList.SERVICE_B_ADJ) {
+            return buildOomTag("svcb ", null, setAdj, ProcessList.SERVICE_B_ADJ);
+        } else if (setAdj >= ProcessList.PREVIOUS_APP_ADJ) {
+            return buildOomTag("prev ", null, setAdj, ProcessList.PREVIOUS_APP_ADJ);
+        } else if (setAdj >= ProcessList.HOME_APP_ADJ) {
+            return buildOomTag("home ", null, setAdj, ProcessList.HOME_APP_ADJ);
+        } else if (setAdj >= ProcessList.SERVICE_ADJ) {
+            return buildOomTag("svc  ", null, setAdj, ProcessList.SERVICE_ADJ);
+        } else if (setAdj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+            return buildOomTag("hvy  ", null, setAdj, ProcessList.HEAVY_WEIGHT_APP_ADJ);
+        } else if (setAdj >= ProcessList.BACKUP_APP_ADJ) {
+            return buildOomTag("bkup ", null, setAdj, ProcessList.BACKUP_APP_ADJ);
+        } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+            return buildOomTag("prcp ", null, setAdj, ProcessList.PERCEPTIBLE_APP_ADJ);
+        } else if (setAdj >= ProcessList.VISIBLE_APP_ADJ) {
+            return buildOomTag("vis  ", null, setAdj, ProcessList.VISIBLE_APP_ADJ);
+        } else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) {
+            return buildOomTag("fore ", null, setAdj, ProcessList.FOREGROUND_APP_ADJ);
+        } else if (setAdj >= ProcessList.PERSISTENT_PROC_ADJ) {
+            return buildOomTag("pers ", null, setAdj, ProcessList.PERSISTENT_PROC_ADJ);
+        } else if (setAdj >= ProcessList.SYSTEM_ADJ) {
+            return buildOomTag("sys  ", null, setAdj, ProcessList.SYSTEM_ADJ);
+        } else if (setAdj >= ProcessList.NATIVE_ADJ) {
+            return buildOomTag("ntv  ", null, setAdj, ProcessList.NATIVE_ADJ);
+        } else {
+            return Integer.toString(setAdj);
+        }
+    }
+
+    public static String makeProcStateString(int curProcState) {
+        String procState;
+        switch (curProcState) {
+            case -1:
+                procState = "N ";
+                break;
+            case ActivityManager.PROCESS_STATE_PERSISTENT:
+                procState = "P ";
+                break;
+            case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
+                procState = "PU";
+                break;
+            case ActivityManager.PROCESS_STATE_TOP:
+                procState = "T ";
+                break;
+            case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+                procState = "IF";
+                break;
+            case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+                procState = "IB";
+                break;
+            case ActivityManager.PROCESS_STATE_BACKUP:
+                procState = "BU";
+                break;
+            case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
+                procState = "HW";
+                break;
+            case ActivityManager.PROCESS_STATE_SERVICE:
+                procState = "S ";
+                break;
+            case ActivityManager.PROCESS_STATE_RECEIVER:
+                procState = "R ";
+                break;
+            case ActivityManager.PROCESS_STATE_HOME:
+                procState = "HO";
+                break;
+            case ActivityManager.PROCESS_STATE_LAST_ACTIVITY:
+                procState = "LA";
+                break;
+            case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
+                procState = "CA";
+                break;
+            case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                procState = "Ca";
+                break;
+            case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+                procState = "CE";
+                break;
+            default:
+                procState = "??";
+                break;
+        }
+        return procState;
+    }
+
+    public static void appendRamKb(StringBuilder sb, long ramKb) {
+        for (int j=0, fact=10; j<6; j++, fact*=10) {
+            if (ramKb < fact) {
+                sb.append(' ');
+            }
+        }
+        sb.append(ramKb);
+    }
+
+    // The minimum amount of time after a state change it is safe ro collect PSS.
+    public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000;
+
+    // The maximum amount of time we want to go between PSS collections.
+    public static final int PSS_MAX_INTERVAL = 30*60*1000;
+
+    // The minimum amount of time between successive PSS requests for *all* processes.
+    public static final int PSS_ALL_INTERVAL = 10*60*1000;
+
+    // The minimum amount of time between successive PSS requests for a process.
+    private static final int PSS_SHORT_INTERVAL = 2*60*1000;
+
+    // The amount of time until PSS when a process first becomes top.
+    private static final int PSS_FIRST_TOP_INTERVAL = 10*1000;
+
+    // The amount of time until PSS when a process first goes into the background.
+    private static final int PSS_FIRST_BACKGROUND_INTERVAL = 20*1000;
+
+    // The amount of time until PSS when a process first becomes cached.
+    private static final int PSS_FIRST_CACHED_INTERVAL = 30*1000;
+
+    // The amount of time until PSS when an important process stays in the same state.
+    private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000;
+
+    // The amount of time until PSS when a service process stays in the same state.
+    private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000;
+
+    // The amount of time until PSS when a cached process stays in the same state.
+    private static final int PSS_SAME_CACHED_INTERVAL = 30*60*1000;
+
+    public static final int PROC_MEM_PERSISTENT = 0;
+    public static final int PROC_MEM_TOP = 1;
+    public static final int PROC_MEM_IMPORTANT = 2;
+    public static final int PROC_MEM_SERVICE = 3;
+    public static final int PROC_MEM_CACHED = 4;
+
+    private static final int[] sProcStateToProcMem = new int[] {
+        PROC_MEM_PERSISTENT,            // ActivityManager.PROCESS_STATE_PERSISTENT
+        PROC_MEM_PERSISTENT,            // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BACKUP
+        PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PROC_MEM_SERVICE,               // ActivityManager.PROCESS_STATE_SERVICE
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_RECEIVER
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_HOME
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sFirstAwakePssTimes = new long[] {
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_FIRST_TOP_INTERVAL,         // ActivityManager.PROCESS_STATE_TOP
+        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HOME
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    private static final long[] sSameAwakePssTimes = new long[] {
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_PERSISTENT_UI
+        PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_TOP
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BACKUP
+        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_SERVICE
+        PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
+    };
+
+    public static boolean procStatesDifferForMem(int procState1, int procState2) {
+        return sProcStateToProcMem[procState1] != sProcStateToProcMem[procState2];
+    }
+
+    public static long computeNextPssTime(int procState, boolean first, boolean sleeping,
+            long now) {
+        final long[] table = sleeping
+                ? (first
+                        ? sFirstAwakePssTimes
+                        : sSameAwakePssTimes)
+                : (first
+                        ? sFirstAwakePssTimes
+                        : sSameAwakePssTimes);
+        return now + table[procState];
+    }
+
+    long getMemLevel(int adjustment) {
+        for (int i=0; i<mOomAdj.length; i++) {
+            if (adjustment <= mOomAdj[i]) {
+                return mOomMinFree[i] * 1024;
+            }
+        }
+        return mOomMinFree[mOomAdj.length-1] * 1024;
+    }
+
+    /**
+     * Return the maximum pss size in kb that we consider a process acceptable to
+     * restore from its cached state for running in the background when RAM is low.
+     */
+    long getCachedRestoreThresholdKb() {
+        return mCachedRestoreLevel;
+    }
+
+    private void writeFile(String path, String data) {
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(path);
+            fos.write(data.getBytes());
+        } catch (IOException e) {
+            Slog.w(ActivityManagerService.TAG, "Unable to write " + path);
+        } finally {
+            if (fos != null) {
+                try {
+                    fos.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessMemInfo.java b/services/core/java/com/android/server/am/ProcessMemInfo.java
new file mode 100644
index 0000000..c94694e
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessMemInfo.java
@@ -0,0 +1,37 @@
+/*
+ * 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.am;
+
+public class ProcessMemInfo {
+    final String name;
+    final int pid;
+    final int oomAdj;
+    final int procState;
+    final String adjType;
+    final String adjReason;
+    long pss;
+
+    public ProcessMemInfo(String _name, int _pid, int _oomAdj, int _procState,
+            String _adjType, String _adjReason) {
+        name = _name;
+        pid = _pid;
+        oomAdj = _oomAdj;
+        procState = _procState;
+        adjType = _adjType;
+        adjReason = _adjReason;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
new file mode 100644
index 0000000..217a8d61
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.util.ArraySet;
+import com.android.internal.app.ProcessStats;
+import com.android.internal.os.BatteryStatsImpl;
+
+import android.app.ActivityManager;
+import android.app.Dialog;
+import android.app.IApplicationThread;
+import android.app.IInstrumentationWatcher;
+import android.app.IUiAutomationConnection;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.PrintWriterPrinter;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Full information about a particular process that
+ * is currently running.
+ */
+final class ProcessRecord {
+    private final BatteryStatsImpl mBatteryStats; // where to collect runtime statistics
+    final ApplicationInfo info; // all about the first app in the process
+    final boolean isolated;     // true if this is a special isolated process
+    final int uid;              // uid of process; may be different from 'info' if isolated
+    final int userId;           // user of process.
+    final String processName;   // name of the process
+    // List of packages running in the process
+    final ArrayMap<String, ProcessStats.ProcessState> pkgList
+            = new ArrayMap<String, ProcessStats.ProcessState>();
+    IApplicationThread thread;  // the actual proc...  may be null only if
+                                // 'persistent' is true (in which case we
+                                // are in the process of launching the app)
+    ProcessStats.ProcessState baseProcessTracker;
+    int pid;                    // The process of this application; 0 if none
+    boolean starting;           // True if the process is being started
+    long lastActivityTime;      // For managing the LRU list
+    long lastPssTime;           // Last time we retrieved PSS data
+    long nextPssTime;           // Next time we want to request PSS data
+    long lastStateTime;         // Last time setProcState changed
+    long initialIdlePss;        // Initial memory pss of process for idle maintenance.
+    long lastPss;               // Last computed memory pss.
+    long lastCachedPss;         // Last computed pss when in cached state.
+    int maxAdj;                 // Maximum OOM adjustment for this process
+    int curRawAdj;              // Current OOM unlimited adjustment for this process
+    int setRawAdj;              // Last set OOM unlimited adjustment for this process
+    int curAdj;                 // Current OOM adjustment for this process
+    int setAdj;                 // Last set OOM adjustment for this process
+    int curSchedGroup;          // Currently desired scheduling class
+    int setSchedGroup;          // Last set to background scheduling class
+    int trimMemoryLevel;        // Last selected memory trimming level
+    int memImportance;          // Importance constant computed from curAdj
+    int curProcState = -1;      // Currently computed process state: ActivityManager.PROCESS_STATE_*
+    int repProcState = -1;      // Last reported process state
+    int setProcState = -1;      // Last set process state in process tracker
+    int pssProcState = -1;      // The proc state we are currently requesting pss for
+    boolean serviceb;           // Process currently is on the service B list
+    boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
+    boolean keeping;            // Actively running code so don't kill due to that?
+    boolean setIsForeground;    // Running foreground UI when last set?
+    boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle?
+    boolean hasClientActivities;  // Are there any client services with activities?
+    boolean hasStartedServices; // Are there any started services running in this process?
+    boolean foregroundServices; // Running any services that are foreground?
+    boolean foregroundActivities; // Running any activities that are foreground?
+    boolean systemNoUi;         // This is a system process, but not currently showing UI.
+    boolean hasShownUi;         // Has UI been shown in this process since it was started?
+    boolean pendingUiClean;     // Want to clean up resources from showing UI?
+    boolean hasAboveClient;     // Bound using BIND_ABOVE_CLIENT, so want to be lower
+    boolean bad;                // True if disabled in the bad process list
+    boolean killedByAm;         // True when proc has been killed by activity manager, not for RAM
+    boolean procStateChanged;   // Keep track of whether we changed 'setAdj'.
+    String waitingToKill;       // Process is waiting to be killed when in the bg, and reason
+    IBinder forcingToForeground;// Token that is forcing this process to be foreground
+    int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
+    int lruSeq;                 // Sequence id for identifying LRU update cycles
+    CompatibilityInfo compat;   // last used compatibility mode
+    IBinder.DeathRecipient deathRecipient; // Who is watching for the death.
+    ComponentName instrumentationClass;// class installed to instrument app
+    ApplicationInfo instrumentationInfo; // the application being instrumented
+    String instrumentationProfileFile; // where to save profiling
+    IInstrumentationWatcher instrumentationWatcher; // who is waiting
+    IUiAutomationConnection instrumentationUiAutomationConnection; // Connection to use the UI introspection APIs.
+    Bundle instrumentationArguments;// as given to us
+    ComponentName instrumentationResultClass;// copy of instrumentationClass
+    boolean usingWrapper;       // Set to true when process was launched with a wrapper attached
+    BroadcastRecord curReceiver;// receiver currently running in the app
+    long lastWakeTime;          // How long proc held wake lock at last check
+    long lastCpuTime;           // How long proc has run CPU at last check
+    long curCpuTime;            // How long proc has run CPU most recently
+    long lastRequestedGc;       // When we last asked the app to do a gc
+    long lastLowMemory;         // When we last told the app that memory is low
+    boolean reportLowMemory;    // Set to true when waiting to report low mem
+    boolean empty;              // Is this an empty background process?
+    boolean cached;             // Is this a cached process?
+    String adjType;             // Debugging: primary thing impacting oom_adj.
+    int adjTypeCode;            // Debugging: adj code to report to app.
+    Object adjSource;           // Debugging: option dependent object.
+    int adjSourceOom;           // Debugging: oom_adj of adjSource's process.
+    Object adjTarget;           // Debugging: target component impacting oom_adj.
+    
+    // contains HistoryRecord objects
+    final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+    // all ServiceRecord running in this process
+    final ArraySet<ServiceRecord> services = new ArraySet<ServiceRecord>();
+    // services that are currently executing code (need to remain foreground).
+    final ArraySet<ServiceRecord> executingServices
+             = new ArraySet<ServiceRecord>();
+    // All ConnectionRecord this process holds
+    final ArraySet<ConnectionRecord> connections
+            = new ArraySet<ConnectionRecord>();
+    // all IIntentReceivers that are registered from this process.
+    final ArraySet<ReceiverList> receivers = new ArraySet<ReceiverList>();
+    // class (String) -> ContentProviderRecord
+    final ArrayMap<String, ContentProviderRecord> pubProviders
+            = new ArrayMap<String, ContentProviderRecord>();
+    // All ContentProviderRecord process is using
+    final ArrayList<ContentProviderConnection> conProviders
+            = new ArrayList<ContentProviderConnection>();
+
+    boolean execServicesFg;     // do we need to be executing services in the foreground?
+    boolean persistent;         // always keep this application running?
+    boolean crashing;           // are we in the process of crashing?
+    Dialog crashDialog;         // dialog being displayed due to crash.
+    boolean forceCrashReport;   // suppress normal auto-dismiss of crash dialog & report UI?
+    boolean notResponding;      // does the app have a not responding dialog?
+    Dialog anrDialog;           // dialog being displayed due to app not resp.
+    boolean removed;            // has app package been removed from device?
+    boolean debugging;          // was app launched for debugging?
+    boolean waitedForDebugger;  // has process show wait for debugger dialog?
+    Dialog waitDialog;          // current wait for debugger dialog
+    
+    String shortStringName;     // caching of toShortString() result.
+    String stringName;          // caching of toString() result.
+    
+    // These reports are generated & stored when an app gets into an error condition.
+    // They will be "null" when all is OK.
+    ActivityManager.ProcessErrorStateInfo crashingReport;
+    ActivityManager.ProcessErrorStateInfo notRespondingReport;
+
+    // Who will be notified of the error. This is usually an activity in the
+    // app that installed the package.
+    ComponentName errorReportReceiver;
+
+    void dump(PrintWriter pw, String prefix) {
+        final long now = SystemClock.uptimeMillis();
+
+        pw.print(prefix); pw.print("user #"); pw.print(userId);
+                pw.print(" uid="); pw.print(info.uid);
+        if (uid != info.uid) {
+            pw.print(" ISOLATED uid="); pw.print(uid);
+        }
+        pw.println();
+        if (info.className != null) {
+            pw.print(prefix); pw.print("class="); pw.println(info.className);
+        }
+        if (info.manageSpaceActivityName != null) {
+            pw.print(prefix); pw.print("manageSpaceActivityName=");
+            pw.println(info.manageSpaceActivityName);
+        }
+        pw.print(prefix); pw.print("dir="); pw.print(info.sourceDir);
+                pw.print(" publicDir="); pw.print(info.publicSourceDir);
+                pw.print(" data="); pw.println(info.dataDir);
+        pw.print(prefix); pw.print("packageList={");
+        for (int i=0; i<pkgList.size(); i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(pkgList.keyAt(i));
+        }
+        pw.println("}");
+        pw.print(prefix); pw.print("compat="); pw.println(compat);
+        if (instrumentationClass != null || instrumentationProfileFile != null
+                || instrumentationArguments != null) {
+            pw.print(prefix); pw.print("instrumentationClass=");
+                    pw.print(instrumentationClass);
+                    pw.print(" instrumentationProfileFile=");
+                    pw.println(instrumentationProfileFile);
+            pw.print(prefix); pw.print("instrumentationArguments=");
+                    pw.println(instrumentationArguments);
+            pw.print(prefix); pw.print("instrumentationInfo=");
+                    pw.println(instrumentationInfo);
+            if (instrumentationInfo != null) {
+                instrumentationInfo.dump(new PrintWriterPrinter(pw), prefix + "  ");
+            }
+        }
+        pw.print(prefix); pw.print("thread="); pw.println(thread);
+        pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting=");
+                pw.println(starting);
+        pw.print(prefix); pw.print("lastActivityTime=");
+                TimeUtils.formatDuration(lastActivityTime, now, pw);
+                pw.print(" lastPssTime=");
+                TimeUtils.formatDuration(lastPssTime, now, pw);
+                pw.print(" nextPssTime=");
+                TimeUtils.formatDuration(nextPssTime, now, pw);
+                pw.println();
+        pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
+                pw.print(" lruSeq="); pw.print(lruSeq);
+                pw.print(" lastPss="); pw.print(lastPss);
+                pw.print(" lastCachedPss="); pw.println(lastCachedPss);
+        pw.print(prefix); pw.print("keeping="); pw.print(keeping);
+                pw.print(" cached="); pw.print(cached);
+                pw.print(" empty="); pw.println(empty);
+        if (serviceb) {
+            pw.print(prefix); pw.print("serviceb="); pw.print(serviceb);
+                    pw.print(" serviceHighRam="); pw.println(serviceHighRam);
+        }
+        if (notCachedSinceIdle) {
+            pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle);
+                    pw.print(" initialIdlePss="); pw.println(initialIdlePss);
+        }
+        pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
+                pw.print(" curRaw="); pw.print(curRawAdj);
+                pw.print(" setRaw="); pw.print(setRawAdj);
+                pw.print(" cur="); pw.print(curAdj);
+                pw.print(" set="); pw.println(setAdj);
+        pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup);
+                pw.print(" setSchedGroup="); pw.print(setSchedGroup);
+                pw.print(" systemNoUi="); pw.print(systemNoUi);
+                pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel);
+        pw.print(prefix); pw.print("curProcState="); pw.print(curProcState);
+                pw.print(" repProcState="); pw.print(repProcState);
+                pw.print(" pssProcState="); pw.print(pssProcState);
+                pw.print(" setProcState="); pw.print(setProcState);
+                pw.print(" lastStateTime=");
+                TimeUtils.formatDuration(lastStateTime, now, pw);
+                pw.println();
+        if (hasShownUi || pendingUiClean || hasAboveClient) {
+            pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi);
+                    pw.print(" pendingUiClean="); pw.print(pendingUiClean);
+                    pw.print(" hasAboveClient="); pw.println(hasAboveClient);
+        }
+        if (setIsForeground || foregroundServices || forcingToForeground != null) {
+            pw.print(prefix); pw.print("setIsForeground="); pw.print(setIsForeground);
+                    pw.print(" foregroundServices="); pw.print(foregroundServices);
+                    pw.print(" forcingToForeground="); pw.println(forcingToForeground);
+        }
+        if (persistent || removed) {
+            pw.print(prefix); pw.print("persistent="); pw.print(persistent);
+                    pw.print(" removed="); pw.println(removed);
+        }
+        if (hasClientActivities || foregroundActivities) {
+            pw.print(prefix); pw.print("hasClientActivities="); pw.print(hasClientActivities);
+                    pw.print(" foregroundActivities="); pw.println(foregroundActivities);
+        }
+        if (hasStartedServices) {
+            pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices);
+        }
+        if (!keeping) {
+            long wtime;
+            synchronized (mBatteryStats) {
+                wtime = mBatteryStats.getProcessWakeTime(info.uid,
+                        pid, SystemClock.elapsedRealtime());
+            }
+            long timeUsed = wtime - lastWakeTime;
+            pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
+                    pw.print(" timeUsed=");
+                    TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+            pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
+                    pw.print(" timeUsed=");
+                    TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
+        }
+        pw.print(prefix); pw.print("lastRequestedGc=");
+                TimeUtils.formatDuration(lastRequestedGc, now, pw);
+                pw.print(" lastLowMemory=");
+                TimeUtils.formatDuration(lastLowMemory, now, pw);
+                pw.print(" reportLowMemory="); pw.println(reportLowMemory);
+        if (killedByAm || waitingToKill != null) {
+            pw.print(prefix); pw.print("killedByAm="); pw.print(killedByAm);
+                    pw.print(" waitingToKill="); pw.println(waitingToKill);
+        }
+        if (debugging || crashing || crashDialog != null || notResponding
+                || anrDialog != null || bad) {
+            pw.print(prefix); pw.print("debugging="); pw.print(debugging);
+                    pw.print(" crashing="); pw.print(crashing);
+                    pw.print(" "); pw.print(crashDialog);
+                    pw.print(" notResponding="); pw.print(notResponding);
+                    pw.print(" " ); pw.print(anrDialog);
+                    pw.print(" bad="); pw.print(bad);
+
+                    // crashing or notResponding is always set before errorReportReceiver
+                    if (errorReportReceiver != null) {
+                        pw.print(" errorReportReceiver=");
+                        pw.print(errorReportReceiver.flattenToShortString());
+                    }
+                    pw.println();
+        }
+        if (activities.size() > 0) {
+            pw.print(prefix); pw.println("Activities:");
+            for (int i=0; i<activities.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(activities.get(i));
+            }
+        }
+        if (services.size() > 0) {
+            pw.print(prefix); pw.println("Services:");
+            for (int i=0; i<services.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(services.valueAt(i));
+            }
+        }
+        if (executingServices.size() > 0) {
+            pw.print(prefix); pw.print("Executing Services (fg=");
+            pw.print(execServicesFg); pw.println(")");
+            for (int i=0; i<executingServices.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(executingServices.valueAt(i));
+            }
+        }
+        if (connections.size() > 0) {
+            pw.print(prefix); pw.println("Connections:");
+            for (int i=0; i<connections.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(connections.valueAt(i));
+            }
+        }
+        if (pubProviders.size() > 0) {
+            pw.print(prefix); pw.println("Published Providers:");
+            for (int i=0; i<pubProviders.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(pubProviders.keyAt(i));
+                pw.print(prefix); pw.print("    -> "); pw.println(pubProviders.valueAt(i));
+            }
+        }
+        if (conProviders.size() > 0) {
+            pw.print(prefix); pw.println("Connected Providers:");
+            for (int i=0; i<conProviders.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(conProviders.get(i).toShortString());
+            }
+        }
+        if (curReceiver != null) {
+            pw.print(prefix); pw.print("curReceiver="); pw.println(curReceiver);
+        }
+        if (receivers.size() > 0) {
+            pw.print(prefix); pw.println("Receivers:");
+            for (int i=0; i<receivers.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(receivers.valueAt(i));
+            }
+        }
+    }
+    
+    ProcessRecord(BatteryStatsImpl _batteryStats, ApplicationInfo _info,
+            String _processName, int _uid) {
+        mBatteryStats = _batteryStats;
+        info = _info;
+        isolated = _info.uid != _uid;
+        uid = _uid;
+        userId = UserHandle.getUserId(_uid);
+        processName = _processName;
+        pkgList.put(_info.packageName, null);
+        maxAdj = ProcessList.UNKNOWN_ADJ;
+        curRawAdj = setRawAdj = -100;
+        curAdj = setAdj = -100;
+        persistent = false;
+        removed = false;
+        lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis();
+    }
+
+    public void setPid(int _pid) {
+        pid = _pid;
+        shortStringName = null;
+        stringName = null;
+    }
+
+    public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
+        if (thread == null) {
+            final ProcessStats.ProcessState origBase = baseProcessTracker;
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid,
+                    processName);
+            baseProcessTracker.makeActive();
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName);
+                if (ps != baseProcessTracker) {
+                    ps.makeActive();
+                }
+                pkgList.setValueAt(i, ps);
+            }
+        }
+        thread = _thread;
+    }
+
+    public void makeInactive(ProcessStatsService tracker) {
+        thread = null;
+        final ProcessStats.ProcessState origBase = baseProcessTracker;
+        if (origBase != null) {
+            if (origBase != null) {
+                origBase.setState(ProcessStats.STATE_NOTHING,
+                        tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList);
+                origBase.makeInactive();
+            }
+            baseProcessTracker = null;
+            for (int i=0; i<pkgList.size(); i++) {
+                ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                if (ps != null && ps != origBase) {
+                    ps.makeInactive();
+                }
+                pkgList.setValueAt(i, null);
+            }
+        }
+    }
+
+    /**
+     * This method returns true if any of the activities within the process record are interesting
+     * to the user. See HistoryRecord.isInterestingToUserLocked()
+     */
+    public boolean isInterestingToUserLocked() {
+        final int size = activities.size();
+        for (int i = 0 ; i < size ; i++) {
+            ActivityRecord r = activities.get(i);
+            if (r.isInterestingToUserLocked()) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    public void stopFreezingAllLocked() {
+        int i = activities.size();
+        while (i > 0) {
+            i--;
+            activities.get(i).stopFreezingScreenLocked(true);
+        }
+    }
+    
+    public void unlinkDeathRecipient() {
+        if (deathRecipient != null && thread != null) {
+            thread.asBinder().unlinkToDeath(deathRecipient, 0);
+        }
+        deathRecipient = null;
+    }
+
+    void updateHasAboveClientLocked() {
+        hasAboveClient = false;
+        for (int i=connections.size()-1; i>=0; i--) {
+            ConnectionRecord cr = connections.valueAt(i);
+            if ((cr.flags&Context.BIND_ABOVE_CLIENT) != 0) {
+                hasAboveClient = true;
+                break;
+            }
+        }
+    }
+
+    int modifyRawOomAdj(int adj) {
+        if (hasAboveClient) {
+            // If this process has bound to any services with BIND_ABOVE_CLIENT,
+            // then we need to drop its adjustment to be lower than the service's
+            // in order to honor the request.  We want to drop it by one adjustment
+            // level...  but there is special meaning applied to various levels so
+            // we will skip some of them.
+            if (adj < ProcessList.FOREGROUND_APP_ADJ) {
+                // System process will not get dropped, ever
+            } else if (adj < ProcessList.VISIBLE_APP_ADJ) {
+                adj = ProcessList.VISIBLE_APP_ADJ;
+            } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) {
+                adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+            } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) {
+                adj = ProcessList.CACHED_APP_MIN_ADJ;
+            } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) {
+                adj++;
+            }
+        }
+        return adj;
+    }
+
+    public String toShortString() {
+        if (shortStringName != null) {
+            return shortStringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        toShortString(sb);
+        return shortStringName = sb.toString();
+    }
+    
+    void toShortString(StringBuilder sb) {
+        sb.append(pid);
+        sb.append(':');
+        sb.append(processName);
+        sb.append('/');
+        if (info.uid < Process.FIRST_APPLICATION_UID) {
+            sb.append(uid);
+        } else {
+            sb.append('u');
+            sb.append(userId);
+            int appId = UserHandle.getAppId(info.uid);
+            if (appId >= Process.FIRST_APPLICATION_UID) {
+                sb.append('a');
+                sb.append(appId - Process.FIRST_APPLICATION_UID);
+            } else {
+                sb.append('s');
+                sb.append(appId);
+            }
+            if (uid != info.uid) {
+                sb.append('i');
+                sb.append(UserHandle.getAppId(uid) - Process.FIRST_ISOLATED_UID);
+            }
+        }
+    }
+    
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ProcessRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        toShortString(sb);
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+
+    public String makeAdjReason() {
+        if (adjSource != null || adjTarget != null) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append(' ');
+            if (adjTarget instanceof ComponentName) {
+                sb.append(((ComponentName)adjTarget).flattenToShortString());
+            } else if (adjTarget != null) {
+                sb.append(adjTarget.toString());
+            } else {
+                sb.append("{null}");
+            }
+            sb.append("<=");
+            if (adjSource instanceof ProcessRecord) {
+                sb.append("Proc{");
+                sb.append(((ProcessRecord)adjSource).toShortString());
+                sb.append("}");
+            } else if (adjSource != null) {
+                sb.append(adjSource.toString());
+            } else {
+                sb.append("{null}");
+            }
+            return sb.toString();
+        }
+        return null;
+    }
+
+    /*
+     *  Return true if package has been added false if not
+     */
+    public boolean addPackage(String pkg, ProcessStatsService tracker) {
+        if (!pkgList.containsKey(pkg)) {
+            if (baseProcessTracker != null) {
+                ProcessStats.ProcessState state = tracker.getProcessStateLocked(
+                        pkg, info.uid, processName);
+                pkgList.put(pkg, state);
+                if (state != baseProcessTracker) {
+                    state.makeActive();
+                }
+            } else {
+                pkgList.put(pkg, null);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public int getSetAdjWithServices() {
+        if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+            if (hasStartedServices) {
+                return ProcessList.SERVICE_B_ADJ;
+            }
+        }
+        return setAdj;
+    }
+
+    public void forceProcessStateUpTo(int newState) {
+        if (repProcState > newState) {
+            curProcState = repProcState = newState;
+        }
+    }
+
+    /*
+     *  Delete all packages from list except the package indicated in info
+     */
+    public void resetPackageList(ProcessStatsService tracker) {
+        final int N = pkgList.size();
+        if (baseProcessTracker != null) {
+            long now = SystemClock.uptimeMillis();
+            baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
+                    tracker.getMemFactorLocked(), now, pkgList);
+            if (N != 1) {
+                for (int i=0; i<N; i++) {
+                    ProcessStats.ProcessState ps = pkgList.valueAt(i);
+                    if (ps != null && ps != baseProcessTracker) {
+                        ps.makeInactive();
+                    }
+
+                }
+                pkgList.clear();
+                ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
+                        info.packageName, info.uid, processName);
+                pkgList.put(info.packageName, ps);
+                if (ps != baseProcessTracker) {
+                    ps.makeActive();
+                }
+            }
+        } else if (N != 1) {
+            pkgList.clear();
+            pkgList.put(info.packageName, null);
+        }
+    }
+    
+    public String[] getPackageList() {
+        int size = pkgList.size();
+        if (size == 0) {
+            return null;
+        }
+        String list[] = new String[size];
+        for (int i=0; i<pkgList.size(); i++) {
+            list[i] = pkgList.keyAt(i);
+        }
+        return list;
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
new file mode 100644
index 0000000..e05fcda
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -0,0 +1,909 @@
+/*
+ * 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.am;
+
+import android.app.AppGlobals;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+import com.android.internal.app.IProcessStats;
+import com.android.internal.app.ProcessStats;
+import com.android.internal.os.BackgroundThread;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
+
+public final class ProcessStatsService extends IProcessStats.Stub {
+    static final String TAG = "ProcessStatsService";
+    static final boolean DEBUG = false;
+
+    // Most data is kept in a sparse data structure: an integer array which integer
+    // holds the type of the entry, and the identifier for a long array that data
+    // exists in and the offset into the array to find it.  The constants below
+    // define the encoding of that data in an integer.
+
+    static final int MAX_HISTORIC_STATES = 8;   // Maximum number of historic states we will keep.
+    static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames.
+    static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames.
+    static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in.
+    static long WRITE_PERIOD = 30*60*1000;      // Write file every 30 minutes or so.
+
+    final ActivityManagerService mAm;
+    final File mBaseDir;
+    ProcessStats mProcessStats;
+    AtomicFile mFile;
+    boolean mCommitPending;
+    boolean mShuttingDown;
+    int mLastMemOnlyState = -1;
+    boolean mMemFactorLowered;
+
+    final ReentrantLock mWriteLock = new ReentrantLock();
+    final Object mPendingWriteLock = new Object();
+    AtomicFile mPendingWriteFile;
+    Parcel mPendingWrite;
+    boolean mPendingWriteCommitted;
+    long mLastWriteTime;
+
+    public ProcessStatsService(ActivityManagerService am, File file) {
+        mAm = am;
+        mBaseDir = file;
+        mBaseDir.mkdirs();
+        mProcessStats = new ProcessStats(true);
+        updateFile();
+        SystemProperties.addChangeCallback(new Runnable() {
+            @Override public void run() {
+                synchronized (mAm) {
+                    if (mProcessStats.evaluateSystemProperties(false)) {
+                        mProcessStats.mFlags |= ProcessStats.FLAG_SYSPROPS;
+                        writeStateLocked(true, true);
+                        mProcessStats.evaluateSystemProperties(true);
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Process Stats Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    public ProcessStats.ProcessState getProcessStateLocked(String packageName,
+            int uid, String processName) {
+        return mProcessStats.getProcessStateLocked(packageName, uid, processName);
+    }
+
+    public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid,
+            String processName, String className) {
+        return mProcessStats.getServiceStateLocked(packageName, uid, processName, className);
+    }
+
+    public boolean isMemFactorLowered() {
+        return mMemFactorLowered;
+    }
+
+    public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) {
+        mMemFactorLowered = memFactor < mLastMemOnlyState;
+        mLastMemOnlyState = memFactor;
+        if (screenOn) {
+            memFactor += ProcessStats.ADJ_SCREEN_ON;
+        }
+        if (memFactor != mProcessStats.mMemFactor) {
+            if (mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING) {
+                mProcessStats.mMemFactorDurations[mProcessStats.mMemFactor]
+                        += now - mProcessStats.mStartTime;
+            }
+            mProcessStats.mMemFactor = memFactor;
+            mProcessStats.mStartTime = now;
+            ArrayMap<String, SparseArray<ProcessStats.PackageState>> pmap
+                    = mProcessStats.mPackages.getMap();
+            for (int i=0; i<pmap.size(); i++) {
+                SparseArray<ProcessStats.PackageState> uids = pmap.valueAt(i);
+                for (int j=0; j<uids.size(); j++) {
+                    ProcessStats.PackageState pkg = uids.valueAt(j);
+                    ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices;
+                    for (int k=0; k<services.size(); k++) {
+                        ProcessStats.ServiceState service = services.valueAt(k);
+                        if (service.isInUse()) {
+                            if (service.mStartedState != ProcessStats.STATE_NOTHING) {
+                                service.setStarted(true, memFactor, now);
+                            }
+                            if (service.mBoundState != ProcessStats.STATE_NOTHING) {
+                                service.setBound(true, memFactor, now);
+                            }
+                            if (service.mExecState != ProcessStats.STATE_NOTHING) {
+                                service.setExecuting(true, memFactor, now);
+                            }
+                        }
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public int getMemFactorLocked() {
+        return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
+    }
+
+    public boolean shouldWriteNowLocked(long now) {
+        if (now > (mLastWriteTime+WRITE_PERIOD)) {
+            if (SystemClock.elapsedRealtime()
+                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) {
+                mCommitPending = true;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    public void shutdownLocked() {
+        Slog.w(TAG, "Writing process stats before shutdown...");
+        mProcessStats.mFlags |= ProcessStats.FLAG_SHUTDOWN;
+        writeStateSyncLocked();
+        mShuttingDown = true;
+    }
+
+    public void writeStateAsyncLocked() {
+        writeStateLocked(false);
+    }
+
+    public void writeStateSyncLocked() {
+        writeStateLocked(true);
+    }
+
+    private void writeStateLocked(boolean sync) {
+        if (mShuttingDown) {
+            return;
+        }
+        boolean commitPending = mCommitPending;
+        mCommitPending = false;
+        writeStateLocked(sync, commitPending);
+    }
+
+    public void writeStateLocked(boolean sync, final boolean commit) {
+        synchronized (mPendingWriteLock) {
+            long now = SystemClock.uptimeMillis();
+            if (mPendingWrite == null || !mPendingWriteCommitted) {
+                mPendingWrite = Parcel.obtain();
+                mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                if (commit) {
+                    mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
+                }
+                mProcessStats.writeToParcel(mPendingWrite, 0);
+                mPendingWriteFile = new AtomicFile(mFile.getBaseFile());
+                mPendingWriteCommitted = commit;
+            }
+            if (commit) {
+                mProcessStats.resetSafely();
+                updateFile();
+            }
+            mLastWriteTime = SystemClock.uptimeMillis();
+            Slog.i(TAG, "Prepared write state in " + (SystemClock.uptimeMillis()-now) + "ms");
+            if (!sync) {
+                BackgroundThread.getHandler().post(new Runnable() {
+                    @Override public void run() {
+                        performWriteState();
+                    }
+                });
+                return;
+            }
+        }
+
+        performWriteState();
+    }
+
+    private void updateFile() {
+        mFile = new AtomicFile(new File(mBaseDir, STATE_FILE_PREFIX
+                + mProcessStats.mTimePeriodStartClockStr + STATE_FILE_SUFFIX));
+        mLastWriteTime = SystemClock.uptimeMillis();
+    }
+
+    void performWriteState() {
+        if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile());
+        Parcel data;
+        AtomicFile file;
+        synchronized (mPendingWriteLock) {
+            data = mPendingWrite;
+            file = mPendingWriteFile;
+            mPendingWriteCommitted = false;
+            if (data == null) {
+                return;
+            }
+            mPendingWrite = null;
+            mPendingWriteFile = null;
+            mWriteLock.lock();
+        }
+
+        FileOutputStream stream = null;
+        try {
+            stream = file.startWrite();
+            stream.write(data.marshall());
+            stream.flush();
+            file.finishWrite(stream);
+            if (DEBUG) Slog.d(TAG, "Write completed successfully!");
+        } catch (IOException e) {
+            Slog.w(TAG, "Error writing process statistics", e);
+            file.failWrite(stream);
+        } finally {
+            data.recycle();
+            trimHistoricStatesWriteLocked();
+            mWriteLock.unlock();
+        }
+    }
+
+    boolean readLocked(ProcessStats stats, AtomicFile file) {
+        try {
+            FileInputStream stream = file.openRead();
+            stats.read(stream);
+            stream.close();
+            if (stats.mReadError != null) {
+                Slog.w(TAG, "Ignoring existing stats; " + stats.mReadError);
+                if (DEBUG) {
+                    ArrayMap<String, SparseArray<ProcessStats.ProcessState>> procMap
+                            = stats.mProcesses.getMap();
+                    final int NPROC = procMap.size();
+                    for (int ip=0; ip<NPROC; ip++) {
+                        Slog.w(TAG, "Process: " + procMap.keyAt(ip));
+                        SparseArray<ProcessStats.ProcessState> uids = procMap.valueAt(ip);
+                        final int NUID = uids.size();
+                        for (int iu=0; iu<NUID; iu++) {
+                            Slog.w(TAG, "  Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu));
+                        }
+                    }
+                    ArrayMap<String, SparseArray<ProcessStats.PackageState>> pkgMap
+                            = stats.mPackages.getMap();
+                    final int NPKG = pkgMap.size();
+                    for (int ip=0; ip<NPKG; ip++) {
+                        Slog.w(TAG, "Package: " + pkgMap.keyAt(ip));
+                        SparseArray<ProcessStats.PackageState> uids = pkgMap.valueAt(ip);
+                        final int NUID = uids.size();
+                        for (int iu=0; iu<NUID; iu++) {
+                            Slog.w(TAG, "  Uid: " + uids.keyAt(iu));
+                            ProcessStats.PackageState pkgState = uids.valueAt(iu);
+                            final int NPROCS = pkgState.mProcesses.size();
+                            for (int iproc=0; iproc<NPROCS; iproc++) {
+                                Slog.w(TAG, "    Process " + pkgState.mProcesses.keyAt(iproc)
+                                        + ": " + pkgState.mProcesses.valueAt(iproc));
+                            }
+                            final int NSRVS = pkgState.mServices.size();
+                            for (int isvc=0; isvc<NSRVS; isvc++) {
+                                Slog.w(TAG, "    Service " + pkgState.mServices.keyAt(isvc)
+                                        + ": " + pkgState.mServices.valueAt(isvc));
+                            }
+                        }
+                    }
+                }
+                return false;
+            }
+        } catch (Throwable e) {
+            stats.mReadError = "caught exception: " + e;
+            Slog.e(TAG, "Error reading process statistics", e);
+            return false;
+        }
+        return true;
+    }
+
+    private ArrayList<String> getCommittedFiles(int minNum, boolean inclCurrent,
+            boolean inclCheckedIn) {
+        File[] files = mBaseDir.listFiles();
+        if (files == null || files.length <= minNum) {
+            return null;
+        }
+        ArrayList<String> filesArray = new ArrayList<String>(files.length);
+        String currentFile = mFile.getBaseFile().getPath();
+        if (DEBUG) Slog.d(TAG, "Collecting " + files.length + " files except: " + currentFile);
+        for (int i=0; i<files.length; i++) {
+            File file = files[i];
+            String fileStr = file.getPath();
+            if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr);
+            if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) {
+                if (DEBUG) Slog.d(TAG, "Skipping: already checked in");
+                continue;
+            }
+            if (!inclCurrent && fileStr.equals(currentFile)) {
+                if (DEBUG) Slog.d(TAG, "Skipping: current stats");
+                continue;
+            }
+            filesArray.add(fileStr);
+        }
+        Collections.sort(filesArray);
+        return filesArray;
+    }
+
+    public void trimHistoricStatesWriteLocked() {
+        ArrayList<String> filesArray = getCommittedFiles(MAX_HISTORIC_STATES, false, true);
+        if (filesArray == null) {
+            return;
+        }
+        while (filesArray.size() > MAX_HISTORIC_STATES) {
+            String file = filesArray.remove(0);
+            Slog.i(TAG, "Pruning old procstats: " + file);
+            (new File(file)).delete();
+        }
+    }
+
+    boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header,
+            boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
+            boolean sepProcStates, int[] procStates, long now, String reqPackage) {
+        ArrayList<ProcessStats.ProcessState> procs = mProcessStats.collectProcessesLocked(
+                screenStates, memStates, procStates, procStates, now, reqPackage, false);
+        if (procs.size() > 0) {
+            if (header != null) {
+                pw.println(header);
+            }
+            ProcessStats.dumpProcessListCsv(pw, procs, sepScreenStates, screenStates,
+                    sepMemStates, memStates, sepProcStates, procStates, now);
+            return true;
+        }
+        return false;
+    }
+
+    static int[] parseStateList(String[] states, int mult, String arg, boolean[] outSep,
+            String[] outError) {
+        ArrayList<Integer> res = new ArrayList<Integer>();
+        int lastPos = 0;
+        for (int i=0; i<=arg.length(); i++) {
+            char c = i < arg.length() ? arg.charAt(i) : 0;
+            if (c != ',' && c != '+' && c != ' ' && c != 0) {
+                continue;
+            }
+            boolean isSep = c == ',';
+            if (lastPos == 0) {
+                // We now know the type of op.
+                outSep[0] = isSep;
+            } else if (c != 0 && outSep[0] != isSep) {
+                outError[0] = "inconsistent separators (can't mix ',' with '+')";
+                return null;
+            }
+            if (lastPos < (i-1)) {
+                String str = arg.substring(lastPos, i);
+                for (int j=0; j<states.length; j++) {
+                    if (str.equals(states[j])) {
+                        res.add(j);
+                        str = null;
+                        break;
+                    }
+                }
+                if (str != null) {
+                    outError[0] = "invalid word \"" + str + "\"";
+                    return null;
+                }
+            }
+            lastPos = i + 1;
+        }
+
+        int[] finalRes = new int[res.size()];
+        for (int i=0; i<res.size(); i++) {
+            finalRes[i] = res.get(i) * mult;
+        }
+        return finalRes;
+    }
+
+    public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) {
+        mAm.mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+        Parcel current = Parcel.obtain();
+        mWriteLock.lock();
+        try {
+            synchronized (mAm) {
+                mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                mProcessStats.writeToParcel(current, 0);
+            }
+            if (historic != null) {
+                ArrayList<String> files = getCommittedFiles(0, false, true);
+                if (files != null) {
+                    for (int i=files.size()-1; i>=0; i--) {
+                        try {
+                            ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
+                                    new File(files.get(i)), ParcelFileDescriptor.MODE_READ_ONLY);
+                            historic.add(pfd);
+                        } catch (IOException e) {
+                            Slog.w(TAG, "Failure opening procstat file " + files.get(i), e);
+                        }
+                    }
+                }
+            }
+        } finally {
+            mWriteLock.unlock();
+        }
+        return current.marshall();
+    }
+
+    public ParcelFileDescriptor getStatsOverTime(long minTime) {
+        mAm.mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+        mWriteLock.lock();
+        try {
+            Parcel current = Parcel.obtain();
+            long curTime;
+            synchronized (mAm) {
+                mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                mProcessStats.writeToParcel(current, 0);
+                curTime = mProcessStats.mTimePeriodEndRealtime
+                        - mProcessStats.mTimePeriodStartRealtime;
+            }
+            if (curTime < minTime) {
+                // Need to add in older stats to reach desired time.
+                ArrayList<String> files = getCommittedFiles(0, false, true);
+                if (files != null && files.size() > 0) {
+                    current.setDataPosition(0);
+                    ProcessStats stats = ProcessStats.CREATOR.createFromParcel(current);
+                    current.recycle();
+                    int i = files.size()-1;
+                    while (i >= 0 && (stats.mTimePeriodEndRealtime
+                            - stats.mTimePeriodStartRealtime) < minTime) {
+                        AtomicFile file = new AtomicFile(new File(files.get(i)));
+                        i--;
+                        ProcessStats moreStats = new ProcessStats(false);
+                        readLocked(moreStats, file);
+                        if (moreStats.mReadError == null) {
+                            stats.add(moreStats);
+                            StringBuilder sb = new StringBuilder();
+                            sb.append("Added stats: ");
+                            sb.append(moreStats.mTimePeriodStartClockStr);
+                            sb.append(", over ");
+                            TimeUtils.formatDuration(moreStats.mTimePeriodEndRealtime
+                                    - moreStats.mTimePeriodStartRealtime, sb);
+                            Slog.i(TAG, sb.toString());
+                        } else {
+                            Slog.w(TAG, "Failure reading " + files.get(i+1) + "; "
+                                    + moreStats.mReadError);
+                            continue;
+                        }
+                    }
+                    current = Parcel.obtain();
+                    stats.writeToParcel(current, 0);
+                }
+            }
+            final byte[] outData = current.marshall();
+            current.recycle();
+            final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe();
+            Thread thr = new Thread("ProcessStats pipe output") {
+                public void run() {
+                    FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]);
+                    try {
+                        fout.write(outData);
+                        fout.close();
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Failure writing pipe", e);
+                    }
+                }
+            };
+            thr.start();
+            return fds[0];
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed building output pipe", e);
+        } finally {
+            mWriteLock.unlock();
+        }
+        return null;
+    }
+
+    public int getCurrentMemoryState() {
+        synchronized (mAm) {
+            return mLastMemOnlyState;
+        }
+    }
+
+    private void dumpAggregatedStats(PrintWriter pw, long aggregateHours, long now,
+            String reqPackage, boolean isCompact, boolean dumpDetails, boolean dumpFullDetails,
+            boolean dumpAll, boolean activeOnly) {
+        ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+                - (ProcessStats.COMMIT_PERIOD/2));
+        if (pfd == null) {
+            pw.println("Unable to build stats!");
+            return;
+        }
+        ProcessStats stats = new ProcessStats(false);
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        stats.read(stream);
+        if (stats.mReadError != null) {
+            pw.print("Failure reading: "); pw.println(stats.mReadError);
+            return;
+        }
+        if (isCompact) {
+            stats.dumpCheckinLocked(pw, reqPackage);
+        } else {
+            if (dumpDetails || dumpFullDetails) {
+                stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly);
+            } else {
+                stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+            }
+        }
+    }
+
+    static private void dumpHelp(PrintWriter pw) {
+        pw.println("Process stats (procstats) dump options:");
+        pw.println("    [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]");
+        pw.println("    [--details] [--full-details] [--current] [--hours] [--active]");
+        pw.println("    [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]");
+        pw.println("  --checkin: perform a checkin: print and delete old committed states.");
+        pw.println("  --c: print only state in checkin format.");
+        pw.println("  --csv: output data suitable for putting in a spreadsheet.");
+        pw.println("  --csv-screen: on, off.");
+        pw.println("  --csv-mem: norm, mod, low, crit.");
+        pw.println("  --csv-proc: pers, top, fore, vis, precept, backup,");
+        pw.println("    service, home, prev, cached");
+        pw.println("  --details: dump per-package details, not just summary.");
+        pw.println("  --full-details: dump all timing and active state details.");
+        pw.println("  --current: only dump current state.");
+        pw.println("  --hours: aggregate over about N last hours.");
+        pw.println("  --active: only show currently active processes/services.");
+        pw.println("  --commit: commit current stats to disk and reset to start new stats.");
+        pw.println("  --reset: reset current stats, without committing.");
+        pw.println("  --clear: clear all stats; does both --reset and deletes old stats.");
+        pw.println("  --write: write current in-memory stats to disk.");
+        pw.println("  --read: replace current stats with last-written stats.");
+        pw.println("  -a: print everything.");
+        pw.println("  -h: print this help text.");
+        pw.println("  <package.name>: optional name of package to filter output by.");
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mAm.checkCallingPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump procstats from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            dumpInner(fd, pw, args);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final long now = SystemClock.uptimeMillis();
+
+        boolean isCheckin = false;
+        boolean isCompact = false;
+        boolean isCsv = false;
+        boolean currentOnly = false;
+        boolean dumpDetails = false;
+        boolean dumpFullDetails = false;
+        boolean dumpAll = false;
+        int aggregateHours = 0;
+        boolean activeOnly = false;
+        String reqPackage = null;
+        boolean csvSepScreenStats = false;
+        int[] csvScreenStats = new int[] { ProcessStats.ADJ_SCREEN_OFF, ProcessStats.ADJ_SCREEN_ON};
+        boolean csvSepMemStats = false;
+        int[] csvMemStats = new int[] { ProcessStats.ADJ_MEM_FACTOR_CRITICAL};
+        boolean csvSepProcStats = true;
+        int[] csvProcStats = ProcessStats.ALL_PROC_STATES;
+        if (args != null) {
+            for (int i=0; i<args.length; i++) {
+                String arg = args[i];
+                if ("--checkin".equals(arg)) {
+                    isCheckin = true;
+                } else if ("-c".equals(arg)) {
+                    isCompact = true;
+                } else if ("--csv".equals(arg)) {
+                    isCsv = true;
+                } else if ("--csv-screen".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --csv-screen");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    boolean[] sep = new boolean[1];
+                    String[] error = new String[1];
+                    csvScreenStats = parseStateList(ProcessStats.ADJ_SCREEN_NAMES_CSV, ProcessStats.ADJ_SCREEN_MOD,
+                            args[i], sep, error);
+                    if (csvScreenStats == null) {
+                        pw.println("Error in \"" + args[i] + "\": " + error[0]);
+                        dumpHelp(pw);
+                        return;
+                    }
+                    csvSepScreenStats = sep[0];
+                } else if ("--csv-mem".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --csv-mem");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    boolean[] sep = new boolean[1];
+                    String[] error = new String[1];
+                    csvMemStats = parseStateList(ProcessStats.ADJ_MEM_NAMES_CSV, 1, args[i], sep, error);
+                    if (csvMemStats == null) {
+                        pw.println("Error in \"" + args[i] + "\": " + error[0]);
+                        dumpHelp(pw);
+                        return;
+                    }
+                    csvSepMemStats = sep[0];
+                } else if ("--csv-proc".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --csv-proc");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    boolean[] sep = new boolean[1];
+                    String[] error = new String[1];
+                    csvProcStats = parseStateList(ProcessStats.STATE_NAMES_CSV, 1, args[i], sep, error);
+                    if (csvProcStats == null) {
+                        pw.println("Error in \"" + args[i] + "\": " + error[0]);
+                        dumpHelp(pw);
+                        return;
+                    }
+                    csvSepProcStats = sep[0];
+                } else if ("--details".equals(arg)) {
+                    dumpDetails = true;
+                } else if ("--full-details".equals(arg)) {
+                    dumpFullDetails = true;
+                } else if ("--hours".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --hours");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    try {
+                        aggregateHours = Integer.parseInt(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: --hours argument not an int -- " + args[i]);
+                        dumpHelp(pw);
+                        return;
+                    }
+                } else if ("--active".equals(arg)) {
+                    activeOnly = true;
+                    currentOnly = true;
+                } else if ("--current".equals(arg)) {
+                    currentOnly = true;
+                } else if ("--commit".equals(arg)) {
+                    synchronized (mAm) {
+                        mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
+                        writeStateLocked(true, true);
+                        pw.println("Process stats committed.");
+                    }
+                    return;
+                } else if ("--reset".equals(arg)) {
+                    synchronized (mAm) {
+                        mProcessStats.resetSafely();
+                        pw.println("Process stats reset.");
+                    }
+                    return;
+                } else if ("--clear".equals(arg)) {
+                    synchronized (mAm) {
+                        mProcessStats.resetSafely();
+                        ArrayList<String> files = getCommittedFiles(0, true, true);
+                        if (files != null) {
+                            for (int fi=0; fi<files.size(); fi++) {
+                                (new File(files.get(fi))).delete();
+                            }
+                        }
+                        pw.println("All process stats cleared.");
+                    }
+                    return;
+                } else if ("--write".equals(arg)) {
+                    synchronized (mAm) {
+                        writeStateSyncLocked();
+                        pw.println("Process stats written.");
+                    }
+                    return;
+                } else if ("--read".equals(arg)) {
+                    synchronized (mAm) {
+                        readLocked(mProcessStats, mFile);
+                        pw.println("Process stats read.");
+                    }
+                    return;
+                } else if ("-h".equals(arg)) {
+                    dumpHelp(pw);
+                    return;
+                } else if ("-a".equals(arg)) {
+                    dumpDetails = true;
+                    dumpAll = true;
+                } else if (arg.length() > 0 && arg.charAt(0) == '-'){
+                    pw.println("Unknown option: " + arg);
+                    dumpHelp(pw);
+                    return;
+                } else {
+                    // Not an option, last argument must be a package name.
+                    reqPackage = arg;
+                    // Include all details, since we know we are only going to
+                    // be dumping a smaller set of data.  In fact only the details
+                    // container per-package data, so that are needed to be able
+                    // to dump anything at all when filtering by package.
+                    dumpDetails = true;
+                }
+            }
+        }
+
+        if (isCsv) {
+            pw.print("Processes running summed over");
+            if (!csvSepScreenStats) {
+                for (int i=0; i<csvScreenStats.length; i++) {
+                    pw.print(" ");
+                    ProcessStats.printScreenLabelCsv(pw, csvScreenStats[i]);
+                }
+            }
+            if (!csvSepMemStats) {
+                for (int i=0; i<csvMemStats.length; i++) {
+                    pw.print(" ");
+                    ProcessStats.printMemLabelCsv(pw, csvMemStats[i]);
+                }
+            }
+            if (!csvSepProcStats) {
+                for (int i=0; i<csvProcStats.length; i++) {
+                    pw.print(" ");
+                    pw.print(ProcessStats.STATE_NAMES_CSV[csvProcStats[i]]);
+                }
+            }
+            pw.println();
+            synchronized (mAm) {
+                dumpFilteredProcessesCsvLocked(pw, null,
+                        csvSepScreenStats, csvScreenStats, csvSepMemStats, csvMemStats,
+                        csvSepProcStats, csvProcStats, now, reqPackage);
+                /*
+                dumpFilteredProcessesCsvLocked(pw, "Processes running while critical mem:",
+                        false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON},
+                        true, new int[] {ADJ_MEM_FACTOR_CRITICAL},
+                        true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE,
+                                STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME,
+                                STATE_PREVIOUS, STATE_CACHED},
+                        now, reqPackage);
+                dumpFilteredProcessesCsvLocked(pw, "Processes running over all mem:",
+                        false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON},
+                        false, new int[] {ADJ_MEM_FACTOR_CRITICAL, ADJ_MEM_FACTOR_LOW,
+                                ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_MODERATE},
+                        true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE,
+                                STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME,
+                                STATE_PREVIOUS, STATE_CACHED},
+                        now, reqPackage);
+                */
+            }
+            return;
+        } else if (aggregateHours != 0) {
+            pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:");
+            dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
+                    dumpDetails, dumpFullDetails, dumpAll, activeOnly);
+            return;
+        }
+
+        boolean sepNeeded = false;
+        if (dumpAll || isCheckin) {
+            mWriteLock.lock();
+            try {
+                ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
+                if (files != null) {
+                    for (int i=0; i<files.size(); i++) {
+                        if (DEBUG) Slog.d(TAG, "Retrieving state: " + files.get(i));
+                        try {
+                            AtomicFile file = new AtomicFile(new File(files.get(i)));
+                            ProcessStats processStats = new ProcessStats(false);
+                            readLocked(processStats, file);
+                            if (processStats.mReadError != null) {
+                                if (isCheckin || isCompact) pw.print("err,");
+                                pw.print("Failure reading "); pw.print(files.get(i));
+                                pw.print("; "); pw.println(processStats.mReadError);
+                                if (DEBUG) Slog.d(TAG, "Deleting state: " + files.get(i));
+                                (new File(files.get(i))).delete();
+                                continue;
+                            }
+                            String fileStr = file.getBaseFile().getPath();
+                            boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX);
+                            if (isCheckin || isCompact) {
+                                // Don't really need to lock because we uniquely own this object.
+                                processStats.dumpCheckinLocked(pw, reqPackage);
+                            } else {
+                                if (sepNeeded) {
+                                    pw.println();
+                                } else {
+                                    sepNeeded = true;
+                                }
+                                pw.print("COMMITTED STATS FROM ");
+                                pw.print(processStats.mTimePeriodStartClockStr);
+                                if (checkedIn) pw.print(" (checked in)");
+                                pw.println(":");
+                                // Don't really need to lock because we uniquely own this object.
+                                // Always dump summary here, dumping all details is just too
+                                // much crud.
+                                if (dumpFullDetails) {
+                                    mProcessStats.dumpLocked(pw, reqPackage, now, false, false,
+                                            activeOnly);
+                                } else {
+                                    processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+                                }
+                            }
+                            if (isCheckin) {
+                                // Rename file suffix to mark that it has checked in.
+                                file.getBaseFile().renameTo(new File(
+                                        fileStr + STATE_FILE_CHECKIN_SUFFIX));
+                            }
+                        } catch (Throwable e) {
+                            pw.print("**** FAILURE DUMPING STATE: "); pw.println(files.get(i));
+                            e.printStackTrace(pw);
+                        }
+                    }
+                }
+            } finally {
+                mWriteLock.unlock();
+            }
+        }
+        if (!isCheckin) {
+            if (!currentOnly) {
+                if (sepNeeded) {
+                    pw.println();
+                }
+                pw.println("AGGREGATED OVER LAST 24 HOURS:");
+                dumpAggregatedStats(pw, 24, now, reqPackage, isCompact,
+                        dumpDetails, dumpFullDetails, dumpAll, activeOnly);
+                pw.println();
+                pw.println("AGGREGATED OVER LAST 3 HOURS:");
+                dumpAggregatedStats(pw, 3, now, reqPackage, isCompact,
+                        dumpDetails, dumpFullDetails, dumpAll, activeOnly);
+                sepNeeded = true;
+            }
+            synchronized (mAm) {
+                if (isCompact) {
+                    mProcessStats.dumpCheckinLocked(pw, reqPackage);
+                } else {
+                    if (sepNeeded) {
+                        pw.println();
+                    }
+                    pw.println("CURRENT STATS:");
+                    if (dumpDetails || dumpFullDetails) {
+                        mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
+                                activeOnly);
+                        if (dumpAll) {
+                            pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                        }
+                    } else {
+                        mProcessStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ProviderMap.java b/services/core/java/com/android/server/am/ProviderMap.java
new file mode 100644
index 0000000..7da8c48
--- /dev/null
+++ b/services/core/java/com/android/server/am/ProviderMap.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import com.android.internal.os.TransferPipe;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Keeps track of content providers by authority (name) and class. It separates the mapping by
+ * user and ones that are not user-specific (system providers).
+ */
+public final class ProviderMap {
+
+    private static final String TAG = "ProviderMap";
+
+    private static final boolean DBG = false;
+
+    private final ActivityManagerService mAm;
+
+    private final HashMap<String, ContentProviderRecord> mSingletonByName
+            = new HashMap<String, ContentProviderRecord>();
+    private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass
+            = new HashMap<ComponentName, ContentProviderRecord>();
+
+    private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser
+            = new SparseArray<HashMap<String, ContentProviderRecord>>();
+    private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser
+            = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>();
+
+    ProviderMap(ActivityManagerService am) {
+        mAm = am;
+    }
+
+    ContentProviderRecord getProviderByName(String name) {
+        return getProviderByName(name, -1);
+    }
+
+    ContentProviderRecord getProviderByName(String name, int userId) {
+        if (DBG) {
+            Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid());
+        }
+        // Try to find it in the global list
+        ContentProviderRecord record = mSingletonByName.get(name);
+        if (record != null) {
+            return record;
+        }
+
+        // Check the current user's list
+        return getProvidersByName(userId).get(name);
+    }
+
+    ContentProviderRecord getProviderByClass(ComponentName name) {
+        return getProviderByClass(name, -1);
+    }
+
+    ContentProviderRecord getProviderByClass(ComponentName name, int userId) {
+        if (DBG) {
+            Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid());
+        }
+        // Try to find it in the global list
+        ContentProviderRecord record = mSingletonByClass.get(name);
+        if (record != null) {
+            return record;
+        }
+
+        // Check the current user's list
+        return getProvidersByClass(userId).get(name);
+    }
+
+    void putProviderByName(String name, ContentProviderRecord record) {
+        if (DBG) {
+            Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()
+                + ", record uid = " + record.appInfo.uid);
+        }
+        if (record.singleton) {
+            mSingletonByName.put(name, record);
+        } else {
+            final int userId = UserHandle.getUserId(record.appInfo.uid);
+            getProvidersByName(userId).put(name, record);
+        }
+    }
+
+    void putProviderByClass(ComponentName name, ContentProviderRecord record) {
+        if (DBG) {
+            Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid()
+                + ", record uid = " + record.appInfo.uid);
+        }
+        if (record.singleton) {
+            mSingletonByClass.put(name, record);
+        } else {
+            final int userId = UserHandle.getUserId(record.appInfo.uid);
+            getProvidersByClass(userId).put(name, record);
+        }
+    }
+
+    void removeProviderByName(String name, int userId) {
+        if (mSingletonByName.containsKey(name)) {
+            if (DBG)
+                Slog.i(TAG, "Removing from globalByName name=" + name);
+            mSingletonByName.remove(name);
+        } else {
+            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
+            if (DBG)
+                Slog.i(TAG,
+                        "Removing from providersByName name=" + name + " user=" + userId);
+            HashMap<String, ContentProviderRecord> map = getProvidersByName(userId);
+            // map returned by getProvidersByName wouldn't be null
+            map.remove(name);
+            if (map.size() == 0) {
+                mProvidersByNamePerUser.remove(userId);
+            }
+        }
+    }
+
+    void removeProviderByClass(ComponentName name, int userId) {
+        if (mSingletonByClass.containsKey(name)) {
+            if (DBG)
+                Slog.i(TAG, "Removing from globalByClass name=" + name);
+            mSingletonByClass.remove(name);
+        } else {
+            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
+            if (DBG)
+                Slog.i(TAG,
+                        "Removing from providersByClass name=" + name + " user=" + userId);
+            HashMap<ComponentName, ContentProviderRecord> map = getProvidersByClass(userId);
+            // map returned by getProvidersByClass wouldn't be null
+            map.remove(name);
+            if (map.size() == 0) {
+                mProvidersByClassPerUser.remove(userId);
+            }
+        }
+    }
+
+    private HashMap<String, ContentProviderRecord> getProvidersByName(int userId) {
+        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
+        final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId);
+        if (map == null) {
+            HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>();
+            mProvidersByNamePerUser.put(userId, newMap);
+            return newMap;
+        } else {
+            return map;
+        }
+    }
+
+    HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int userId) {
+        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
+        final HashMap<ComponentName, ContentProviderRecord> map
+                = mProvidersByClassPerUser.get(userId);
+        if (map == null) {
+            HashMap<ComponentName, ContentProviderRecord> newMap
+                    = new HashMap<ComponentName, ContentProviderRecord>();
+            mProvidersByClassPerUser.put(userId, newMap);
+            return newMap;
+        } else {
+            return map;
+        }
+    }
+
+    private boolean collectForceStopProvidersLocked(String name, int appId,
+            boolean doit, boolean evenPersistent, int userId,
+            HashMap<ComponentName, ContentProviderRecord> providers,
+            ArrayList<ContentProviderRecord> result) {
+        boolean didSomething = false;
+        for (ContentProviderRecord provider : providers.values()) {
+            if ((name == null || provider.info.packageName.equals(name))
+                    && (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
+                if (!doit) {
+                    return true;
+                }
+                didSomething = true;
+                result.add(provider);
+            }
+        }
+        return didSomething;
+    }
+
+    boolean collectForceStopProviders(String name, int appId,
+            boolean doit, boolean evenPersistent, int userId,
+            ArrayList<ContentProviderRecord> result) {
+        boolean didSomething = collectForceStopProvidersLocked(name, appId, doit,
+                evenPersistent, userId, mSingletonByClass, result);
+        if (!doit && didSomething) {
+            return true;
+        }
+        if (userId == UserHandle.USER_ALL) {
+            for (int i=0; i<mProvidersByClassPerUser.size(); i++) {
+                if (collectForceStopProvidersLocked(name, appId, doit, evenPersistent,
+                        userId, mProvidersByClassPerUser.valueAt(i), result)) {
+                    if (!doit) {
+                        return true;
+                    }
+                    didSomething = true;
+                }
+            }
+        } else {
+            HashMap<ComponentName, ContentProviderRecord> items
+                    = getProvidersByClass(userId);
+            if (items != null) {
+                didSomething |= collectForceStopProvidersLocked(name, appId, doit,
+                        evenPersistent, userId, items, result);
+            }
+        }
+        return didSomething;
+    }
+
+    private boolean dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, String dumpPackage,
+            String header, boolean needSep, HashMap<ComponentName, ContentProviderRecord> map) {
+        Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator();
+        boolean written = false;
+        while (it.hasNext()) {
+            Map.Entry<ComponentName, ContentProviderRecord> e = it.next();
+            ContentProviderRecord r = e.getValue();
+            if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                continue;
+            }
+            if (needSep) {
+                pw.println("");
+                needSep = false;
+            }
+            if (header != null) {
+                pw.println(header);
+                header = null;
+            }
+            written = true;
+            pw.print("  * ");
+            pw.println(r);
+            r.dump(pw, "    ", dumpAll);
+        }
+        return written;
+    }
+
+    private boolean dumpProvidersByNameLocked(PrintWriter pw, String dumpPackage,
+            String header, boolean needSep, HashMap<String, ContentProviderRecord> map) {
+        Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator();
+        boolean written = false;
+        while (it.hasNext()) {
+            Map.Entry<String, ContentProviderRecord> e = it.next();
+            ContentProviderRecord r = e.getValue();
+            if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
+                continue;
+            }
+            if (needSep) {
+                pw.println("");
+                needSep = false;
+            }
+            if (header != null) {
+                pw.println(header);
+                header = null;
+            }
+            written = true;
+            pw.print("  ");
+            pw.print(e.getKey());
+            pw.print(": ");
+            pw.println(r.toShortString());
+        }
+        return written;
+    }
+
+    boolean dumpProvidersLocked(PrintWriter pw, boolean dumpAll, String dumpPackage) {
+        boolean needSep = false;
+
+        if (mSingletonByClass.size() > 0) {
+            needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage,
+                    "  Published single-user content providers (by class):", needSep,
+                    mSingletonByClass);
+        }
+
+        for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
+            HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
+            needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage,
+                    "  Published user " + mProvidersByClassPerUser.keyAt(i)
+                            + " content providers (by class):", needSep, map);
+        }
+
+        if (dumpAll) {
+            needSep |= dumpProvidersByNameLocked(pw, dumpPackage,
+                    "  Single-user authority to provider mappings:", needSep, mSingletonByName);
+
+            for (int i = 0; i < mProvidersByNamePerUser.size(); i++) {
+                needSep |= dumpProvidersByNameLocked(pw, dumpPackage,
+                        "  User " + mProvidersByNamePerUser.keyAt(i)
+                                + " authority to provider mappings:", needSep,
+                        mProvidersByNamePerUser.valueAt(i));
+            }
+        }
+        return needSep;
+    }
+
+    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
+        ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
+
+        synchronized (mAm) {
+            allProviders.addAll(mSingletonByClass.values());
+            for (int i=0; i<mProvidersByClassPerUser.size(); i++) {
+                allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values());
+            }
+
+            if ("all".equals(name)) {
+                providers.addAll(allProviders);
+            } else {
+                ComponentName componentName = name != null
+                        ? ComponentName.unflattenFromString(name) : null;
+                int objectId = 0;
+                if (componentName == null) {
+                    // Not a '/' separated full component name; maybe an object ID?
+                    try {
+                        objectId = Integer.parseInt(name, 16);
+                        name = null;
+                        componentName = null;
+                    } catch (RuntimeException e) {
+                    }
+                }
+
+                for (int i=0; i<allProviders.size(); i++) {
+                    ContentProviderRecord r1 = allProviders.get(i);
+                    if (componentName != null) {
+                        if (r1.name.equals(componentName)) {
+                            providers.add(r1);
+                        }
+                    } else if (name != null) {
+                        if (r1.name.flattenToString().contains(name)) {
+                            providers.add(r1);
+                        }
+                    } else if (System.identityHashCode(r1) == objectId) {
+                        providers.add(r1);
+                    }
+                }
+            }
+        }
+
+        if (providers.size() <= 0) {
+            return false;
+        }
+
+        boolean needSep = false;
+        for (int i=0; i<providers.size(); i++) {
+            if (needSep) {
+                pw.println();
+            }
+            needSep = true;
+            dumpProvider("", fd, pw, providers.get(i), args, dumpAll);
+        }
+        return true;
+    }
+
+    /**
+     * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider if
+     * there is a thread associated with the provider.
+     */
+    private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw,
+            final ContentProviderRecord r, String[] args, boolean dumpAll) {
+        String innerPrefix = prefix + "  ";
+        synchronized (mAm) {
+            pw.print(prefix); pw.print("PROVIDER ");
+                    pw.print(r);
+                    pw.print(" pid=");
+                    if (r.proc != null) pw.println(r.proc.pid);
+                    else pw.println("(not running)");
+            if (dumpAll) {
+                r.dump(pw, innerPrefix, true);
+            }
+        }
+        if (r.proc != null && r.proc.thread != null) {
+            pw.println("    Client:");
+            pw.flush();
+            try {
+                TransferPipe tp = new TransferPipe();
+                try {
+                    r.proc.thread.dumpProvider(
+                            tp.getWriteFd().getFileDescriptor(), r.provider.asBinder(), args);
+                    tp.setBufferPrefix("      ");
+                    // Short timeout, since blocking here can
+                    // deadlock with the application.
+                    tp.go(fd, 2000);
+                } finally {
+                    tp.kill();
+                }
+            } catch (IOException ex) {
+                pw.println("      Failure while dumping the provider: " + ex);
+            } catch (RemoteException ex) {
+                pw.println("      Got a RemoteException while dumping the service");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/ReceiverList.java b/services/core/java/com/android/server/am/ReceiverList.java
new file mode 100644
index 0000000..fa8c1df
--- /dev/null
+++ b/services/core/java/com/android/server/am/ReceiverList.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.PrintWriterPrinter;
+import android.util.Printer;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * A receiver object that has registered for one or more broadcasts.
+ * The ArrayList holds BroadcastFilter objects.
+ */
+final class ReceiverList extends ArrayList<BroadcastFilter>
+        implements IBinder.DeathRecipient {
+    final ActivityManagerService owner;
+    public final IIntentReceiver receiver;
+    public final ProcessRecord app;
+    public final int pid;
+    public final int uid;
+    public final int userId;
+    BroadcastRecord curBroadcast = null;
+    boolean linkedToDeath = false;
+
+    String stringName;
+    
+    ReceiverList(ActivityManagerService _owner, ProcessRecord _app,
+            int _pid, int _uid, int _userId, IIntentReceiver _receiver) {
+        owner = _owner;
+        receiver = _receiver;
+        app = _app;
+        pid = _pid;
+        uid = _uid;
+        userId = _userId;
+    }
+
+    // Want object identity, not the array identity we are inheriting.
+    public boolean equals(Object o) {
+        return this == o;
+    }
+    public int hashCode() {
+        return System.identityHashCode(this);
+    }
+    
+    public void binderDied() {
+        linkedToDeath = false;
+        owner.unregisterReceiver(receiver);
+    }
+    
+    void dumpLocal(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null);
+            pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid);
+            pw.print(" user="); pw.println(userId);
+        if (curBroadcast != null || linkedToDeath) {
+            pw.print(prefix); pw.print("curBroadcast="); pw.print(curBroadcast);
+                pw.print(" linkedToDeath="); pw.println(linkedToDeath);
+        }
+    }
+    
+    void dump(PrintWriter pw, String prefix) {
+        Printer pr = new PrintWriterPrinter(pw);
+        dumpLocal(pw, prefix);
+        String p2 = prefix + "  ";
+        final int N = size();
+        for (int i=0; i<N; i++) {
+            BroadcastFilter bf = get(i);
+            pw.print(prefix); pw.print("Filter #"); pw.print(i);
+                    pw.print(": BroadcastFilter{");
+                    pw.print(Integer.toHexString(System.identityHashCode(bf)));
+                    pw.println('}');
+            bf.dumpInReceiverList(pw, pr, p2);
+        }
+    }
+    
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ReceiverList{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        sb.append(pid);
+        sb.append(' ');
+        sb.append((app != null ? app.processName : "(unknown name)"));
+        sb.append('/');
+        sb.append(uid);
+        sb.append("/u");
+        sb.append(userId);
+        sb.append((receiver.asBinder() instanceof Binder) ? " local:" : " remote:");
+        sb.append(Integer.toHexString(System.identityHashCode(receiver.asBinder())));
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
new file mode 100644
index 0000000..cb04835
--- /dev/null
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -0,0 +1,544 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import com.android.internal.app.ProcessStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A running application service.
+ */
+final class ServiceRecord extends Binder {
+    // Maximum number of delivery attempts before giving up.
+    static final int MAX_DELIVERY_COUNT = 3;
+
+    // Maximum number of times it can fail during execution before giving up.
+    static final int MAX_DONE_EXECUTING_COUNT = 6;
+
+    final ActivityManagerService ams;
+    final BatteryStatsImpl.Uid.Pkg.Serv stats;
+    final ComponentName name; // service component.
+    final String shortName; // name.flattenToShortString().
+    final Intent.FilterComparison intent;
+                            // original intent used to find service.
+    final ServiceInfo serviceInfo;
+                            // all information about the service.
+    final ApplicationInfo appInfo;
+                            // information about service's app.
+    final int userId;       // user that this service is running as
+    final String packageName; // the package implementing intent's component
+    final String processName; // process where this component wants to run
+    final String permission;// permission needed to access service
+    final String baseDir;   // where activity source (resources etc) located
+    final String resDir;   // where public activity source (public resources etc) located
+    final String dataDir;   // where activity data should go
+    final boolean exported; // from ServiceInfo.exported
+    final Runnable restarter; // used to schedule retries of starting the service
+    final long createTime;  // when this service was created
+    final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
+            = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
+                            // All active bindings to the service.
+    final ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections
+            = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>();
+                            // IBinder -> ConnectionRecord of all bound clients
+
+    ProcessRecord app;      // where this service is running or null.
+    ProcessRecord isolatedProc; // keep track of isolated process, if requested
+    ProcessStats.ServiceState tracker; // tracking service execution, may be null
+    ProcessStats.ServiceState restartTracker; // tracking service restart
+    boolean delayed;        // are we waiting to start this service in the background?
+    boolean isForeground;   // is service currently in foreground mode?
+    int foregroundId;       // Notification ID of last foreground req.
+    Notification foregroundNoti; // Notification record of foreground state.
+    long lastActivity;      // last time there was some activity on the service.
+    long startingBgTimeout;  // time at which we scheduled this for a delayed start.
+    boolean startRequested; // someone explicitly called start?
+    boolean delayedStop;    // service has been stopped but is in a delayed start?
+    boolean stopIfKilled;   // last onStart() said to stop if service killed?
+    boolean callStart;      // last onStart() has asked to alway be called on restart.
+    int executeNesting;     // number of outstanding operations keeping foreground.
+    boolean executeFg;      // should we be executing in the foreground?
+    long executingStart;    // start time of last execute request.
+    boolean createdFromFg;  // was this service last created due to a foreground process call?
+    int crashCount;         // number of times proc has crashed with service running
+    int totalRestartCount;  // number of times we have had to restart.
+    int restartCount;       // number of restarts performed in a row.
+    long restartDelay;      // delay until next restart attempt.
+    long restartTime;       // time of last restart.
+    long nextRestartTime;   // time when restartDelay will expire.
+
+    String stringName;      // caching of toString
+    
+    private int lastStartId;    // identifier of most recent start request.
+
+    static class StartItem {
+        final ServiceRecord sr;
+        final boolean taskRemoved;
+        final int id;
+        final Intent intent;
+        final ActivityManagerService.NeededUriGrants neededGrants;
+        long deliveredTime;
+        int deliveryCount;
+        int doneExecutingCount;
+        UriPermissionOwner uriPermissions;
+
+        String stringName;      // caching of toString
+
+        StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
+                ActivityManagerService.NeededUriGrants _neededGrants) {
+            sr = _sr;
+            taskRemoved = _taskRemoved;
+            id = _id;
+            intent = _intent;
+            neededGrants = _neededGrants;
+        }
+
+        UriPermissionOwner getUriPermissionsLocked() {
+            if (uriPermissions == null) {
+                uriPermissions = new UriPermissionOwner(sr.ams, this);
+            }
+            return uriPermissions;
+        }
+
+        void removeUriPermissionsLocked() {
+            if (uriPermissions != null) {
+                uriPermissions.removeUriPermissionsLocked();
+                uriPermissions = null;
+            }
+        }
+
+        public String toString() {
+            if (stringName != null) {
+                return stringName;
+            }
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("ServiceRecord{")
+                .append(Integer.toHexString(System.identityHashCode(sr)))
+                .append(' ').append(sr.shortName)
+                .append(" StartItem ")
+                .append(Integer.toHexString(System.identityHashCode(this)))
+                .append(" id=").append(id).append('}');
+            return stringName = sb.toString();
+        }
+    }
+
+    final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>();
+                            // start() arguments which been delivered.
+    final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>();
+                            // start() arguments that haven't yet been delivered.
+
+    void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) {
+        final int N = list.size();
+        for (int i=0; i<N; i++) {
+            StartItem si = list.get(i);
+            pw.print(prefix); pw.print("#"); pw.print(i);
+                    pw.print(" id="); pw.print(si.id);
+                    if (now != 0) {
+                        pw.print(" dur=");
+                        TimeUtils.formatDuration(si.deliveredTime, now, pw);
+                    }
+                    if (si.deliveryCount != 0) {
+                        pw.print(" dc="); pw.print(si.deliveryCount);
+                    }
+                    if (si.doneExecutingCount != 0) {
+                        pw.print(" dxc="); pw.print(si.doneExecutingCount);
+                    }
+                    pw.println("");
+            pw.print(prefix); pw.print("  intent=");
+                    if (si.intent != null) pw.println(si.intent.toString());
+                    else pw.println("null");
+            if (si.neededGrants != null) {
+                pw.print(prefix); pw.print("  neededGrants=");
+                        pw.println(si.neededGrants);
+            }
+            if (si.uriPermissions != null) {
+                if (si.uriPermissions.readUriPermissions != null) {
+                    pw.print(prefix); pw.print("  readUriPermissions=");
+                            pw.println(si.uriPermissions.readUriPermissions);
+                }
+                if (si.uriPermissions.writeUriPermissions != null) {
+                    pw.print(prefix); pw.print("  writeUriPermissions=");
+                            pw.println(si.uriPermissions.writeUriPermissions);
+                }
+            }
+        }
+    }
+    
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("intent={");
+                pw.print(intent.getIntent().toShortString(false, true, false, true));
+                pw.println('}');
+        pw.print(prefix); pw.print("packageName="); pw.println(packageName);
+        pw.print(prefix); pw.print("processName="); pw.println(processName);
+        if (permission != null) {
+            pw.print(prefix); pw.print("permission="); pw.println(permission);
+        }
+        long now = SystemClock.uptimeMillis();
+        long nowReal = SystemClock.elapsedRealtime();
+        pw.print(prefix); pw.print("baseDir="); pw.println(baseDir);
+        if (!resDir.equals(baseDir)) {
+            pw.print(prefix); pw.print("resDir="); pw.println(resDir);
+        }
+        pw.print(prefix); pw.print("dataDir="); pw.println(dataDir);
+        pw.print(prefix); pw.print("app="); pw.println(app);
+        if (isolatedProc != null) {
+            pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc);
+        }
+        if (delayed) {
+            pw.print(prefix); pw.print("delayed="); pw.println(delayed);
+        }
+        if (isForeground || foregroundId != 0) {
+            pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
+                    pw.print(" foregroundId="); pw.print(foregroundId);
+                    pw.print(" foregroundNoti="); pw.println(foregroundNoti);
+        }
+        pw.print(prefix); pw.print("createTime=");
+                TimeUtils.formatDuration(createTime, nowReal, pw);
+                pw.print(" startingBgTimeout=");
+                TimeUtils.formatDuration(startingBgTimeout, now, pw);
+                pw.println();
+        pw.print(prefix); pw.print("lastActivity=");
+                TimeUtils.formatDuration(lastActivity, now, pw);
+                pw.print(" restartTime=");
+                TimeUtils.formatDuration(restartTime, now, pw);
+                pw.print(" createdFromFg="); pw.println(createdFromFg);
+        if (startRequested || delayedStop || lastStartId != 0) {
+            pw.print(prefix); pw.print("startRequested="); pw.print(startRequested);
+                    pw.print(" delayedStop="); pw.print(delayedStop);
+                    pw.print(" stopIfKilled="); pw.print(stopIfKilled);
+                    pw.print(" callStart="); pw.print(callStart);
+                    pw.print(" lastStartId="); pw.println(lastStartId);
+        }
+        if (executeNesting != 0) {
+            pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting);
+                    pw.print(" executeFg="); pw.print(executeFg);
+                    pw.print(" executingStart=");
+                    TimeUtils.formatDuration(executingStart, now, pw);
+                    pw.println();
+        }
+        if (crashCount != 0 || restartCount != 0
+                || restartDelay != 0 || nextRestartTime != 0) {
+            pw.print(prefix); pw.print("restartCount="); pw.print(restartCount);
+                    pw.print(" restartDelay=");
+                    TimeUtils.formatDuration(restartDelay, now, pw);
+                    pw.print(" nextRestartTime=");
+                    TimeUtils.formatDuration(nextRestartTime, now, pw);
+                    pw.print(" crashCount="); pw.println(crashCount);
+        }
+        if (deliveredStarts.size() > 0) {
+            pw.print(prefix); pw.println("Delivered Starts:");
+            dumpStartList(pw, prefix, deliveredStarts, now);
+        }
+        if (pendingStarts.size() > 0) {
+            pw.print(prefix); pw.println("Pending Starts:");
+            dumpStartList(pw, prefix, pendingStarts, 0);
+        }
+        if (bindings.size() > 0) {
+            pw.print(prefix); pw.println("Bindings:");
+            for (int i=0; i<bindings.size(); i++) {
+                IntentBindRecord b = bindings.valueAt(i);
+                pw.print(prefix); pw.print("* IntentBindRecord{");
+                        pw.print(Integer.toHexString(System.identityHashCode(b)));
+                        if ((b.collectFlags()&Context.BIND_AUTO_CREATE) != 0) {
+                            pw.append(" CREATE");
+                        }
+                        pw.println("}:");
+                b.dumpInService(pw, prefix + "  ");
+            }
+        }
+        if (connections.size() > 0) {
+            pw.print(prefix); pw.println("All Connections:");
+            for (int conni=0; conni<connections.size(); conni++) {
+                ArrayList<ConnectionRecord> c = connections.valueAt(conni);
+                for (int i=0; i<c.size(); i++) {
+                    pw.print(prefix); pw.print("  "); pw.println(c.get(i));
+                }
+            }
+        }
+    }
+
+    ServiceRecord(ActivityManagerService ams,
+            BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name,
+            Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
+            Runnable restarter) {
+        this.ams = ams;
+        this.stats = servStats;
+        this.name = name;
+        shortName = name.flattenToShortString();
+        this.intent = intent;
+        serviceInfo = sInfo;
+        appInfo = sInfo.applicationInfo;
+        packageName = sInfo.applicationInfo.packageName;
+        processName = sInfo.processName;
+        permission = sInfo.permission;
+        baseDir = sInfo.applicationInfo.sourceDir;
+        resDir = sInfo.applicationInfo.publicSourceDir;
+        dataDir = sInfo.applicationInfo.dataDir;
+        exported = sInfo.exported;
+        this.restarter = restarter;
+        createTime = SystemClock.elapsedRealtime();
+        lastActivity = SystemClock.uptimeMillis();
+        userId = UserHandle.getUserId(appInfo.uid);
+        createdFromFg = callerIsFg;
+    }
+
+    public ProcessStats.ServiceState getTracker() {
+        if (tracker != null) {
+            return tracker;
+        }
+        if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
+            tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+                    serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+            tracker.applyNewOwner(this);
+        }
+        return tracker;
+    }
+
+    public void forceClearTracker() {
+        if (tracker != null) {
+            tracker.clearCurrentOwner(this, true);
+            tracker = null;
+        }
+    }
+
+    public void makeRestarting(int memFactor, long now) {
+        if (restartTracker == null) {
+            if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+                        serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+            }
+            if (restartTracker == null) {
+                return;
+            }
+        }
+        restartTracker.setRestarting(true, memFactor, now);
+    }
+
+    public AppBindRecord retrieveAppBindingLocked(Intent intent,
+            ProcessRecord app) {
+        Intent.FilterComparison filter = new Intent.FilterComparison(intent);
+        IntentBindRecord i = bindings.get(filter);
+        if (i == null) {
+            i = new IntentBindRecord(this, filter);
+            bindings.put(filter, i);
+        }
+        AppBindRecord a = i.apps.get(app);
+        if (a != null) {
+            return a;
+        }
+        a = new AppBindRecord(this, i, app);
+        i.apps.put(app, a);
+        return a;
+    }
+
+    public boolean hasAutoCreateConnections() {
+        // XXX should probably keep a count of the number of auto-create
+        // connections directly in the service.
+        for (int conni=connections.size()-1; conni>=0; conni--) {
+            ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
+            for (int i=0; i<cr.size(); i++) {
+                if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    public void resetRestartCounter() {
+        restartCount = 0;
+        restartDelay = 0;
+        restartTime = 0;
+    }
+    
+    public StartItem findDeliveredStart(int id, boolean remove) {
+        final int N = deliveredStarts.size();
+        for (int i=0; i<N; i++) {
+            StartItem si = deliveredStarts.get(i);
+            if (si.id == id) {
+                if (remove) deliveredStarts.remove(i);
+                return si;
+            }
+        }
+        
+        return null;
+    }
+    
+    public int getLastStartId() {
+        return lastStartId;
+    }
+
+    public int makeNextStartId() {
+        lastStartId++;
+        if (lastStartId < 1) {
+            lastStartId = 1;
+        }
+        return lastStartId;
+    }
+
+    public void postNotification() {
+        final int appUid = appInfo.uid;
+        final int appPid = app.pid;
+        if (foregroundId != 0 && foregroundNoti != null) {
+            // Do asynchronous communication with notification manager to
+            // avoid deadlocks.
+            final String localPackageName = packageName;
+            final int localForegroundId = foregroundId;
+            final Notification localForegroundNoti = foregroundNoti;
+            ams.mHandler.post(new Runnable() {
+                public void run() {
+                    NotificationManagerInternal nm = LocalServices.getService(
+                            NotificationManagerInternal.class);
+                    if (nm == null) {
+                        return;
+                    }
+                    try {
+                        if (localForegroundNoti.icon == 0) {
+                            // It is not correct for the caller to supply a notification
+                            // icon, but this used to be able to slip through, so for
+                            // those dirty apps give it the app's icon.
+                            localForegroundNoti.icon = appInfo.icon;
+
+                            // Do not allow apps to present a sneaky invisible content view either.
+                            localForegroundNoti.contentView = null;
+                            localForegroundNoti.bigContentView = null;
+                            CharSequence appName = appInfo.loadLabel(
+                                    ams.mContext.getPackageManager());
+                            if (appName == null) {
+                                appName = appInfo.packageName;
+                            }
+                            Context ctx = null;
+                            try {
+                                ctx = ams.mContext.createPackageContext(
+                                        appInfo.packageName, 0);
+                                Intent runningIntent = new Intent(
+                                        Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+                                runningIntent.setData(Uri.fromParts("package",
+                                        appInfo.packageName, null));
+                                PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
+                                        runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+                                localForegroundNoti.setLatestEventInfo(ctx,
+                                        ams.mContext.getString(
+                                                com.android.internal.R.string
+                                                        .app_running_notification_title,
+                                                appName),
+                                        ams.mContext.getString(
+                                                com.android.internal.R.string
+                                                        .app_running_notification_text,
+                                                appName),
+                                        pi);
+                            } catch (PackageManager.NameNotFoundException e) {
+                                localForegroundNoti.icon = 0;
+                            }
+                        }
+                        if (localForegroundNoti.icon == 0) {
+                            // Notifications whose icon is 0 are defined to not show
+                            // a notification, silently ignoring it.  We don't want to
+                            // just ignore it, we want to prevent the service from
+                            // being foreground.
+                            throw new RuntimeException("icon must be non-zero");
+                        }
+                        int[] outId = new int[1];
+                        nm.enqueueNotification(localPackageName, localPackageName,
+                                appUid, appPid, null, localForegroundId, localForegroundNoti,
+                                outId, userId);
+                    } catch (RuntimeException e) {
+                        Slog.w(ActivityManagerService.TAG,
+                                "Error showing notification for service", e);
+                        // If it gave us a garbage notification, it doesn't
+                        // get to be foreground.
+                        ams.setServiceForeground(name, ServiceRecord.this,
+                                0, null, true);
+                        ams.crashApplication(appUid, appPid, localPackageName,
+                                "Bad notification for startForeground: " + e);
+                    }
+                }
+            });
+        }
+    }
+    
+    public void cancelNotification() {
+        if (foregroundId != 0) {
+            // Do asynchronous communication with notification manager to
+            // avoid deadlocks.
+            final String localPackageName = packageName;
+            final int localForegroundId = foregroundId;
+            ams.mHandler.post(new Runnable() {
+                public void run() {
+                    INotificationManager inm = NotificationManager.getService();
+                    if (inm == null) {
+                        return;
+                    }
+                    try {
+                        inm.cancelNotificationWithTag(localPackageName, null,
+                                localForegroundId, userId);
+                    } catch (RuntimeException e) {
+                        Slog.w(ActivityManagerService.TAG,
+                                "Error canceling notification for service", e);
+                    } catch (RemoteException e) {
+                    }
+                }
+            });
+        }
+    }
+    
+    public void clearDeliveredStartsLocked() {
+        for (int i=deliveredStarts.size()-1; i>=0; i--) {
+            deliveredStarts.get(i).removeUriPermissionsLocked();
+        }
+        deliveredStarts.clear();
+    }
+
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ServiceRecord{")
+            .append(Integer.toHexString(System.identityHashCode(this)))
+            .append(" u").append(userId)
+            .append(' ').append(shortName).append('}');
+        return stringName = sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/StrictModeViolationDialog.java b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
new file mode 100644
index 0000000..fda1ec1
--- /dev/null
+++ b/services/core/java/com/android/server/am/StrictModeViolationDialog.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Message;
+
+final class StrictModeViolationDialog extends BaseErrorDialog {
+    private final static String TAG = "StrictModeViolationDialog";
+
+    private final ActivityManagerService mService;
+    private final AppErrorResult mResult;
+    private final ProcessRecord mProc;
+
+    // Event 'what' codes
+    static final int ACTION_OK = 0;
+    static final int ACTION_OK_AND_REPORT = 1;
+
+    // 1-minute timeout, then we automatically dismiss the violation
+    // dialog
+    static final long DISMISS_TIMEOUT = 1000 * 60 * 1;
+
+    public StrictModeViolationDialog(Context context, ActivityManagerService service,
+            AppErrorResult result, ProcessRecord app) {
+        super(context);
+
+        Resources res = context.getResources();
+
+        mService = service;
+        mProc = app;
+        mResult = result;
+        CharSequence name;
+        if ((app.pkgList.size() == 1) &&
+                (name=context.getPackageManager().getApplicationLabel(app.info)) != null) {
+            setMessage(res.getString(
+                    com.android.internal.R.string.smv_application,
+                    name.toString(), app.info.processName));
+        } else {
+            name = app.processName;
+            setMessage(res.getString(
+                    com.android.internal.R.string.smv_process,
+                    name.toString()));
+        }
+
+        setCancelable(false);
+
+        setButton(DialogInterface.BUTTON_POSITIVE,
+                  res.getText(com.android.internal.R.string.dlg_ok),
+                  mHandler.obtainMessage(ACTION_OK));
+
+        if (app.errorReportReceiver != null) {
+            setButton(DialogInterface.BUTTON_NEGATIVE,
+                      res.getText(com.android.internal.R.string.report),
+                      mHandler.obtainMessage(ACTION_OK_AND_REPORT));
+        }
+
+        setTitle(res.getText(com.android.internal.R.string.aerr_title));
+        getWindow().addPrivateFlags(PRIVATE_FLAG_SYSTEM_ERROR);
+        getWindow().setTitle("Strict Mode Violation: " + app.info.processName);
+
+        // After the timeout, pretend the user clicked the quit button
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(ACTION_OK),
+                DISMISS_TIMEOUT);
+    }
+
+    private final Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            synchronized (mService) {
+                if (mProc != null && mProc.crashDialog == StrictModeViolationDialog.this) {
+                    mProc.crashDialog = null;
+                }
+            }
+            mResult.set(msg.what);
+
+            // If this is a timeout we won't be automatically closed, so go
+            // ahead and explicitly dismiss ourselves just in case.
+            dismiss();
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/am/TaskAccessInfo.java b/services/core/java/com/android/server/am/TaskAccessInfo.java
new file mode 100644
index 0000000..50aeec1
--- /dev/null
+++ b/services/core/java/com/android/server/am/TaskAccessInfo.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.util.ArrayList;
+
+import android.app.ActivityManager.TaskThumbnails;
+
+final class TaskAccessInfo extends TaskThumbnails {
+    final static class SubTask {
+        ThumbnailHolder holder;
+        ActivityRecord activity;
+        int index;
+    }
+    
+    public ActivityRecord root;
+    public int rootIndex;
+    
+    public ArrayList<SubTask> subtasks;
+}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
new file mode 100644
index 0000000..9105103
--- /dev/null
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import static com.android.server.am.ActivityManagerService.TAG;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.IThumbnailRetriever;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.graphics.Bitmap;
+import android.os.UserHandle;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+final class TaskRecord extends ThumbnailHolder {
+    final int taskId;       // Unique identifier for this task.
+    final String affinity;  // The affinity name for this task, or null.
+    Intent intent;          // The original intent that started the task.
+    Intent affinityIntent;  // Intent of affinity-moved activity that started this task.
+    ComponentName origActivity; // The non-alias activity component of the intent.
+    ComponentName realActivity; // The actual activity component that started the task.
+    int numActivities;      // Current number of activities in this task.
+    long lastActiveTime;    // Last time this task was active, including sleep.
+    boolean rootWasReset;   // True if the intent at the root of the task had
+                            // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
+    boolean askedCompatMode;// Have asked the user about compat mode for this task.
+
+    String stringName;      // caching of toString() result.
+    int userId;             // user for which this task was created
+
+    int numFullscreen;      // Number of fullscreen activities.
+
+    /** List of all activities in the task arranged in history order */
+    final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>();
+
+    /** Current stack */
+    ActivityStack stack;
+
+    /** Takes on same set of values as ActivityRecord.mActivityType */
+    private int mTaskType;
+
+    /** Launch the home activity when leaving this task. */
+    boolean mOnTopOfHome = false;
+
+    TaskRecord(int _taskId, ActivityInfo info, Intent _intent) {
+        taskId = _taskId;
+        affinity = info.taskAffinity;
+        setIntent(_intent, info);
+    }
+
+    void touchActiveTime() {
+        lastActiveTime = android.os.SystemClock.elapsedRealtime();
+    }
+
+    long getInactiveDuration() {
+        return android.os.SystemClock.elapsedRealtime() - lastActiveTime;
+    }
+
+    void setIntent(Intent _intent, ActivityInfo info) {
+        stringName = null;
+
+        if (info.targetActivity == null) {
+            if (_intent != null) {
+                // If this Intent has a selector, we want to clear it for the
+                // recent task since it is not relevant if the user later wants
+                // to re-launch the app.
+                if (_intent.getSelector() != null || _intent.getSourceBounds() != null) {
+                    _intent = new Intent(_intent);
+                    _intent.setSelector(null);
+                    _intent.setSourceBounds(null);
+                }
+            }
+            if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
+                    "Setting Intent of " + this + " to " + _intent);
+            intent = _intent;
+            realActivity = _intent != null ? _intent.getComponent() : null;
+            origActivity = null;
+        } else {
+            ComponentName targetComponent = new ComponentName(
+                    info.packageName, info.targetActivity);
+            if (_intent != null) {
+                Intent targetIntent = new Intent(_intent);
+                targetIntent.setComponent(targetComponent);
+                targetIntent.setSelector(null);
+                targetIntent.setSourceBounds(null);
+                if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
+                        "Setting Intent of " + this + " to target " + targetIntent);
+                intent = targetIntent;
+                realActivity = targetComponent;
+                origActivity = _intent.getComponent();
+            } else {
+                intent = null;
+                realActivity = targetComponent;
+                origActivity = new ComponentName(info.packageName, info.name);
+            }
+        }
+
+        if (intent != null &&
+                (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+            // Once we are set to an Intent with this flag, we count this
+            // task as having a true root activity.
+            rootWasReset = true;
+        }
+
+        if (info.applicationInfo != null) {
+            userId = UserHandle.getUserId(info.applicationInfo.uid);
+        }
+    }
+
+    void disposeThumbnail() {
+        super.disposeThumbnail();
+        for (int i=mActivities.size()-1; i>=0; i--) {
+            ThumbnailHolder thumb = mActivities.get(i).thumbHolder;
+            if (thumb != this) {
+                thumb.disposeThumbnail();
+            }
+        }
+    }
+
+    ActivityRecord getTopActivity() {
+        for (int i = mActivities.size() - 1; i >= 0; --i) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.finishing) {
+                continue;
+            }
+            return r;
+        }
+        return null;
+    }
+
+    ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+            ActivityRecord r = mActivities.get(activityNdx);
+            if (!r.finishing && r != notTop && stack.okToShow(r)) {
+                return r;
+            }
+        }
+        return null;
+    }
+
+    /** Call after activity movement or finish to make sure that frontOfTask is set correctly */
+    final void setFrontOfTask() {
+        boolean foundFront = false;
+        final int numActivities = mActivities.size();
+        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (foundFront || r.finishing) {
+                r.frontOfTask = false;
+            } else {
+                r.frontOfTask = true;
+                // Set frontOfTask false for every following activity.
+                foundFront = true;
+            }
+        }
+    }
+
+    /**
+     * Reorder the history stack so that the passed activity is brought to the front.
+     */
+    final void moveActivityToFrontLocked(ActivityRecord newTop) {
+        if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop
+            + " to stack at top", new RuntimeException("here").fillInStackTrace());
+
+        mActivities.remove(newTop);
+        mActivities.add(newTop);
+
+        setFrontOfTask();
+    }
+
+    void addActivityAtBottom(ActivityRecord r) {
+        addActivityAtIndex(0, r);
+    }
+
+    void addActivityToTop(ActivityRecord r) {
+        addActivityAtIndex(mActivities.size(), r);
+    }
+
+    void addActivityAtIndex(int index, ActivityRecord r) {
+        // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
+        if (!mActivities.remove(r) && r.fullscreen) {
+            // Was not previously in list.
+            numFullscreen++;
+        }
+        // Only set this based on the first activity
+        if (mActivities.isEmpty()) {
+            mTaskType = r.mActivityType;
+        } else {
+            // Otherwise make all added activities match this one.
+            r.mActivityType = mTaskType;
+        }
+        mActivities.add(index, r);
+    }
+
+    /** @return true if this was the last activity in the task */
+    boolean removeActivity(ActivityRecord r) {
+        if (mActivities.remove(r) && r.fullscreen) {
+            // Was previously in list.
+            numFullscreen--;
+        }
+        return mActivities.size() == 0;
+    }
+
+    /**
+     * Completely remove all activities associated with an existing
+     * task starting at a specified index.
+     */
+    final void performClearTaskAtIndexLocked(int activityNdx) {
+        int numActivities = mActivities.size();
+        for ( ; activityNdx < numActivities; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", false)) {
+                --activityNdx;
+                --numActivities;
+            }
+        }
+    }
+
+    /**
+     * Completely remove all activities associated with an existing task.
+     */
+    final void performClearTaskLocked() {
+        performClearTaskAtIndexLocked(0);
+    }
+
+    /**
+     * Perform clear operation as requested by
+     * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the
+     * stack to the given task, then look for
+     * an instance of that activity in the stack and, if found, finish all
+     * activities on top of it and return the instance.
+     *
+     * @param newR Description of the new activity being started.
+     * @return Returns the old activity that should be continued to be used,
+     * or null if none was found.
+     */
+    final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
+        int numActivities = mActivities.size();
+        for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
+            ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if (r.realActivity.equals(newR.realActivity)) {
+                // Here it is!  Now finish everything in front...
+                final ActivityRecord ret = r;
+
+                for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
+                    r = mActivities.get(activityNdx);
+                    if (r.finishing) {
+                        continue;
+                    }
+                    ActivityOptions opts = r.takeOptionsLocked();
+                    if (opts != null) {
+                        ret.updateOptionsLocked(opts);
+                    }
+                    if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
+                            false)) {
+                        --activityNdx;
+                        --numActivities;
+                    }
+                }
+
+                // Finally, if this is a normal launch mode (that is, not
+                // expecting onNewIntent()), then we will finish the current
+                // instance of the activity so a new fresh one can be started.
+                if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE
+                        && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) {
+                    if (!ret.finishing) {
+                        stack.finishActivityLocked(ret, Activity.RESULT_CANCELED, null,
+                                "clear", false);
+                        return null;
+                    }
+                }
+
+                return ret;
+            }
+        }
+
+        return null;
+    }
+
+    public ActivityManager.TaskThumbnails getTaskThumbnailsLocked() {
+        TaskAccessInfo info = getTaskAccessInfoLocked(true);
+        final ActivityRecord resumedActivity = stack.mResumedActivity;
+        if (resumedActivity != null && resumedActivity.thumbHolder == this) {
+            info.mainThumbnail = stack.screenshotActivities(resumedActivity);
+        }
+        if (info.mainThumbnail == null) {
+            info.mainThumbnail = lastThumbnail;
+        }
+        return info;
+    }
+
+    public Bitmap getTaskTopThumbnailLocked() {
+        final ActivityRecord resumedActivity = stack.mResumedActivity;
+        if (resumedActivity != null && resumedActivity.task == this) {
+            // This task is the current resumed task, we just need to take
+            // a screenshot of it and return that.
+            return stack.screenshotActivities(resumedActivity);
+        }
+        // Return the information about the task, to figure out the top
+        // thumbnail to return.
+        TaskAccessInfo info = getTaskAccessInfoLocked(true);
+        if (info.numSubThumbbails <= 0) {
+            return info.mainThumbnail != null ? info.mainThumbnail : lastThumbnail;
+        }
+        return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail;
+    }
+
+    public ActivityRecord removeTaskActivitiesLocked(int subTaskIndex,
+            boolean taskRequired) {
+        TaskAccessInfo info = getTaskAccessInfoLocked(false);
+        if (info.root == null) {
+            if (taskRequired) {
+                Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
+            }
+            return null;
+        }
+
+        if (subTaskIndex < 0) {
+            // Just remove the entire task.
+            performClearTaskAtIndexLocked(info.rootIndex);
+            return info.root;
+        }
+
+        if (subTaskIndex >= info.subtasks.size()) {
+            if (taskRequired) {
+                Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex);
+            }
+            return null;
+        }
+
+        // Remove all of this task's activities starting at the sub task.
+        TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex);
+        performClearTaskAtIndexLocked(subtask.index);
+        return subtask.activity;
+    }
+
+    boolean isHomeTask() {
+        return mTaskType == ActivityRecord.HOME_ACTIVITY_TYPE;
+    }
+
+    boolean isApplicationTask() {
+        return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+    }
+
+    public TaskAccessInfo getTaskAccessInfoLocked(boolean inclThumbs) {
+        final TaskAccessInfo thumbs = new TaskAccessInfo();
+        // How many different sub-thumbnails?
+        final int NA = mActivities.size();
+        int j = 0;
+        ThumbnailHolder holder = null;
+        while (j < NA) {
+            ActivityRecord ar = mActivities.get(j);
+            if (!ar.finishing) {
+                thumbs.root = ar;
+                thumbs.rootIndex = j;
+                holder = ar.thumbHolder;
+                if (holder != null) {
+                    thumbs.mainThumbnail = holder.lastThumbnail;
+                }
+                j++;
+                break;
+            }
+            j++;
+        }
+
+        if (j >= NA) {
+            return thumbs;
+        }
+
+        ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>();
+        thumbs.subtasks = subtasks;
+        while (j < NA) {
+            ActivityRecord ar = mActivities.get(j);
+            j++;
+            if (ar.finishing) {
+                continue;
+            }
+            if (ar.thumbHolder != holder && holder != null) {
+                thumbs.numSubThumbbails++;
+                holder = ar.thumbHolder;
+                TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask();
+                sub.holder = holder;
+                sub.activity = ar;
+                sub.index = j-1;
+                subtasks.add(sub);
+            }
+        }
+        if (thumbs.numSubThumbbails > 0) {
+            thumbs.retriever = new IThumbnailRetriever.Stub() {
+                @Override
+                public Bitmap getThumbnail(int index) {
+                    if (index < 0 || index >= thumbs.subtasks.size()) {
+                        return null;
+                    }
+                    TaskAccessInfo.SubTask sub = thumbs.subtasks.get(index);
+                    ActivityRecord resumedActivity = stack.mResumedActivity;
+                    if (resumedActivity != null && resumedActivity.thumbHolder == sub.holder) {
+                        return stack.screenshotActivities(resumedActivity);
+                    }
+                    return sub.holder.lastThumbnail;
+                }
+            };
+        }
+        return thumbs;
+    }
+
+    /**
+     * Find the activity in the history stack within the given task.  Returns
+     * the index within the history at which it's found, or < 0 if not found.
+     */
+    final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
+        final ComponentName realActivity = r.realActivity;
+        for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+            ActivityRecord candidate = mActivities.get(activityNdx);
+            if (candidate.finishing) {
+                continue;
+            }
+            if (candidate.realActivity.equals(realActivity)) {
+                return candidate;
+            }
+        }
+        return null;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) {
+            pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
+                    pw.print(" rootWasReset="); pw.print(rootWasReset);
+                    pw.print(" userId="); pw.print(userId);
+                    pw.print(" mTaskType="); pw.print(mTaskType);
+                    pw.print(" numFullscreen="); pw.print(numFullscreen);
+                    pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
+        }
+        if (affinity != null) {
+            pw.print(prefix); pw.print("affinity="); pw.println(affinity);
+        }
+        if (intent != null) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append(prefix); sb.append("intent={");
+            intent.toShortString(sb, false, true, false, true);
+            sb.append('}');
+            pw.println(sb.toString());
+        }
+        if (affinityIntent != null) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append(prefix); sb.append("affinityIntent={");
+            affinityIntent.toShortString(sb, false, true, false, true);
+            sb.append('}');
+            pw.println(sb.toString());
+        }
+        if (origActivity != null) {
+            pw.print(prefix); pw.print("origActivity=");
+            pw.println(origActivity.flattenToShortString());
+        }
+        if (realActivity != null) {
+            pw.print(prefix); pw.print("realActivity=");
+            pw.println(realActivity.flattenToShortString());
+        }
+        pw.print(prefix); pw.print("Activities="); pw.println(mActivities);
+        if (!askedCompatMode) {
+            pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode);
+        }
+        pw.print(prefix); pw.print("lastThumbnail="); pw.print(lastThumbnail);
+                pw.print(" lastDescription="); pw.println(lastDescription);
+        pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime);
+                pw.print(" (inactive for ");
+                pw.print((getInactiveDuration()/1000)); pw.println("s)");
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        if (stringName != null) {
+            sb.append(stringName);
+            sb.append(" U=");
+            sb.append(userId);
+            sb.append(" sz=");
+            sb.append(mActivities.size());
+            sb.append('}');
+            return sb.toString();
+        }
+        sb.append("TaskRecord{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" #");
+        sb.append(taskId);
+        if (affinity != null) {
+            sb.append(" A=");
+            sb.append(affinity);
+        } else if (intent != null) {
+            sb.append(" I=");
+            sb.append(intent.getComponent().flattenToShortString());
+        } else if (affinityIntent != null) {
+            sb.append(" aI=");
+            sb.append(affinityIntent.getComponent().flattenToShortString());
+        } else {
+            sb.append(" ??");
+        }
+        stringName = sb.toString();
+        return toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/ThumbnailHolder.java b/services/core/java/com/android/server/am/ThumbnailHolder.java
new file mode 100644
index 0000000..a6974f5
--- /dev/null
+++ b/services/core/java/com/android/server/am/ThumbnailHolder.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.graphics.Bitmap;
+
+public class ThumbnailHolder {
+    Bitmap lastThumbnail;         // Last thumbnail captured for this item.
+    CharSequence lastDescription; // Last description captured for this item.
+
+    void disposeThumbnail() {
+        lastThumbnail = null;
+        lastDescription = null;
+    }
+}
diff --git a/services/core/java/com/android/server/am/UriPermission.java b/services/core/java/com/android/server/am/UriPermission.java
new file mode 100644
index 0000000..1f12b74
--- /dev/null
+++ b/services/core/java/com/android/server/am/UriPermission.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2006 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.am;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.google.android.collect.Sets;
+
+import java.io.PrintWriter;
+import java.util.Comparator;
+import java.util.HashSet;
+
+/**
+ * Description of a permission granted to an app to access a particular URI.
+ *
+ * CTS tests for this functionality can be run with "runtest cts-appsecurity".
+ *
+ * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
+ *      src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+ */
+final class UriPermission {
+    private static final String TAG = "UriPermission";
+
+    public static final int STRENGTH_NONE = 0;
+    public static final int STRENGTH_OWNED = 1;
+    public static final int STRENGTH_GLOBAL = 2;
+    public static final int STRENGTH_PERSISTABLE = 3;
+
+    final int userHandle;
+    final String sourcePkg;
+    final String targetPkg;
+
+    /** Cached UID of {@link #targetPkg}; should not be persisted */
+    final int targetUid;
+
+    final Uri uri;
+
+    /**
+     * Allowed modes. All permission enforcement should use this field. Must
+     * always be a combination of {@link #ownedModeFlags},
+     * {@link #globalModeFlags}, {@link #persistableModeFlags}, and
+     * {@link #persistedModeFlags}. Mutations <em>must</em> only be performed by
+     * the owning class.
+     */
+    int modeFlags = 0;
+
+    /** Allowed modes with explicit owner. */
+    int ownedModeFlags = 0;
+    /** Allowed modes without explicit owner. */
+    int globalModeFlags = 0;
+    /** Allowed modes that have been offered for possible persisting. */
+    int persistableModeFlags = 0;
+    /** Allowed modes that should be persisted across device boots. */
+    int persistedModeFlags = 0;
+
+    /**
+     * Timestamp when {@link #persistedModeFlags} was first defined in
+     * {@link System#currentTimeMillis()} time base.
+     */
+    long persistedCreateTime = INVALID_TIME;
+
+    private static final long INVALID_TIME = Long.MIN_VALUE;
+
+    private HashSet<UriPermissionOwner> mReadOwners;
+    private HashSet<UriPermissionOwner> mWriteOwners;
+
+    private String stringName;
+
+    UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) {
+        this.userHandle = UserHandle.getUserId(targetUid);
+        this.sourcePkg = sourcePkg;
+        this.targetPkg = targetPkg;
+        this.targetUid = targetUid;
+        this.uri = uri;
+    }
+
+    private void updateModeFlags() {
+        modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags;
+    }
+
+    /**
+     * Initialize persisted modes as read from file. This doesn't issue any
+     * global or owner grants.
+     */
+    void initPersistedModes(int modeFlags, long createdTime) {
+        persistableModeFlags = modeFlags;
+        persistedModeFlags = modeFlags;
+        persistedCreateTime = createdTime;
+
+        updateModeFlags();
+    }
+
+    void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) {
+        if (persistable) {
+            persistableModeFlags |= modeFlags;
+        }
+
+        if (owner == null) {
+            globalModeFlags |= modeFlags;
+        } else {
+            if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                addReadOwner(owner);
+            }
+            if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                addWriteOwner(owner);
+            }
+        }
+
+        updateModeFlags();
+    }
+
+    /**
+     * @return if mode changes should trigger persisting.
+     */
+    boolean takePersistableModes(int modeFlags) {
+        if ((modeFlags & persistableModeFlags) != modeFlags) {
+            throw new SecurityException("Requested flags 0x"
+                    + Integer.toHexString(modeFlags) + ", but only 0x"
+                    + Integer.toHexString(persistableModeFlags) + " are allowed");
+        }
+
+        final int before = persistedModeFlags;
+        persistedModeFlags |= (persistableModeFlags & modeFlags);
+
+        if (persistedModeFlags != 0) {
+            persistedCreateTime = System.currentTimeMillis();
+        }
+
+        updateModeFlags();
+        return persistedModeFlags != before;
+    }
+
+    boolean releasePersistableModes(int modeFlags) {
+        final int before = persistedModeFlags;
+
+        persistableModeFlags &= ~modeFlags;
+        persistedModeFlags &= ~modeFlags;
+
+        if (persistedModeFlags == 0) {
+            persistedCreateTime = INVALID_TIME;
+        }
+
+        updateModeFlags();
+        return persistedModeFlags != before;
+    }
+
+    /**
+     * @return if mode changes should trigger persisting.
+     */
+    boolean clearModes(int modeFlags, boolean persistable) {
+        final int before = persistedModeFlags;
+
+        if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+            if (persistable) {
+                persistableModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+                persistedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+            }
+            globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+            if (mReadOwners != null) {
+                ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+                for (UriPermissionOwner r : mReadOwners) {
+                    r.removeReadPermission(this);
+                }
+                mReadOwners = null;
+            }
+        }
+        if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+            if (persistable) {
+                persistableModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+                persistedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+            }
+            globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+            if (mWriteOwners != null) {
+                ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+                for (UriPermissionOwner r : mWriteOwners) {
+                    r.removeWritePermission(this);
+                }
+                mWriteOwners = null;
+            }
+        }
+
+        if (persistedModeFlags == 0) {
+            persistedCreateTime = INVALID_TIME;
+        }
+
+        updateModeFlags();
+        return persistedModeFlags != before;
+    }
+
+    /**
+     * Return strength of this permission grant for the given flags.
+     */
+    public int getStrength(int modeFlags) {
+        if ((persistableModeFlags & modeFlags) == modeFlags) {
+            return STRENGTH_PERSISTABLE;
+        } else if ((globalModeFlags & modeFlags) == modeFlags) {
+            return STRENGTH_GLOBAL;
+        } else if ((ownedModeFlags & modeFlags) == modeFlags) {
+            return STRENGTH_OWNED;
+        } else {
+            return STRENGTH_NONE;
+        }
+    }
+
+    private void addReadOwner(UriPermissionOwner owner) {
+        if (mReadOwners == null) {
+            mReadOwners = Sets.newHashSet();
+            ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
+            updateModeFlags();
+        }
+        if (mReadOwners.add(owner)) {
+            owner.addReadPermission(this);
+        }
+    }
+
+    /**
+     * Remove given read owner, updating {@Link #modeFlags} as needed.
+     */
+    void removeReadOwner(UriPermissionOwner owner) {
+        if (!mReadOwners.remove(owner)) {
+            Log.wtf(TAG, "Unknown read owner " + owner + " in " + this);
+        }
+        if (mReadOwners.size() == 0) {
+            mReadOwners = null;
+            ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+            updateModeFlags();
+        }
+    }
+
+    private void addWriteOwner(UriPermissionOwner owner) {
+        if (mWriteOwners == null) {
+            mWriteOwners = Sets.newHashSet();
+            ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+            updateModeFlags();
+        }
+        if (mWriteOwners.add(owner)) {
+            owner.addWritePermission(this);
+        }
+    }
+
+    /**
+     * Remove given write owner, updating {@Link #modeFlags} as needed.
+     */
+    void removeWriteOwner(UriPermissionOwner owner) {
+        if (!mWriteOwners.remove(owner)) {
+            Log.wtf(TAG, "Unknown write owner " + owner + " in " + this);
+        }
+        if (mWriteOwners.size() == 0) {
+            mWriteOwners = null;
+            ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+            updateModeFlags();
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName != null) {
+            return stringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("UriPermission{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        sb.append(uri);
+        sb.append('}');
+        return stringName = sb.toString();
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix);
+        pw.print("userHandle=" + userHandle);
+        pw.print(" sourcePkg=" + sourcePkg);
+        pw.println(" targetPkg=" + targetPkg);
+
+        pw.print(prefix);
+        pw.print("mode=0x" + Integer.toHexString(modeFlags));
+        pw.print(" owned=0x" + Integer.toHexString(ownedModeFlags));
+        pw.print(" global=0x" + Integer.toHexString(globalModeFlags));
+        pw.print(" persistable=0x" + Integer.toHexString(persistableModeFlags));
+        pw.print(" persisted=0x" + Integer.toHexString(persistedModeFlags));
+        if (persistedCreateTime != INVALID_TIME) {
+            pw.print(" persistedCreate=" + persistedCreateTime);
+        }
+        pw.println();
+
+        if (mReadOwners != null) {
+            pw.print(prefix);
+            pw.println("readOwners:");
+            for (UriPermissionOwner owner : mReadOwners) {
+                pw.print(prefix);
+                pw.println("  * " + owner);
+            }
+        }
+        if (mWriteOwners != null) {
+            pw.print(prefix);
+            pw.println("writeOwners:");
+            for (UriPermissionOwner owner : mReadOwners) {
+                pw.print(prefix);
+                pw.println("  * " + owner);
+            }
+        }
+    }
+
+    public static class PersistedTimeComparator implements Comparator<UriPermission> {
+        @Override
+        public int compare(UriPermission lhs, UriPermission rhs) {
+            return Long.compare(lhs.persistedCreateTime, rhs.persistedCreateTime);
+        }
+    }
+
+    /**
+     * Snapshot of {@link UriPermission} with frozen
+     * {@link UriPermission#persistedModeFlags} state.
+     */
+    public static class Snapshot {
+        final int userHandle;
+        final String sourcePkg;
+        final String targetPkg;
+        final Uri uri;
+        final int persistedModeFlags;
+        final long persistedCreateTime;
+
+        private Snapshot(UriPermission perm) {
+            this.userHandle = perm.userHandle;
+            this.sourcePkg = perm.sourcePkg;
+            this.targetPkg = perm.targetPkg;
+            this.uri = perm.uri;
+            this.persistedModeFlags = perm.persistedModeFlags;
+            this.persistedCreateTime = perm.persistedCreateTime;
+        }
+    }
+
+    public Snapshot snapshot() {
+        return new Snapshot(this);
+    }
+
+    public android.content.UriPermission buildPersistedPublicApiObject() {
+        return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime);
+    }
+}
diff --git a/services/core/java/com/android/server/am/UriPermissionOwner.java b/services/core/java/com/android/server/am/UriPermissionOwner.java
new file mode 100644
index 0000000..7bbd3bc
--- /dev/null
+++ b/services/core/java/com/android/server/am/UriPermissionOwner.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2010 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.am;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+final class UriPermissionOwner {
+    final ActivityManagerService service;
+    final Object owner;
+
+    Binder externalToken;
+
+    HashSet<UriPermission> readUriPermissions; // special access to reading uris.
+    HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+
+    class ExternalToken extends Binder {
+        UriPermissionOwner getOwner() {
+            return UriPermissionOwner.this;
+        }
+    }
+
+    UriPermissionOwner(ActivityManagerService _service, Object _owner) {
+        service = _service;
+        owner = _owner;
+    }
+
+    Binder getExternalTokenLocked() {
+        if (externalToken == null) {
+            externalToken = new ExternalToken();
+        }
+        return externalToken;
+    }
+
+    static UriPermissionOwner fromExternalToken(IBinder token) {
+        if (token instanceof ExternalToken) {
+            return ((ExternalToken)token).getOwner();
+        }
+        return null;
+    }
+
+    void removeUriPermissionsLocked() {
+        removeUriPermissionsLocked(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    void removeUriPermissionsLocked(int mode) {
+        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+                && readUriPermissions != null) {
+            for (UriPermission perm : readUriPermissions) {
+                perm.removeReadOwner(this);
+                service.removeUriPermissionIfNeededLocked(perm);
+            }
+            readUriPermissions = null;
+        }
+        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+                && writeUriPermissions != null) {
+            for (UriPermission perm : writeUriPermissions) {
+                perm.removeWriteOwner(this);
+                service.removeUriPermissionIfNeededLocked(perm);
+            }
+            writeUriPermissions = null;
+        }
+    }
+
+    void removeUriPermissionLocked(Uri uri, int mode) {
+        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+                && readUriPermissions != null) {
+            Iterator<UriPermission> it = readUriPermissions.iterator();
+            while (it.hasNext()) {
+                UriPermission perm = it.next();
+                if (uri.equals(perm.uri)) {
+                    perm.removeReadOwner(this);
+                    service.removeUriPermissionIfNeededLocked(perm);
+                    it.remove();
+                }
+            }
+            if (readUriPermissions.size() == 0) {
+                readUriPermissions = null;
+            }
+        }
+        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+                && writeUriPermissions != null) {
+            Iterator<UriPermission> it = writeUriPermissions.iterator();
+            while (it.hasNext()) {
+                UriPermission perm = it.next();
+                if (uri.equals(perm.uri)) {
+                    perm.removeWriteOwner(this);
+                    service.removeUriPermissionIfNeededLocked(perm);
+                    it.remove();
+                }
+            }
+            if (writeUriPermissions.size() == 0) {
+                writeUriPermissions = null;
+            }
+        }
+    }
+
+    public void addReadPermission(UriPermission perm) {
+        if (readUriPermissions == null) {
+            readUriPermissions = new HashSet<UriPermission>();
+        }
+        readUriPermissions.add(perm);
+    }
+
+    public void addWritePermission(UriPermission perm) {
+        if (writeUriPermissions == null) {
+            writeUriPermissions = new HashSet<UriPermission>();
+        }
+        writeUriPermissions.add(perm);
+    }
+
+    public void removeReadPermission(UriPermission perm) {
+        readUriPermissions.remove(perm);
+        if (readUriPermissions.size() == 0) {
+            readUriPermissions = null;
+        }
+    }
+
+    public void removeWritePermission(UriPermission perm) {
+        writeUriPermissions.remove(perm);
+        if (writeUriPermissions.size() == 0) {
+            writeUriPermissions = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return owner.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/am/UsageStatsService.java b/services/core/java/com/android/server/am/UsageStatsService.java
new file mode 100644
index 0000000..e96d8b1
--- /dev/null
+++ b/services/core/java/com/android/server/am/UsageStatsService.java
@@ -0,0 +1,1173 @@
+/*
+ * Copyright (C) 2006-2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.FileUtils;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.app.IUsageStats;
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.os.PkgUsageStats;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This service collects the statistics associated with usage
+ * of various components, like when a particular package is launched or
+ * paused and aggregates events like number of time a component is launched
+ * total duration of a component launch.
+ */
+public final class UsageStatsService extends IUsageStats.Stub {
+    public static final String SERVICE_NAME = "usagestats";
+    private static final boolean localLOGV = false;
+    private static final boolean REPORT_UNEXPECTED = false;
+    private static final String TAG = "UsageStats";
+    
+    // Current on-disk Parcel version
+    private static final int VERSION = 1008;
+
+    private static final int CHECKIN_VERSION = 4;
+    
+    private static final String FILE_PREFIX = "usage-";
+
+    private static final String FILE_HISTORY = FILE_PREFIX + "history.xml";
+
+    private static final int FILE_WRITE_INTERVAL = 30*60*1000; //ms
+    
+    private static final int MAX_NUM_FILES = 5;
+    
+    private static final int NUM_LAUNCH_TIME_BINS = 10;
+    private static final int[] LAUNCH_TIME_BINS = {
+        250, 500, 750, 1000, 1500, 2000, 3000, 4000, 5000
+    };
+    
+    static IUsageStats sService;
+    private Context mContext;
+    // structure used to maintain statistics since the last checkin.
+    final private ArrayMap<String, PkgUsageStatsExtended> mStats;
+
+    // Maintains the last time any component was resumed, for all time.
+    final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes;
+
+    // To remove last-resume time stats when a pacakge is removed.
+    private PackageMonitor mPackageMonitor;
+
+    // Lock to update package stats. Methods suffixed by SLOCK should invoked with
+    // this lock held
+    final Object mStatsLock;
+    // Lock to write to file. Methods suffixed by FLOCK should invoked with
+    // this lock held.
+    final Object mFileLock;
+    // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks
+    private String mLastResumedPkg;
+    private String mLastResumedComp;
+    private boolean mIsResumed;
+    private File mFile;
+    private AtomicFile mHistoryFile;
+    private String mFileLeaf;
+    private File mDir;
+
+    private Calendar mCal; // guarded by itself
+
+    private final AtomicInteger mLastWriteDay = new AtomicInteger(-1);
+    private final AtomicLong mLastWriteElapsedTime = new AtomicLong(0);
+    private final AtomicBoolean mUnforcedDiskWriteRunning = new AtomicBoolean(false);
+    
+    static class TimeStats {
+        int count;
+        int[] times = new int[NUM_LAUNCH_TIME_BINS];
+        
+        TimeStats() {
+        }
+        
+        void incCount() {
+            count++;
+        }
+        
+        void add(int val) {
+            final int[] bins = LAUNCH_TIME_BINS;
+            for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) {
+                if (val < bins[i]) {
+                    times[i]++;
+                    return;
+                }
+            }
+            times[NUM_LAUNCH_TIME_BINS-1]++;
+        }
+        
+        TimeStats(Parcel in) {
+            count = in.readInt();
+            final int[] localTimes = times;
+            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
+                localTimes[i] = in.readInt();
+            }
+        }
+        
+        void writeToParcel(Parcel out) {
+            out.writeInt(count);
+            final int[] localTimes = times;
+            for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) {
+                out.writeInt(localTimes[i]);
+            }
+        }
+    }
+    
+    private class PkgUsageStatsExtended {
+        final ArrayMap<String, TimeStats> mLaunchTimes
+                = new ArrayMap<String, TimeStats>();
+        final ArrayMap<String, TimeStats> mFullyDrawnTimes
+                = new ArrayMap<String, TimeStats>();
+        int mLaunchCount;
+        long mUsageTime;
+        long mPausedTime;
+        long mResumedTime;
+        
+        PkgUsageStatsExtended() {
+            mLaunchCount = 0;
+            mUsageTime = 0;
+        }
+        
+        PkgUsageStatsExtended(Parcel in) {
+            mLaunchCount = in.readInt();
+            mUsageTime = in.readLong();
+            if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount
+                    + ", Usage time:" + mUsageTime);
+            
+            final int numLaunchTimeStats = in.readInt();
+            if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats);
+            mLaunchTimes.ensureCapacity(numLaunchTimeStats);
+            for (int i=0; i<numLaunchTimeStats; i++) {
+                String comp = in.readString();
+                if (localLOGV) Slog.v(TAG, "Component: " + comp);
+                TimeStats times = new TimeStats(in);
+                mLaunchTimes.put(comp, times);
+            }
+
+            final int numFullyDrawnTimeStats = in.readInt();
+            if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats);
+            mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats);
+            for (int i=0; i<numFullyDrawnTimeStats; i++) {
+                String comp = in.readString();
+                if (localLOGV) Slog.v(TAG, "Component: " + comp);
+                TimeStats times = new TimeStats(in);
+                mFullyDrawnTimes.put(comp, times);
+            }
+        }
+
+        void updateResume(String comp, boolean launched) {
+            if (launched) {
+                mLaunchCount ++;
+            }
+            mResumedTime = SystemClock.elapsedRealtime();
+        }
+        
+        void updatePause() {
+            mPausedTime =  SystemClock.elapsedRealtime();
+            mUsageTime += (mPausedTime - mResumedTime);
+        }
+        
+        void addLaunchCount(String comp) {
+            TimeStats times = mLaunchTimes.get(comp);
+            if (times == null) {
+                times = new TimeStats();
+                mLaunchTimes.put(comp, times);
+            }
+            times.incCount();
+        }
+        
+        void addLaunchTime(String comp, int millis) {
+            TimeStats times = mLaunchTimes.get(comp);
+            if (times == null) {
+                times = new TimeStats();
+                mLaunchTimes.put(comp, times);
+            }
+            times.add(millis);
+        }
+
+        void addFullyDrawnTime(String comp, int millis) {
+            TimeStats times = mFullyDrawnTimes.get(comp);
+            if (times == null) {
+                times = new TimeStats();
+                mFullyDrawnTimes.put(comp, times);
+            }
+            times.add(millis);
+        }
+
+        void writeToParcel(Parcel out) {
+            out.writeInt(mLaunchCount);
+            out.writeLong(mUsageTime);
+            final int numLaunchTimeStats = mLaunchTimes.size();
+            out.writeInt(numLaunchTimeStats);
+            for (int i=0; i<numLaunchTimeStats; i++) {
+                out.writeString(mLaunchTimes.keyAt(i));
+                mLaunchTimes.valueAt(i).writeToParcel(out);
+            }
+            final int numFullyDrawnTimeStats = mFullyDrawnTimes.size();
+            out.writeInt(numFullyDrawnTimeStats);
+            for (int i=0; i<numFullyDrawnTimeStats; i++) {
+                out.writeString(mFullyDrawnTimes.keyAt(i));
+                mFullyDrawnTimes.valueAt(i).writeToParcel(out);
+            }
+        }
+        
+        void clear() {
+            mLaunchTimes.clear();
+            mFullyDrawnTimes.clear();
+            mLaunchCount = 0;
+            mUsageTime = 0;
+        }
+    }
+    
+    UsageStatsService(String dir) {
+        mStats = new ArrayMap<String, PkgUsageStatsExtended>();
+        mLastResumeTimes = new ArrayMap<String, ArrayMap<String, Long>>();
+        mStatsLock = new Object();
+        mFileLock = new Object();
+        mDir = new File(dir);
+        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+        
+        mDir.mkdir();
+        
+        // Remove any old usage files from previous versions.
+        File parentDir = mDir.getParentFile();
+        String fList[] = parentDir.list();
+        if (fList != null) {
+            String prefix = mDir.getName() + ".";
+            int i = fList.length;
+            while (i > 0) {
+                i--;
+                if (fList[i].startsWith(prefix)) {
+                    Slog.i(TAG, "Deleting old usage file: " + fList[i]);
+                    (new File(parentDir, fList[i])).delete();
+                }
+            }
+        }
+        
+        // Update current stats which are binned by date
+        mFileLeaf = getCurrentDateStr(FILE_PREFIX);
+        mFile = new File(mDir, mFileLeaf);
+        mHistoryFile = new AtomicFile(new File(mDir, FILE_HISTORY));
+        readStatsFromFile();
+        readHistoryStatsFromFile();
+        mLastWriteElapsedTime.set(SystemClock.elapsedRealtime());
+        // mCal was set by getCurrentDateStr(), want to use that same time.
+        mLastWriteDay.set(mCal.get(Calendar.DAY_OF_YEAR));
+    }
+
+    /*
+     * Utility method to convert date into string.
+     */
+    private String getCurrentDateStr(String prefix) {
+        StringBuilder sb = new StringBuilder();
+        synchronized (mCal) {
+            mCal.setTimeInMillis(System.currentTimeMillis());
+            if (prefix != null) {
+                sb.append(prefix);
+            }
+            sb.append(mCal.get(Calendar.YEAR));
+            int mm = mCal.get(Calendar.MONTH) - Calendar.JANUARY +1;
+            if (mm < 10) {
+                sb.append("0");
+            }
+            sb.append(mm);
+            int dd = mCal.get(Calendar.DAY_OF_MONTH);
+            if (dd < 10) {
+                sb.append("0");
+            }
+            sb.append(dd);
+        }
+        return sb.toString();
+    }
+    
+    private Parcel getParcelForFile(File file) throws IOException {
+        FileInputStream stream = new FileInputStream(file);
+        byte[] raw = readFully(stream);
+        Parcel in = Parcel.obtain();
+        in.unmarshall(raw, 0, raw.length);
+        in.setDataPosition(0);
+        stream.close();
+        return in;
+    }
+    
+    private void readStatsFromFile() {
+        File newFile = mFile;
+        synchronized (mFileLock) {
+            try {
+                if (newFile.exists()) {
+                    readStatsFLOCK(newFile);
+                } else {
+                    // Check for file limit before creating a new file
+                    checkFileLimitFLOCK();
+                    newFile.createNewFile();
+                }
+            } catch (IOException e) {
+                Slog.w(TAG,"Error : " + e + " reading data from file:" + newFile);
+            }
+        }
+    }
+    
+    private void readStatsFLOCK(File file) throws IOException {
+        Parcel in = getParcelForFile(file);
+        int vers = in.readInt();
+        if (vers != VERSION) {
+            Slog.w(TAG, "Usage stats version changed; dropping");
+            return;
+        }
+        int N = in.readInt();
+        while (N > 0) {
+            N--;
+            String pkgName = in.readString();
+            if (pkgName == null) {
+                break;
+            }
+            if (localLOGV) Slog.v(TAG, "Reading package #" + N + ": " + pkgName);
+            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
+            synchronized (mStatsLock) {
+                mStats.put(pkgName, pus);
+            }
+        }
+    }
+
+    private void readHistoryStatsFromFile() {
+        synchronized (mFileLock) {
+            if (mHistoryFile.getBaseFile().exists()) {
+                readHistoryStatsFLOCK(mHistoryFile);
+            }
+        }
+    }
+
+    private void readHistoryStatsFLOCK(AtomicFile file) {
+        FileInputStream fis = null;
+        try {
+            fis = mHistoryFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG) {
+                eventType = parser.next();
+            }
+            String tagName = parser.getName();
+            if ("usage-history".equals(tagName)) {
+                String pkg = null;
+                do {
+                    eventType = parser.next();
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = parser.getName();
+                        int depth = parser.getDepth();
+                        if ("pkg".equals(tagName) && depth == 2) {
+                            pkg = parser.getAttributeValue(null, "name");
+                        } else if ("comp".equals(tagName) && depth == 3 && pkg != null) {
+                            String comp = parser.getAttributeValue(null, "name");
+                            String lastResumeTimeStr = parser.getAttributeValue(null, "lrt");
+                            if (comp != null && lastResumeTimeStr != null) {
+                                try {
+                                    long lastResumeTime = Long.parseLong(lastResumeTimeStr);
+                                    synchronized (mStatsLock) {
+                                        ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg);
+                                        if (lrt == null) {
+                                            lrt = new ArrayMap<String, Long>();
+                                            mLastResumeTimes.put(pkg, lrt);
+                                        }
+                                        lrt.put(comp, lastResumeTime);
+                                    }
+                                } catch (NumberFormatException e) {
+                                }
+                            }
+                        }
+                    } else if (eventType == XmlPullParser.END_TAG) {
+                        if ("pkg".equals(parser.getName())) {
+                            pkg = null;
+                        }
+                    }
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG,"Error reading history stats: " + e);
+        } catch (IOException e) {
+            Slog.w(TAG,"Error reading history stats: " + e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    private ArrayList<String> getUsageStatsFileListFLOCK() {
+        // Check if there are too many files in the system and delete older files
+        String fList[] = mDir.list();
+        if (fList == null) {
+            return null;
+        }
+        ArrayList<String> fileList = new ArrayList<String>();
+        for (String file : fList) {
+            if (!file.startsWith(FILE_PREFIX)) {
+                continue;
+            }
+            if (file.endsWith(".bak")) {
+                (new File(mDir, file)).delete();
+                continue;
+            }
+            fileList.add(file);
+        }
+        return fileList;
+    }
+    
+    private void checkFileLimitFLOCK() {
+        // Get all usage stats output files
+        ArrayList<String> fileList = getUsageStatsFileListFLOCK();
+        if (fileList == null) {
+            // Strange but we dont have to delete any thing
+            return;
+        }
+        int count = fileList.size();
+        if (count <= MAX_NUM_FILES) {
+            return;
+        }
+        // Sort files
+        Collections.sort(fileList);
+        count -= MAX_NUM_FILES;
+        // Delete older files
+        for (int i = 0; i < count; i++) {
+            String fileName = fileList.get(i);
+            File file = new File(mDir, fileName);
+            Slog.i(TAG, "Deleting usage file : " + fileName);
+            file.delete();
+        }
+    }
+
+    /**
+     * Conditionally start up a disk write if it's been awhile, or the
+     * day has rolled over.
+     *
+     * This is called indirectly from user-facing actions (when
+     * 'force' is false) so it tries to be quick, without writing to
+     * disk directly or acquiring heavy locks.
+     *
+     * @params force  do an unconditional, synchronous stats flush
+     *                to disk on the current thread.
+     * @params forceWriteHistoryStats Force writing of historical stats.
+     */
+    private void writeStatsToFile(final boolean force, final boolean forceWriteHistoryStats) {
+        int curDay;
+        synchronized (mCal) {
+            mCal.setTimeInMillis(System.currentTimeMillis());
+            curDay = mCal.get(Calendar.DAY_OF_YEAR);
+        }
+        final boolean dayChanged = curDay != mLastWriteDay.get();
+
+        // Determine if the day changed...  note that this will be wrong
+        // if the year has changed but we are in the same day of year...
+        // we can probably live with this.
+        final long currElapsedTime = SystemClock.elapsedRealtime();
+
+        // Fast common path, without taking the often-contentious
+        // mFileLock.
+        if (!force) {
+            if (!dayChanged &&
+                (currElapsedTime - mLastWriteElapsedTime.get()) < FILE_WRITE_INTERVAL) {
+                // wait till the next update
+                return;
+            }
+            if (mUnforcedDiskWriteRunning.compareAndSet(false, true)) {
+                new Thread("UsageStatsService_DiskWriter") {
+                    public void run() {
+                        try {
+                            if (localLOGV) Slog.d(TAG, "Disk writer thread starting.");
+                            writeStatsToFile(true, false);
+                        } finally {
+                            mUnforcedDiskWriteRunning.set(false);
+                            if (localLOGV) Slog.d(TAG, "Disk writer thread ending.");
+                        }
+                    }
+                }.start();
+            }
+            return;
+        }
+
+        synchronized (mFileLock) {
+            // Get the most recent file
+            mFileLeaf = getCurrentDateStr(FILE_PREFIX);
+            // Copy current file to back up
+            File backupFile = null;
+            if (mFile != null && mFile.exists()) {
+                backupFile = new File(mFile.getPath() + ".bak");
+                if (!backupFile.exists()) {
+                    if (!mFile.renameTo(backupFile)) {
+                        Slog.w(TAG, "Failed to persist new stats");
+                        return;
+                    }
+                } else {
+                    mFile.delete();
+                }
+            }
+
+            try {
+                // Write mStats to file
+                writeStatsFLOCK(mFile);
+                mLastWriteElapsedTime.set(currElapsedTime);
+                if (dayChanged) {
+                    mLastWriteDay.set(curDay);
+                    // clear stats
+                    synchronized (mStats) {
+                        mStats.clear();
+                    }
+                    mFile = new File(mDir, mFileLeaf);
+                    checkFileLimitFLOCK();
+                }
+
+                if (dayChanged || forceWriteHistoryStats) {
+                    // Write history stats daily, or when forced (due to shutdown).
+                    writeHistoryStatsFLOCK(mHistoryFile);
+                }
+
+                // Delete the backup file
+                if (backupFile != null) {
+                    backupFile.delete();
+                }
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed writing stats to file:" + mFile);
+                if (backupFile != null) {
+                    mFile.delete();
+                    backupFile.renameTo(mFile);
+                }
+            }
+        }
+        if (localLOGV) Slog.d(TAG, "Dumped usage stats.");
+    }
+
+    private void writeStatsFLOCK(File file) throws IOException {
+        FileOutputStream stream = new FileOutputStream(file);
+        try {
+            Parcel out = Parcel.obtain();
+            writeStatsToParcelFLOCK(out);
+            stream.write(out.marshall());
+            out.recycle();
+            stream.flush();
+        } finally {
+            FileUtils.sync(stream);
+            stream.close();
+        }
+    }
+
+    private void writeStatsToParcelFLOCK(Parcel out) {
+        synchronized (mStatsLock) {
+            out.writeInt(VERSION);
+            Set<String> keys = mStats.keySet();
+            out.writeInt(keys.size());
+            for (String key : keys) {
+                PkgUsageStatsExtended pus = mStats.get(key);
+                out.writeString(key);
+                pus.writeToParcel(out);
+            }
+        }
+    }
+
+    /** Filter out stats for any packages which aren't present anymore. */
+    private void filterHistoryStats() {
+        synchronized (mStatsLock) {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            for (int i=0; i<mLastResumeTimes.size(); i++) {
+                String pkg = mLastResumeTimes.keyAt(i);
+                try {
+                    if (pm.getPackageUid(pkg, 0) < 0) {
+                        mLastResumeTimes.removeAt(i);
+                        i--;
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
+    private void writeHistoryStatsFLOCK(AtomicFile historyFile) {
+        FileOutputStream fos = null;
+        try {
+            fos = historyFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            out.startTag(null, "usage-history");
+            synchronized (mStatsLock) {
+                for (int i=0; i<mLastResumeTimes.size(); i++) {
+                    out.startTag(null, "pkg");
+                    out.attribute(null, "name", mLastResumeTimes.keyAt(i));
+                    ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i);
+                    for (int j=0; j<comp.size(); j++) {
+                        out.startTag(null, "comp");
+                        out.attribute(null, "name", comp.keyAt(j));
+                        out.attribute(null, "lrt", comp.valueAt(j).toString());
+                        out.endTag(null, "comp");
+                    }
+                    out.endTag(null, "pkg");
+                }
+            }
+            out.endTag(null, "usage-history");
+            out.endDocument();
+
+            historyFile.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.w(TAG,"Error writing history stats" + e);
+            if (fos != null) {
+                historyFile.failWrite(fos);
+            }
+        }
+    }
+
+    public void publish(Context context) {
+        mContext = context;
+        ServiceManager.addService(SERVICE_NAME, asBinder());
+    }
+
+    /**
+     * Start watching packages to remove stats when a package is uninstalled.
+     * May only be called when the package manager is ready.
+     */
+    public void monitorPackages() {
+        mPackageMonitor = new PackageMonitor() {
+            @Override
+            public void onPackageRemovedAllUsers(String packageName, int uid) {
+                synchronized (mStatsLock) {
+                    mLastResumeTimes.remove(packageName);
+                }
+            }
+        };
+        mPackageMonitor.register(mContext, null, true);
+        filterHistoryStats();
+    }
+
+    public void shutdown() {
+        if (mPackageMonitor != null) {
+            mPackageMonitor.unregister();
+        }
+        Slog.i(TAG, "Writing usage stats before shutdown...");
+        writeStatsToFile(true, true);
+    }
+
+    public static IUsageStats getService() {
+        if (sService != null) {
+            return sService;
+        }
+        IBinder b = ServiceManager.getService(SERVICE_NAME);
+        sService = asInterface(b);
+        return sService;
+    }
+    
+    public void noteResumeComponent(ComponentName componentName) {
+        enforceCallingPermission();
+        String pkgName;
+        synchronized (mStatsLock) {
+            if ((componentName == null) ||
+                    ((pkgName = componentName.getPackageName()) == null)) {
+                return;
+            }
+            
+            final boolean samePackage = pkgName.equals(mLastResumedPkg);
+            if (mIsResumed) {
+                if (mLastResumedPkg != null) {
+                    // We last resumed some other package...  just pause it now
+                    // to recover.
+                    if (REPORT_UNEXPECTED) Slog.i(TAG, "Unexpected resume of " + pkgName
+                            + " while already resumed in " + mLastResumedPkg);
+                    PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg);
+                    if (pus != null) {
+                        pus.updatePause();
+                    }
+                }
+            }
+            
+            final boolean sameComp = samePackage
+                    && componentName.getClassName().equals(mLastResumedComp);
+            
+            mIsResumed = true;
+            mLastResumedPkg = pkgName;
+            mLastResumedComp = componentName.getClassName();
+            
+            if (localLOGV) Slog.i(TAG, "started component:" + pkgName);
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            if (pus == null) {
+                pus = new PkgUsageStatsExtended();
+                mStats.put(pkgName, pus);
+            }
+            pus.updateResume(mLastResumedComp, !samePackage);
+            if (!sameComp) {
+                pus.addLaunchCount(mLastResumedComp);
+            }
+
+            ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName);
+            if (componentResumeTimes == null) {
+                componentResumeTimes = new ArrayMap<String, Long>();
+                mLastResumeTimes.put(pkgName, componentResumeTimes);
+            }
+            componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis());
+        }
+    }
+
+    public void notePauseComponent(ComponentName componentName) {
+        enforceCallingPermission();
+        
+        synchronized (mStatsLock) {
+            String pkgName;
+            if ((componentName == null) ||
+                    ((pkgName = componentName.getPackageName()) == null)) {
+                return;
+            }
+            if (!mIsResumed) {
+                if (REPORT_UNEXPECTED) Slog.i(TAG, "Something wrong here, didn't expect "
+                        + pkgName + " to be paused");
+                return;
+            }
+            mIsResumed = false;
+            
+            if (localLOGV) Slog.i(TAG, "paused component:"+pkgName);
+        
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            if (pus == null) {
+                // Weird some error here
+                Slog.i(TAG, "No package stats for pkg:"+pkgName);
+                return;
+            }
+            pus.updatePause();
+        }
+        
+        // Persist current data to file if needed.
+        writeStatsToFile(false, false);
+    }
+    
+    public void noteLaunchTime(ComponentName componentName, int millis) {
+        enforceCallingPermission();
+        String pkgName;
+        if ((componentName == null) ||
+                ((pkgName = componentName.getPackageName()) == null)) {
+            return;
+        }
+        
+        // Persist current data to file if needed.
+        writeStatsToFile(false, false);
+        
+        synchronized (mStatsLock) {
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            if (pus != null) {
+                pus.addLaunchTime(componentName.getClassName(), millis);
+            }
+        }
+    }
+    
+    public void noteFullyDrawnTime(ComponentName componentName, int millis) {
+        enforceCallingPermission();
+        String pkgName;
+        if ((componentName == null) ||
+                ((pkgName = componentName.getPackageName()) == null)) {
+            return;
+        }
+
+        // Persist current data to file if needed.
+        writeStatsToFile(false, false);
+
+        synchronized (mStatsLock) {
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            if (pus != null) {
+                pus.addFullyDrawnTime(componentName.getClassName(), millis);
+            }
+        }
+    }
+
+    public void enforceCallingPermission() {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                Binder.getCallingPid(), Binder.getCallingUid(), null);
+    }
+    
+    public PkgUsageStats getPkgUsageStats(ComponentName componentName) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+        String pkgName;
+        if ((componentName == null) ||
+                ((pkgName = componentName.getPackageName()) == null)) {
+            return null;
+        }
+        synchronized (mStatsLock) {
+            PkgUsageStatsExtended pus = mStats.get(pkgName);
+            Map<String, Long> lastResumeTimes = mLastResumeTimes.get(pkgName);
+            if (pus == null && lastResumeTimes == null) {
+                return null;
+            }
+            int launchCount = pus != null ? pus.mLaunchCount : 0;
+            long usageTime = pus != null ? pus.mUsageTime : 0;
+            return new PkgUsageStats(pkgName, launchCount, usageTime, lastResumeTimes);
+        }
+    }
+    
+    public PkgUsageStats[] getAllPkgUsageStats() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_USAGE_STATS, null);
+        synchronized (mStatsLock) {
+            int size = mLastResumeTimes.size();
+            if (size <= 0) {
+                return null;
+            }
+            PkgUsageStats retArr[] = new PkgUsageStats[size];
+            for (int i=0; i<size; i++) {
+                String pkg = mLastResumeTimes.keyAt(i);
+                long usageTime = 0;
+                int launchCount = 0;
+
+                PkgUsageStatsExtended pus = mStats.get(pkg);
+                if (pus != null) {
+                    usageTime = pus.mUsageTime;
+                    launchCount = pus.mLaunchCount;
+                }
+                retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime,
+                        mLastResumeTimes.valueAt(i));
+            }
+            return retArr;
+        }
+    }
+    
+    static byte[] readFully(FileInputStream stream) throws java.io.IOException {
+        int pos = 0;
+        int avail = stream.available();
+        byte[] data = new byte[avail];
+        while (true) {
+            int amt = stream.read(data, pos, data.length-pos);
+            if (amt <= 0) {
+                return data;
+            }
+            pos += amt;
+            avail = stream.available();
+            if (avail > data.length-pos) {
+                byte[] newData = new byte[pos+avail];
+                System.arraycopy(data, 0, newData, 0, pos);
+                data = newData;
+            }
+        }
+    }
+    
+    private void collectDumpInfoFLOCK(PrintWriter pw, boolean isCompactOutput,
+            boolean deleteAfterPrint, HashSet<String> packages) {
+        List<String> fileList = getUsageStatsFileListFLOCK();
+        if (fileList == null) {
+            return;
+        }
+        Collections.sort(fileList);
+        for (String file : fileList) {
+            if (deleteAfterPrint && file.equalsIgnoreCase(mFileLeaf)) {
+                // In this mode we don't print the current day's stats, since
+                // they are incomplete.
+                continue;
+            }
+            File dFile = new File(mDir, file);
+            String dateStr = file.substring(FILE_PREFIX.length());
+            if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) {
+                // If the remainder does not start with a number, it is not a date,
+                // so we should ignore it for purposes here.
+                continue;
+            }
+            try {
+                Parcel in = getParcelForFile(dFile);
+                collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput,
+                        packages);
+                if (deleteAfterPrint) {
+                    // Delete old file after collecting info only for checkin requests
+                    dFile.delete();
+                }
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : " + file);
+                return;
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed with "+e+" when collecting dump info from file : "+file);
+            }      
+        }
+    }
+    
+    private void collectDumpInfoFromParcelFLOCK(Parcel in, PrintWriter pw,
+            String date, boolean isCompactOutput, HashSet<String> packages) {
+        StringBuilder sb = new StringBuilder(512);
+        if (isCompactOutput) {
+            sb.append("D:");
+            sb.append(CHECKIN_VERSION);
+            sb.append(',');
+        } else {
+            sb.append("Date: ");
+        }
+        
+        sb.append(date);
+        
+        int vers = in.readInt();
+        if (vers != VERSION) {
+            sb.append(" (old data version)");
+            pw.println(sb.toString());
+            return;
+        }
+        
+        pw.println(sb.toString());
+        int N = in.readInt();
+        
+        while (N > 0) {
+            N--;
+            String pkgName = in.readString();
+            if (pkgName == null) {
+                break;
+            }
+            sb.setLength(0);
+            PkgUsageStatsExtended pus = new PkgUsageStatsExtended(in);
+            if (packages != null && !packages.contains(pkgName)) {
+                // This package has not been requested -- don't print
+                // anything for it.
+            } else if (isCompactOutput) {
+                sb.append("P:");
+                sb.append(pkgName);
+                sb.append(',');
+                sb.append(pus.mLaunchCount);
+                sb.append(',');
+                sb.append(pus.mUsageTime);
+                sb.append('\n');
+                final int NLT = pus.mLaunchTimes.size();
+                for (int i=0; i<NLT; i++) {
+                    sb.append("A:");
+                    String activity = pus.mLaunchTimes.keyAt(i);
+                    sb.append(activity);
+                    TimeStats times = pus.mLaunchTimes.valueAt(i);
+                    sb.append(',');
+                    sb.append(times.count);
+                    for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
+                        sb.append(",");
+                        sb.append(times.times[j]);
+                    }
+                    sb.append('\n');
+                }
+                final int NFDT = pus.mFullyDrawnTimes.size();
+                for (int i=0; i<NFDT; i++) {
+                    sb.append("A:");
+                    String activity = pus.mFullyDrawnTimes.keyAt(i);
+                    sb.append(activity);
+                    TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
+                    for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) {
+                        sb.append(",");
+                        sb.append(times.times[j]);
+                    }
+                    sb.append('\n');
+                }
+
+            } else {
+                sb.append("  ");
+                sb.append(pkgName);
+                sb.append(": ");
+                sb.append(pus.mLaunchCount);
+                sb.append(" times, ");
+                sb.append(pus.mUsageTime);
+                sb.append(" ms");
+                sb.append('\n');
+                final int NLT = pus.mLaunchTimes.size();
+                for (int i=0; i<NLT; i++) {
+                    sb.append("    ");
+                    sb.append(pus.mLaunchTimes.keyAt(i));
+                    TimeStats times = pus.mLaunchTimes.valueAt(i);
+                    sb.append(": ");
+                    sb.append(times.count);
+                    sb.append(" starts");
+                    int lastBin = 0;
+                    for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
+                        if (times.times[j] != 0) {
+                            sb.append(", ");
+                            sb.append(lastBin);
+                            sb.append('-');
+                            sb.append(LAUNCH_TIME_BINS[j]);
+                            sb.append("ms=");
+                            sb.append(times.times[j]);
+                        }
+                        lastBin = LAUNCH_TIME_BINS[j];
+                    }
+                    if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
+                        sb.append(", ");
+                        sb.append(">=");
+                        sb.append(lastBin);
+                        sb.append("ms=");
+                        sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
+                    }
+                    sb.append('\n');
+                }
+                final int NFDT = pus.mFullyDrawnTimes.size();
+                for (int i=0; i<NFDT; i++) {
+                    sb.append("    ");
+                    sb.append(pus.mFullyDrawnTimes.keyAt(i));
+                    TimeStats times = pus.mFullyDrawnTimes.valueAt(i);
+                    sb.append(": fully drawn ");
+                    boolean needComma = false;
+                    int lastBin = 0;
+                    for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) {
+                        if (times.times[j] != 0) {
+                            if (needComma) {
+                                sb.append(", ");
+                            } else {
+                                needComma = true;
+                            }
+                            sb.append(lastBin);
+                            sb.append('-');
+                            sb.append(LAUNCH_TIME_BINS[j]);
+                            sb.append("ms=");
+                            sb.append(times.times[j]);
+                        }
+                        lastBin = LAUNCH_TIME_BINS[j];
+                    }
+                    if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) {
+                        if (needComma) {
+                            sb.append(", ");
+                        }
+                        sb.append(">=");
+                        sb.append(lastBin);
+                        sb.append("ms=");
+                        sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]);
+                    }
+                    sb.append('\n');
+                }
+            }
+            
+            pw.write(sb.toString());
+        }
+    }
+    
+    /**
+     * Searches array of arguments for the specified string
+     * @param args array of argument strings
+     * @param value value to search for
+     * @return true if the value is contained in the array
+     */
+    private static boolean scanArgs(String[] args, String value) {
+        if (args != null) {
+            for (String arg : args) {
+                if (value.equals(arg)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
+    /**
+     * Searches array of arguments for the specified string's data
+     * @param args array of argument strings
+     * @param value value to search for
+     * @return the string of data after the arg, or null if there is none
+     */
+    private static String scanArgsData(String[] args, String value) {
+        if (args != null) {
+            final int N = args.length;
+            for (int i=0; i<N; i++) {
+                if (value.equals(args[i])) {
+                    i++;
+                    return i < N ? args[i] : null;
+                }
+            }
+        }
+        return null;
+    }
+    
+    @Override
+    /*
+     * The data persisted to file is parsed and the stats are computed. 
+     */
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump UsageStats from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " without permission " + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        final boolean isCheckinRequest = scanArgs(args, "--checkin");
+        final boolean isCompactOutput = isCheckinRequest || scanArgs(args, "-c");
+        final boolean deleteAfterPrint = isCheckinRequest || scanArgs(args, "-d");
+        final String rawPackages = scanArgsData(args, "--packages");
+        
+        // Make sure the current stats are written to the file.  This
+        // doesn't need to be done if we are deleting files after printing,
+        // since it that case we won't print the current stats.
+        if (!deleteAfterPrint) {
+            writeStatsToFile(true, false);
+        }
+        
+        HashSet<String> packages = null;
+        if (rawPackages != null) {
+            if (!"*".equals(rawPackages)) {
+                // A * is a wildcard to show all packages.
+                String[] names = rawPackages.split(",");
+                for (String n : names) {
+                    if (packages == null) {
+                        packages = new HashSet<String>();
+                    }
+                    packages.add(n);
+                }
+            }
+        } else if (isCheckinRequest) {
+            // If checkin doesn't specify any packages, then we simply won't
+            // show anything.
+            Slog.w(TAG, "Checkin without packages");
+            return;
+        }
+        
+        synchronized (mFileLock) {
+            collectDumpInfoFLOCK(pw, isCompactOutput, deleteAfterPrint, packages);
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/am/UserStartedState.java b/services/core/java/com/android/server/am/UserStartedState.java
new file mode 100644
index 0000000..d3e73d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/UserStartedState.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import android.app.IStopUserCallback;
+import android.os.UserHandle;
+
+public final class UserStartedState {
+    // User is first coming up.
+    public final static int STATE_BOOTING = 0;
+    // User is in the normal running state.
+    public final static int STATE_RUNNING = 1;
+    // User is in the initial process of being stopped.
+    public final static int STATE_STOPPING = 2;
+    // User is in the final phase of stopping, sending Intent.ACTION_SHUTDOWN.
+    public final static int STATE_SHUTDOWN = 3;
+
+    public final UserHandle mHandle;
+    public final ArrayList<IStopUserCallback> mStopCallbacks
+            = new ArrayList<IStopUserCallback>();
+
+    public int mState = STATE_BOOTING;
+    public boolean switching;
+    public boolean initializing;
+
+    public UserStartedState(UserHandle handle, boolean initial) {
+        mHandle = handle;
+    }
+
+    void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mState=");
+        switch (mState) {
+            case STATE_BOOTING: pw.print("BOOTING"); break;
+            case STATE_RUNNING: pw.print("RUNNING"); break;
+            case STATE_STOPPING: pw.print("STOPPING"); break;
+            case STATE_SHUTDOWN: pw.print("SHUTDOWN"); break;
+            default: pw.print(mState); break; 
+        }
+        if (switching) pw.print(" SWITCHING");
+        if (initializing) pw.print(" INITIALIZING");
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/am/package.html b/services/core/java/com/android/server/am/package.html
new file mode 100644
index 0000000..c9f96a6
--- /dev/null
+++ b/services/core/java/com/android/server/am/package.html
@@ -0,0 +1,5 @@
+<body>
+
+{@hide}
+
+</body>
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
new file mode 100644
index 0000000..6aa596d
--- /dev/null
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.clipboard;
+
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.IClipboard;
+import android.content.IOnPrimaryClipChangedListener;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.HashSet;
+
+/**
+ * Implementation of the clipboard for copy and paste.
+ */
+public class ClipboardService extends IClipboard.Stub {
+
+    private static final String TAG = "ClipboardService";
+
+    private final Context mContext;
+    private final IActivityManager mAm;
+    private final PackageManager mPm;
+    private final AppOpsManager mAppOps;
+    private final IBinder mPermissionOwner;
+
+    private class ListenerInfo {
+        final int mUid;
+        final String mPackageName;
+        ListenerInfo(int uid, String packageName) {
+            mUid = uid;
+            mPackageName = packageName;
+        }
+    }
+
+    private class PerUserClipboard {
+        final int userId;
+
+        final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
+                = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
+
+        ClipData primaryClip;
+
+        final HashSet<String> activePermissionOwners
+                = new HashSet<String>();
+
+        PerUserClipboard(int userId) {
+            this.userId = userId;
+        }
+    }
+
+    private SparseArray<PerUserClipboard> mClipboards = new SparseArray<PerUserClipboard>();
+
+    /**
+     * Instantiates the clipboard.
+     */
+    public ClipboardService(Context context) {
+        mContext = context;
+        mAm = ActivityManagerNative.getDefault();
+        mPm = context.getPackageManager();
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        IBinder permOwner = null;
+        try {
+            permOwner = mAm.newUriPermissionOwner("clipboard");
+        } catch (RemoteException e) {
+            Slog.w("clipboard", "AM dead", e);
+        }
+        mPermissionOwner = permOwner;
+
+        // Remove the clipboard if a user is removed
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    removeClipboard(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
+                }
+            }
+        }, userFilter);
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf("clipboard", "Exception: ", e);
+            }
+            throw e;
+        }
+        
+    }
+
+    private PerUserClipboard getClipboard() {
+        return getClipboard(UserHandle.getCallingUserId());
+    }
+
+    private PerUserClipboard getClipboard(int userId) {
+        synchronized (mClipboards) {
+            PerUserClipboard puc = mClipboards.get(userId);
+            if (puc == null) {
+                puc = new PerUserClipboard(userId);
+                mClipboards.put(userId, puc);
+            }
+            return puc;
+        }
+    }
+
+    private void removeClipboard(int userId) {
+        synchronized (mClipboards) {
+            mClipboards.remove(userId);
+        }
+    }
+
+    public void setPrimaryClip(ClipData clip, String callingPackage) {
+        synchronized (this) {
+            if (clip != null && clip.getItemCount() <= 0) {
+                throw new IllegalArgumentException("No items");
+            }
+            final int callingUid = Binder.getCallingUid();
+            if (mAppOps.noteOp(AppOpsManager.OP_WRITE_CLIPBOARD, callingUid,
+                    callingPackage) != AppOpsManager.MODE_ALLOWED) {
+                return;
+            }
+            checkDataOwnerLocked(clip, callingUid);
+            clearActiveOwnersLocked();
+            PerUserClipboard clipboard = getClipboard();
+            clipboard.primaryClip = clip;
+            final long ident = Binder.clearCallingIdentity();
+            final int n = clipboard.primaryClipListeners.beginBroadcast();
+            try {
+                for (int i = 0; i < n; i++) {
+                    try {
+                        ListenerInfo li = (ListenerInfo)
+                                clipboard.primaryClipListeners.getBroadcastCookie(i);
+                        if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid,
+                                li.mPackageName) == AppOpsManager.MODE_ALLOWED) {
+                            clipboard.primaryClipListeners.getBroadcastItem(i)
+                                    .dispatchPrimaryClipChanged();
+                        }
+                    } catch (RemoteException e) {
+                        // The RemoteCallbackList will take care of removing
+                        // the dead object for us.
+                    }
+                }
+            } finally {
+                clipboard.primaryClipListeners.finishBroadcast();
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+    
+    public ClipData getPrimaryClip(String pkg) {
+        synchronized (this) {
+            if (mAppOps.noteOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+                    pkg) != AppOpsManager.MODE_ALLOWED) {
+                return null;
+            }
+            addActiveOwnerLocked(Binder.getCallingUid(), pkg);
+            return getClipboard().primaryClip;
+        }
+    }
+
+    public ClipDescription getPrimaryClipDescription(String callingPackage) {
+        synchronized (this) {
+            if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+                    callingPackage) != AppOpsManager.MODE_ALLOWED) {
+                return null;
+            }
+            PerUserClipboard clipboard = getClipboard();
+            return clipboard.primaryClip != null ? clipboard.primaryClip.getDescription() : null;
+        }
+    }
+
+    public boolean hasPrimaryClip(String callingPackage) {
+        synchronized (this) {
+            if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+                    callingPackage) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+            return getClipboard().primaryClip != null;
+        }
+    }
+
+    public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
+            String callingPackage) {
+        synchronized (this) {
+            getClipboard().primaryClipListeners.register(listener,
+                    new ListenerInfo(Binder.getCallingUid(), callingPackage));
+        }
+    }
+
+    public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
+        synchronized (this) {
+            getClipboard().primaryClipListeners.unregister(listener);
+        }
+    }
+
+    public boolean hasClipboardText(String callingPackage) {
+        synchronized (this) {
+            if (mAppOps.checkOp(AppOpsManager.OP_READ_CLIPBOARD, Binder.getCallingUid(),
+                    callingPackage) != AppOpsManager.MODE_ALLOWED) {
+                return false;
+            }
+            PerUserClipboard clipboard = getClipboard();
+            if (clipboard.primaryClip != null) {
+                CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
+                return text != null && text.length() > 0;
+            }
+            return false;
+        }
+    }
+
+    private final void checkUriOwnerLocked(Uri uri, int uid) {
+        if (!"content".equals(uri.getScheme())) {
+            return;
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            // This will throw SecurityException for us.
+            mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+        if (item.getUri() != null) {
+            checkUriOwnerLocked(item.getUri(), uid);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            checkUriOwnerLocked(intent.getData(), uid);
+        }
+    }
+
+    private final void checkDataOwnerLocked(ClipData data, int uid) {
+        final int N = data.getItemCount();
+        for (int i=0; i<N; i++) {
+            checkItemOwnerLocked(data.getItemAt(i), uid);
+        }
+    }
+
+    private final void grantUriLocked(Uri uri, String pkg) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void grantItemLocked(ClipData.Item item, String pkg) {
+        if (item.getUri() != null) {
+            grantUriLocked(item.getUri(), pkg);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            grantUriLocked(intent.getData(), pkg);
+        }
+    }
+
+    private final void addActiveOwnerLocked(int uid, String pkg) {
+        final IPackageManager pm = AppGlobals.getPackageManager();
+        final int targetUserHandle = UserHandle.getCallingUserId();
+        final long oldIdentity = Binder.clearCallingIdentity();
+        try {
+            PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
+            if (pi == null) {
+                throw new IllegalArgumentException("Unknown package " + pkg);
+            }
+            if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
+                throw new SecurityException("Calling uid " + uid
+                        + " does not own package " + pkg);
+            }
+        } catch (RemoteException e) {
+            // Can't happen; the package manager is in the same process
+        } finally {
+            Binder.restoreCallingIdentity(oldIdentity);
+        }
+        PerUserClipboard clipboard = getClipboard();
+        if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
+            final int N = clipboard.primaryClip.getItemCount();
+            for (int i=0; i<N; i++) {
+                grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg);
+            }
+            clipboard.activePermissionOwners.add(pkg);
+        }
+    }
+
+    private final void revokeUriLocked(Uri uri) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void revokeItemLocked(ClipData.Item item) {
+        if (item.getUri() != null) {
+            revokeUriLocked(item.getUri());
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            revokeUriLocked(intent.getData());
+        }
+    }
+
+    private final void clearActiveOwnersLocked() {
+        PerUserClipboard clipboard = getClipboard();
+        clipboard.activePermissionOwners.clear();
+        if (clipboard.primaryClip == null) {
+            return;
+        }
+        final int N = clipboard.primaryClip.getItemCount();
+        for (int i=0; i<N; i++) {
+            revokeItemLocked(clipboard.primaryClip.getItemAt(i));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
new file mode 100644
index 0000000..227ab23
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -0,0 +1,154 @@
+/*
+ * 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.connectivity;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.RemoteException;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.server.am.BatteryStatsService;
+
+public class DataConnectionStats extends BroadcastReceiver {
+    private static final String TAG = "DataConnectionStats";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final IBatteryStats mBatteryStats;
+
+    private IccCardConstants.State mSimState = IccCardConstants.State.READY;
+    private SignalStrength mSignalStrength;
+    private ServiceState mServiceState;
+    private int mDataState = TelephonyManager.DATA_DISCONNECTED;
+
+    public DataConnectionStats(Context context) {
+        mContext = context;
+        mBatteryStats = BatteryStatsService.getService();
+    }
+
+    public void startMonitoring() {
+        TelephonyManager phone =
+                (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        phone.listen(mPhoneStateListener,
+                PhoneStateListener.LISTEN_SERVICE_STATE
+              | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS
+              | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE
+              | PhoneStateListener.LISTEN_DATA_ACTIVITY);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
+        mContext.registerReceiver(this, filter);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String action = intent.getAction();
+        if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
+            updateSimState(intent);
+            notePhoneDataConnectionState();
+        } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) ||
+                action.equals(ConnectivityManager.INET_CONDITION_ACTION)) {
+            notePhoneDataConnectionState();
+       }
+    }
+
+    private void notePhoneDataConnectionState() {
+        if (mServiceState == null) {
+            return;
+        }
+        boolean simReadyOrUnknown = mSimState == IccCardConstants.State.READY
+                || mSimState == IccCardConstants.State.UNKNOWN;
+        boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
+                && hasService()
+                && mDataState == TelephonyManager.DATA_CONNECTED;
+        int networkType = mServiceState.getDataNetworkType();
+        if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
+                networkType, visible ? "" : "not "));
+        try {
+            mBatteryStats.notePhoneDataConnectionState(networkType, visible);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error noting data connection state", e);
+        }
+    }
+
+    private final void updateSimState(Intent intent) {
+        String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE);
+        if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
+            mSimState = IccCardConstants.State.ABSENT;
+        } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
+            mSimState = IccCardConstants.State.READY;
+        } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
+            final String lockedReason =
+                    intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON);
+            if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) {
+                mSimState = IccCardConstants.State.PIN_REQUIRED;
+            } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
+                mSimState = IccCardConstants.State.PUK_REQUIRED;
+            } else {
+                mSimState = IccCardConstants.State.NETWORK_LOCKED;
+            }
+        } else {
+            mSimState = IccCardConstants.State.UNKNOWN;
+        }
+    }
+
+    private boolean isCdma() {
+        return mSignalStrength != null && !mSignalStrength.isGsm();
+    }
+
+    private boolean hasService() {
+        return mServiceState != null
+                && mServiceState.getState() != ServiceState.STATE_OUT_OF_SERVICE
+                && mServiceState.getState() != ServiceState.STATE_POWER_OFF;
+    }
+
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            mSignalStrength = signalStrength;
+        }
+
+        @Override
+        public void onServiceStateChanged(ServiceState state) {
+            mServiceState = state;
+            notePhoneDataConnectionState();
+        }
+
+        @Override
+        public void onDataConnectionStateChanged(int state, int networkType) {
+            mDataState = state;
+            notePhoneDataConnectionState();
+        }
+
+        @Override
+        public void onDataActivity(int direction) {
+            notePhoneDataConnectionState();
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
new file mode 100644
index 0000000..a15d678
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+
+import java.net.Inet4Address;
+
+import android.content.Context;
+import android.net.IConnectivityManager;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.Handler;
+import android.os.Message;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.net.BaseNetworkObserver;
+
+/**
+ * @hide
+ *
+ * Class to manage a 464xlat CLAT daemon.
+ */
+public class Nat464Xlat extends BaseNetworkObserver {
+    private Context mContext;
+    private INetworkManagementService mNMService;
+    private IConnectivityManager mConnService;
+    private NetworkStateTracker mTracker;
+    private Handler mHandler;
+
+    // Whether we started clatd and expect it to be running.
+    private boolean mIsStarted;
+    // Whether the clatd interface exists (i.e., clatd is running).
+    private boolean mIsRunning;
+    // The LinkProperties of the clat interface.
+    private LinkProperties mLP;
+
+    // This must match the interface name in clatd.conf.
+    private static final String CLAT_INTERFACE_NAME = "clat4";
+
+    private static final String TAG = "Nat464Xlat";
+
+    public Nat464Xlat(Context context, INetworkManagementService nmService,
+                      IConnectivityManager connService, Handler handler) {
+        mContext = context;
+        mNMService = nmService;
+        mConnService = connService;
+        mHandler = handler;
+
+        mIsStarted = false;
+        mIsRunning = false;
+        mLP = new LinkProperties();
+    }
+
+    /**
+     * Determines whether an interface requires clat.
+     * @param netType the network type (one of the
+     *   android.net.ConnectivityManager.TYPE_* constants)
+     * @param tracker the NetworkStateTracker corresponding to the network type.
+     * @return true if the interface requires clat, false otherwise.
+     */
+    public boolean requiresClat(int netType, NetworkStateTracker tracker) {
+        LinkProperties lp = tracker.getLinkProperties();
+        // Only support clat on mobile for now.
+        Slog.d(TAG, "requiresClat: netType=" + netType + ", hasIPv4Address=" +
+               lp.hasIPv4Address());
+        return netType == TYPE_MOBILE && !lp.hasIPv4Address();
+    }
+
+    public static boolean isRunningClat(LinkProperties lp) {
+      return lp != null && lp.getAllInterfaceNames().contains(CLAT_INTERFACE_NAME);
+    }
+
+    /**
+     * Starts the clat daemon.
+     * @param lp The link properties of the interface to start clatd on.
+     */
+    public void startClat(NetworkStateTracker tracker) {
+        if (mIsStarted) {
+            Slog.e(TAG, "startClat: already started");
+            return;
+        }
+        mTracker = tracker;
+        LinkProperties lp = mTracker.getLinkProperties();
+        String iface = lp.getInterfaceName();
+        Slog.i(TAG, "Starting clatd on " + iface + ", lp=" + lp);
+        try {
+            mNMService.startClatd(iface);
+        } catch(RemoteException e) {
+            Slog.e(TAG, "Error starting clat daemon: " + e);
+        }
+        mIsStarted = true;
+    }
+
+    /**
+     * Stops the clat daemon.
+     */
+    public void stopClat() {
+        if (mIsStarted) {
+            Slog.i(TAG, "Stopping clatd");
+            try {
+                mNMService.stopClatd();
+            } catch(RemoteException e) {
+                Slog.e(TAG, "Error stopping clat daemon: " + e);
+            }
+            mIsStarted = false;
+            mIsRunning = false;
+            mTracker = null;
+            mLP.clear();
+        } else {
+            Slog.e(TAG, "stopClat: already stopped");
+        }
+    }
+
+    public boolean isStarted() {
+        return mIsStarted;
+    }
+
+    public boolean isRunning() {
+        return mIsRunning;
+    }
+
+    @Override
+    public void interfaceAdded(String iface) {
+        if (iface.equals(CLAT_INTERFACE_NAME)) {
+            Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+                   " added, mIsRunning = " + mIsRunning + " -> true");
+            mIsRunning = true;
+
+            // Create the LinkProperties for the clat interface by fetching the
+            // IPv4 address for the interface and adding an IPv4 default route,
+            // then stack the LinkProperties on top of the link it's running on.
+            // Although the clat interface is a point-to-point tunnel, we don't
+            // point the route directly at the interface because some apps don't
+            // understand routes without gateways (see, e.g., http://b/9597256
+            // http://b/9597516). Instead, set the next hop of the route to the
+            // clat IPv4 address itself (for those apps, it doesn't matter what
+            // the IP of the gateway is, only that there is one).
+            try {
+                InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
+                LinkAddress clatAddress = config.getLinkAddress();
+                mLP.clear();
+                mLP.setInterfaceName(iface);
+                RouteInfo ipv4Default = new RouteInfo(new LinkAddress(Inet4Address.ANY, 0),
+                                                      clatAddress.getAddress(), iface);
+                mLP.addRoute(ipv4Default);
+                mLP.addLinkAddress(clatAddress);
+                mTracker.addStackedLink(mLP);
+                Slog.i(TAG, "Adding stacked link. tracker LP: " +
+                       mTracker.getLinkProperties());
+            } catch(RemoteException e) {
+                Slog.e(TAG, "Error getting link properties: " + e);
+            }
+
+            // Inform ConnectivityService that things have changed.
+            Message msg = mHandler.obtainMessage(
+                NetworkStateTracker.EVENT_CONFIGURATION_CHANGED,
+                mTracker.getNetworkInfo());
+            Slog.i(TAG, "sending message to ConnectivityService: " + msg);
+            msg.sendToTarget();
+        }
+    }
+
+    @Override
+    public void interfaceRemoved(String iface) {
+        if (iface == CLAT_INTERFACE_NAME) {
+            if (mIsRunning) {
+                NetworkUtils.resetConnections(
+                    CLAT_INTERFACE_NAME,
+                    NetworkUtils.RESET_IPV4_ADDRESSES);
+            }
+            Slog.i(TAG, "interface " + CLAT_INTERFACE_NAME +
+                   " removed, mIsRunning = " + mIsRunning + " -> false");
+            mIsRunning = false;
+            mTracker.removeStackedLink(mLP);
+            mLP.clear();
+            Slog.i(TAG, "mLP = " + mLP);
+        }
+    }
+};
diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java
new file mode 100644
index 0000000..7786fe6
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/PacManager.java
@@ -0,0 +1,381 @@
+/**
+ * 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.connectivity;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.net.Proxy;
+import android.net.ProxyProperties;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.net.IProxyCallback;
+import com.android.net.IProxyPortListener;
+import com.android.net.IProxyService;
+import com.android.server.IoThread;
+
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * @hide
+ */
+public class PacManager {
+    public static final String PAC_PACKAGE = "com.android.pacprocessor";
+    public static final String PAC_SERVICE = "com.android.pacprocessor.PacService";
+    public static final String PAC_SERVICE_NAME = "com.android.net.IProxyService";
+
+    public static final String PROXY_PACKAGE = "com.android.proxyhandler";
+    public static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService";
+
+    private static final String TAG = "PacManager";
+
+    private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH";
+
+    private static final String DEFAULT_DELAYS = "8 32 120 14400 43200";
+    private static final int DELAY_1 = 0;
+    private static final int DELAY_4 = 3;
+    private static final int DELAY_LONG = 4;
+
+    /** Keep these values up-to-date with ProxyService.java */
+    public static final String KEY_PROXY = "keyProxy";
+    private String mCurrentPac;
+    @GuardedBy("mProxyLock")
+    private String mPacUrl;
+
+    private AlarmManager mAlarmManager;
+    @GuardedBy("mProxyLock")
+    private IProxyService mProxyService;
+    private PendingIntent mPacRefreshIntent;
+    private ServiceConnection mConnection;
+    private ServiceConnection mProxyConnection;
+    private Context mContext;
+
+    private int mCurrentDelay;
+    private int mLastPort;
+
+    private boolean mHasSentBroadcast;
+    private boolean mHasDownloaded;
+
+    private Handler mConnectivityHandler;
+    private int mProxyMessage;
+
+    /**
+     * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac.
+     */
+    private final Object mProxyLock = new Object();
+
+    private Runnable mPacDownloader = new Runnable() {
+        @Override
+        public void run() {
+            String file;
+            synchronized (mProxyLock) {
+                if (mPacUrl == null) return;
+                try {
+                    file = get(mPacUrl);
+                } catch (IOException ioe) {
+                    file = null;
+                    Log.w(TAG, "Failed to load PAC file: " + ioe);
+                }
+            }
+            if (file != null) {
+                synchronized (mProxyLock) {
+                    if (!file.equals(mCurrentPac)) {
+                        setCurrentProxyScript(file);
+                    }
+                }
+                mHasDownloaded = true;
+                sendProxyIfNeeded();
+                longSchedule();
+            } else {
+                reschedule();
+            }
+        }
+    };
+
+    class PacRefreshIntentReceiver extends BroadcastReceiver {
+        public void onReceive(Context context, Intent intent) {
+            IoThread.getHandler().post(mPacDownloader);
+        }
+    }
+
+    public PacManager(Context context, Handler handler, int proxyMessage) {
+        mContext = context;
+        mLastPort = -1;
+
+        mPacRefreshIntent = PendingIntent.getBroadcast(
+                context, 0, new Intent(ACTION_PAC_REFRESH), 0);
+        context.registerReceiver(new PacRefreshIntentReceiver(),
+                new IntentFilter(ACTION_PAC_REFRESH));
+        mConnectivityHandler = handler;
+        mProxyMessage = proxyMessage;
+    }
+
+    private AlarmManager getAlarmManager() {
+        if (mAlarmManager == null) {
+            mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        }
+        return mAlarmManager;
+    }
+
+    /**
+     * Updates the PAC Manager with current Proxy information. This is called by
+     * the ConnectivityService directly before a broadcast takes place to allow
+     * the PacManager to indicate that the broadcast should not be sent and the
+     * PacManager will trigger a new broadcast when it is ready.
+     *
+     * @param proxy Proxy information that is about to be broadcast.
+     * @return Returns true when the broadcast should not be sent
+     */
+    public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) {
+        if (!TextUtils.isEmpty(proxy.getPacFileUrl())) {
+            if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) {
+                // Allow to send broadcast, nothing to do.
+                return false;
+            }
+            synchronized (mProxyLock) {
+                mPacUrl = proxy.getPacFileUrl();
+            }
+            mCurrentDelay = DELAY_1;
+            mHasSentBroadcast = false;
+            mHasDownloaded = false;
+            getAlarmManager().cancel(mPacRefreshIntent);
+            bind();
+            return true;
+        } else {
+            getAlarmManager().cancel(mPacRefreshIntent);
+            synchronized (mProxyLock) {
+                mPacUrl = null;
+                mCurrentPac = null;
+                if (mProxyService != null) {
+                    try {
+                        mProxyService.stopPacSystem();
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to stop PAC service", e);
+                    } finally {
+                        unbind();
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Does a post and reports back the status code.
+     *
+     * @throws IOException
+     */
+    private static String get(String urlString) throws IOException {
+        URL url = new URL(urlString);
+        URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY);
+        return new String(Streams.readFully(urlConnection.getInputStream()));
+    }
+
+    private int getNextDelay(int currentDelay) {
+       if (++currentDelay > DELAY_4) {
+           return DELAY_4;
+       }
+       return currentDelay;
+    }
+
+    private void longSchedule() {
+        mCurrentDelay = DELAY_1;
+        setDownloadIn(DELAY_LONG);
+    }
+
+    private void reschedule() {
+        mCurrentDelay = getNextDelay(mCurrentDelay);
+        setDownloadIn(mCurrentDelay);
+    }
+
+    private String getPacChangeDelay() {
+        final ContentResolver cr = mContext.getContentResolver();
+
+        /** Check system properties for the default value then use secure settings value, if any. */
+        String defaultDelay = SystemProperties.get(
+                "conn." + Settings.Global.PAC_CHANGE_DELAY,
+                DEFAULT_DELAYS);
+        String val = Settings.Global.getString(cr, Settings.Global.PAC_CHANGE_DELAY);
+        return (val == null) ? defaultDelay : val;
+    }
+
+    private long getDownloadDelay(int delayIndex) {
+        String[] list = getPacChangeDelay().split(" ");
+        if (delayIndex < list.length) {
+            return Long.parseLong(list[delayIndex]);
+        }
+        return 0;
+    }
+
+    private void setDownloadIn(int delayIndex) {
+        long delay = getDownloadDelay(delayIndex);
+        long timeTillTrigger = 1000 * delay + SystemClock.elapsedRealtime();
+        getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent);
+    }
+
+    private boolean setCurrentProxyScript(String script) {
+        if (mProxyService == null) {
+            Log.e(TAG, "setCurrentProxyScript: no proxy service");
+            return false;
+        }
+        try {
+            mProxyService.setPacFile(script);
+            mCurrentPac = script;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to set PAC file", e);
+        }
+        return true;
+    }
+
+    private void bind() {
+        if (mContext == null) {
+            Log.e(TAG, "No context for binding");
+            return;
+        }
+        Intent intent = new Intent();
+        intent.setClassName(PAC_PACKAGE, PAC_SERVICE);
+        // Already bound no need to bind again.
+        if ((mProxyConnection != null) && (mConnection != null)) {
+            if (mLastPort != -1) {
+                sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+            } else {
+                Log.e(TAG, "Received invalid port from Local Proxy,"
+                        + " PAC will not be operational");
+            }
+            return;
+        }
+        mConnection = new ServiceConnection() {
+            @Override
+            public void onServiceDisconnected(ComponentName component) {
+                synchronized (mProxyLock) {
+                    mProxyService = null;
+                }
+            }
+
+            @Override
+            public void onServiceConnected(ComponentName component, IBinder binder) {
+                synchronized (mProxyLock) {
+                    try {
+                        Log.d(TAG, "Adding service " + PAC_SERVICE_NAME + " "
+                                + binder.getInterfaceDescriptor());
+                    } catch (RemoteException e1) {
+                        Log.e(TAG, "Remote Exception", e1);
+                    }
+                    ServiceManager.addService(PAC_SERVICE_NAME, binder);
+                    mProxyService = IProxyService.Stub.asInterface(binder);
+                    if (mProxyService == null) {
+                        Log.e(TAG, "No proxy service");
+                    } else {
+                        try {
+                            mProxyService.startPacSystem();
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e);
+                        }
+                        IoThread.getHandler().post(mPacDownloader);
+                    }
+                }
+            }
+        };
+        mContext.bindService(intent, mConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+
+        intent = new Intent();
+        intent.setClassName(PROXY_PACKAGE, PROXY_SERVICE);
+        mProxyConnection = new ServiceConnection() {
+            @Override
+            public void onServiceDisconnected(ComponentName component) {
+            }
+
+            @Override
+            public void onServiceConnected(ComponentName component, IBinder binder) {
+                IProxyCallback callbackService = IProxyCallback.Stub.asInterface(binder);
+                if (callbackService != null) {
+                    try {
+                        callbackService.getProxyPort(new IProxyPortListener.Stub() {
+                            @Override
+                            public void setProxyPort(int port) throws RemoteException {
+                                if (mLastPort != -1) {
+                                    // Always need to send if port changed
+                                    mHasSentBroadcast = false;
+                                }
+                                mLastPort = port;
+                                if (port != -1) {
+                                    Log.d(TAG, "Local proxy is bound on " + port);
+                                    sendProxyIfNeeded();
+                                } else {
+                                    Log.e(TAG, "Received invalid port from Local Proxy,"
+                                            + " PAC will not be operational");
+                                }
+                            }
+                        });
+                    } catch (RemoteException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        };
+        mContext.bindService(intent, mProxyConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE);
+    }
+
+    private void unbind() {
+        if (mConnection != null) {
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+        if (mProxyConnection != null) {
+            mContext.unbindService(mProxyConnection);
+            mProxyConnection = null;
+        }
+        mProxyService = null;
+        mLastPort = -1;
+    }
+
+    private void sendPacBroadcast(ProxyProperties proxy) {
+        mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy));
+    }
+
+    private synchronized void sendProxyIfNeeded() {
+        if (!mHasDownloaded || (mLastPort == -1)) {
+            return;
+        }
+        if (!mHasSentBroadcast) {
+            sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort));
+            mHasSentBroadcast = true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
new file mode 100644
index 0000000..adf1dfc
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -0,0 +1,1590 @@
+/*
+ * Copyright (C) 2010 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.connectivity;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkInfo;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.Binder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.IoThread;
+import com.android.server.net.BaseNetworkObserver;
+import com.google.android.collect.Lists;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @hide
+ *
+ * Timeout
+ *
+ * TODO - look for parent classes and code sharing
+ */
+public class Tethering extends BaseNetworkObserver {
+
+    private Context mContext;
+    private final static String TAG = "Tethering";
+    private final static boolean DBG = true;
+    private final static boolean VDBG = false;
+
+    // TODO - remove both of these - should be part of interface inspection/selection stuff
+    private String[] mTetherableUsbRegexs;
+    private String[] mTetherableWifiRegexs;
+    private String[] mTetherableBluetoothRegexs;
+    private Collection<Integer> mUpstreamIfaceTypes;
+
+    // used to synchronize public access to members
+    private Object mPublicSync;
+
+    private static final Integer MOBILE_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE);
+    private static final Integer HIPRI_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_HIPRI);
+    private static final Integer DUN_TYPE = new Integer(ConnectivityManager.TYPE_MOBILE_DUN);
+
+    // if we have to connect to mobile, what APN type should we use?  Calculated by examining the
+    // upstream type list and the DUN_REQUIRED secure-setting
+    private int mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_NONE;
+
+    private final INetworkManagementService mNMService;
+    private final INetworkStatsService mStatsService;
+    private final IConnectivityManager mConnService;
+    private Looper mLooper;
+
+    private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
+
+    private BroadcastReceiver mStateReceiver;
+
+    private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
+    private static final int USB_PREFIX_LENGTH        = 24;
+
+    // USB is  192.168.42.1 and 255.255.255.0
+    // Wifi is 192.168.43.1 and 255.255.255.0
+    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+    // with 255.255.255.0
+
+    private String[] mDhcpRange;
+    private static final String[] DHCP_DEFAULT_RANGE = {
+        "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+        "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+        "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+        "192.168.48.2", "192.168.48.254",
+    };
+
+    private String[] mDefaultDnsServers;
+    private static final String DNS_DEFAULT_SERVER1 = "8.8.8.8";
+    private static final String DNS_DEFAULT_SERVER2 = "8.8.4.4";
+
+    private StateMachine mTetherMasterSM;
+
+    private Notification mTetheredNotification;
+
+    private boolean mRndisEnabled;       // track the RNDIS function enabled state
+    private boolean mUsbTetherRequested; // true if USB tethering should be started
+                                         // when RNDIS is enabled
+
+    public Tethering(Context context, INetworkManagementService nmService,
+            INetworkStatsService statsService, IConnectivityManager connService, Looper looper) {
+        mContext = context;
+        mNMService = nmService;
+        mStatsService = statsService;
+        mConnService = connService;
+        mLooper = looper;
+
+        mPublicSync = new Object();
+
+        mIfaces = new HashMap<String, TetherInterfaceSM>();
+
+        // make our own thread so we don't anr the system
+        mLooper = IoThread.get().getLooper();
+        mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper);
+        mTetherMasterSM.start();
+
+        mStateReceiver = new StateReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UsbManager.ACTION_USB_STATE);
+        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
+        mContext.registerReceiver(mStateReceiver, filter);
+
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_MEDIA_SHARED);
+        filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
+        filter.addDataScheme("file");
+        mContext.registerReceiver(mStateReceiver, filter);
+
+        mDhcpRange = context.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_dhcp_range);
+        if ((mDhcpRange.length == 0) || (mDhcpRange.length % 2 ==1)) {
+            mDhcpRange = DHCP_DEFAULT_RANGE;
+        }
+
+        // load device config info
+        updateConfiguration();
+
+        // TODO - remove and rely on real notifications of the current iface
+        mDefaultDnsServers = new String[2];
+        mDefaultDnsServers[0] = DNS_DEFAULT_SERVER1;
+        mDefaultDnsServers[1] = DNS_DEFAULT_SERVER2;
+    }
+
+    void updateConfiguration() {
+        String[] tetherableUsbRegexs = mContext.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_usb_regexs);
+        String[] tetherableWifiRegexs = mContext.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_wifi_regexs);
+        String[] tetherableBluetoothRegexs = mContext.getResources().getStringArray(
+                com.android.internal.R.array.config_tether_bluetooth_regexs);
+
+        int ifaceTypes[] = mContext.getResources().getIntArray(
+                com.android.internal.R.array.config_tether_upstream_types);
+        Collection<Integer> upstreamIfaceTypes = new ArrayList();
+        for (int i : ifaceTypes) {
+            upstreamIfaceTypes.add(new Integer(i));
+        }
+
+        synchronized (mPublicSync) {
+            mTetherableUsbRegexs = tetherableUsbRegexs;
+            mTetherableWifiRegexs = tetherableWifiRegexs;
+            mTetherableBluetoothRegexs = tetherableBluetoothRegexs;
+            mUpstreamIfaceTypes = upstreamIfaceTypes;
+        }
+
+        // check if the upstream type list needs to be modified due to secure-settings
+        checkDunRequired();
+    }
+
+    public void interfaceStatusChanged(String iface, boolean up) {
+        if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
+        boolean found = false;
+        boolean usb = false;
+        synchronized (mPublicSync) {
+            if (isWifi(iface)) {
+                found = true;
+            } else if (isUsb(iface)) {
+                found = true;
+                usb = true;
+            } else if (isBluetooth(iface)) {
+                found = true;
+            }
+            if (found == false) return;
+
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (up) {
+                if (sm == null) {
+                    sm = new TetherInterfaceSM(iface, mLooper, usb);
+                    mIfaces.put(iface, sm);
+                    sm.start();
+                }
+            } else {
+                if (isUsb(iface)) {
+                    // ignore usb0 down after enabling RNDIS
+                    // we will handle disconnect in interfaceRemoved instead
+                    if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
+                } else if (sm != null) {
+                    sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
+                    mIfaces.remove(iface);
+                }
+            }
+        }
+    }
+
+    public void interfaceLinkStateChanged(String iface, boolean up) {
+        if (VDBG) Log.d(TAG, "interfaceLinkStateChanged " + iface + ", " + up);
+        interfaceStatusChanged(iface, up);
+    }
+
+    private boolean isUsb(String iface) {
+        synchronized (mPublicSync) {
+            for (String regex : mTetherableUsbRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+    }
+
+    public boolean isWifi(String iface) {
+        synchronized (mPublicSync) {
+            for (String regex : mTetherableWifiRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+    }
+
+    public boolean isBluetooth(String iface) {
+        synchronized (mPublicSync) {
+            for (String regex : mTetherableBluetoothRegexs) {
+                if (iface.matches(regex)) return true;
+            }
+            return false;
+        }
+    }
+
+    public void interfaceAdded(String iface) {
+        if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
+        boolean found = false;
+        boolean usb = false;
+        synchronized (mPublicSync) {
+            if (isWifi(iface)) {
+                found = true;
+            }
+            if (isUsb(iface)) {
+                found = true;
+                usb = true;
+            }
+            if (isBluetooth(iface)) {
+                found = true;
+            }
+            if (found == false) {
+                if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
+                return;
+            }
+
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (sm != null) {
+                if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
+                return;
+            }
+            sm = new TetherInterfaceSM(iface, mLooper, usb);
+            mIfaces.put(iface, sm);
+            sm.start();
+        }
+    }
+
+    public void interfaceRemoved(String iface) {
+        if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
+        synchronized (mPublicSync) {
+            TetherInterfaceSM sm = mIfaces.get(iface);
+            if (sm == null) {
+                if (VDBG) {
+                    Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
+                }
+                return;
+            }
+            sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
+            mIfaces.remove(iface);
+        }
+    }
+
+    public int tether(String iface) {
+        if (DBG) Log.d(TAG, "Tethering " + iface);
+        TetherInterfaceSM sm = null;
+        synchronized (mPublicSync) {
+            sm = mIfaces.get(iface);
+        }
+        if (sm == null) {
+            Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
+            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+        }
+        if (!sm.isAvailable() && !sm.isErrored()) {
+            Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
+            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+        }
+        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED);
+        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+    }
+
+    public int untether(String iface) {
+        if (DBG) Log.d(TAG, "Untethering " + iface);
+        TetherInterfaceSM sm = null;
+        synchronized (mPublicSync) {
+            sm = mIfaces.get(iface);
+        }
+        if (sm == null) {
+            Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
+            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+        }
+        if (sm.isErrored()) {
+            Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
+            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+        }
+        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
+        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+    }
+
+    public int getLastTetherError(String iface) {
+        TetherInterfaceSM sm = null;
+        synchronized (mPublicSync) {
+            sm = mIfaces.get(iface);
+            if (sm == null) {
+                Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
+                        ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+            }
+            return sm.getLastError();
+        }
+    }
+
+    // TODO - move all private methods used only by the state machine into the state machine
+    // to clarify what needs synchronized protection.
+    private void sendTetherStateChangedBroadcast() {
+        try {
+            if (!mConnService.isTetheringSupported()) return;
+        } catch (RemoteException e) {
+            return;
+        }
+
+        ArrayList<String> availableList = new ArrayList<String>();
+        ArrayList<String> activeList = new ArrayList<String>();
+        ArrayList<String> erroredList = new ArrayList<String>();
+
+        boolean wifiTethered = false;
+        boolean usbTethered = false;
+        boolean bluetoothTethered = false;
+
+        synchronized (mPublicSync) {
+            Set ifaces = mIfaces.keySet();
+            for (Object iface : ifaces) {
+                TetherInterfaceSM sm = mIfaces.get(iface);
+                if (sm != null) {
+                    if (sm.isErrored()) {
+                        erroredList.add((String)iface);
+                    } else if (sm.isAvailable()) {
+                        availableList.add((String)iface);
+                    } else if (sm.isTethered()) {
+                        if (isUsb((String)iface)) {
+                            usbTethered = true;
+                        } else if (isWifi((String)iface)) {
+                            wifiTethered = true;
+                      } else if (isBluetooth((String)iface)) {
+                            bluetoothTethered = true;
+                        }
+                        activeList.add((String)iface);
+                    }
+                }
+            }
+        }
+        Intent broadcast = new Intent(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+        broadcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING |
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_AVAILABLE_TETHER,
+                availableList);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER, activeList);
+        broadcast.putStringArrayListExtra(ConnectivityManager.EXTRA_ERRORED_TETHER,
+                erroredList);
+        mContext.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
+        if (DBG) {
+            Log.d(TAG, "sendTetherStateChangedBroadcast " + availableList.size() + ", " +
+                    activeList.size() + ", " + erroredList.size());
+        }
+
+        if (usbTethered) {
+            if (wifiTethered || bluetoothTethered) {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+            } else {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_usb);
+            }
+        } else if (wifiTethered) {
+            if (bluetoothTethered) {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_general);
+            } else {
+                showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_wifi);
+            }
+        } else if (bluetoothTethered) {
+            showTetheredNotification(com.android.internal.R.drawable.stat_sys_tether_bluetooth);
+        } else {
+            clearTetheredNotification();
+        }
+    }
+
+    private void showTetheredNotification(int icon) {
+        NotificationManager notificationManager =
+                (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        if (notificationManager == null) {
+            return;
+        }
+
+        if (mTetheredNotification != null) {
+            if (mTetheredNotification.icon == icon) {
+                return;
+            }
+            notificationManager.cancelAsUser(null, mTetheredNotification.icon,
+                    UserHandle.ALL);
+        }
+
+        Intent intent = new Intent();
+        intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
+
+        PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
+                null, UserHandle.CURRENT);
+
+        Resources r = Resources.getSystem();
+        CharSequence title = r.getText(com.android.internal.R.string.tethered_notification_title);
+        CharSequence message = r.getText(com.android.internal.R.string.
+                tethered_notification_message);
+
+        if (mTetheredNotification == null) {
+            mTetheredNotification = new Notification();
+            mTetheredNotification.when = 0;
+        }
+        mTetheredNotification.icon = icon;
+        mTetheredNotification.defaults &= ~Notification.DEFAULT_SOUND;
+        mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT;
+        mTetheredNotification.tickerText = title;
+        mTetheredNotification.setLatestEventInfo(mContext, title, message, pi);
+
+        notificationManager.notifyAsUser(null, mTetheredNotification.icon,
+                mTetheredNotification, UserHandle.ALL);
+    }
+
+    private void clearTetheredNotification() {
+        NotificationManager notificationManager =
+            (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        if (notificationManager != null && mTetheredNotification != null) {
+            notificationManager.cancelAsUser(null, mTetheredNotification.icon,
+                    UserHandle.ALL);
+            mTetheredNotification = null;
+        }
+    }
+
+    private class StateReceiver extends BroadcastReceiver {
+        public void onReceive(Context content, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(UsbManager.ACTION_USB_STATE)) {
+                synchronized (Tethering.this.mPublicSync) {
+                    boolean usbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+                    mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
+                    // start tethering if we have a request pending
+                    if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
+                        tetherUsb(true);
+                    }
+                    mUsbTetherRequested = false;
+                }
+            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+                        ConnectivityManager.EXTRA_NETWORK_INFO);
+                if (networkInfo != null &&
+                        networkInfo.getDetailedState() != NetworkInfo.DetailedState.FAILED) {
+                    if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
+                    mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
+                }
+            } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                updateConfiguration();
+            }
+        }
+    }
+
+    private void tetherUsb(boolean enable) {
+        if (VDBG) Log.d(TAG, "tetherUsb " + enable);
+
+        String[] ifaces = new String[0];
+        try {
+            ifaces = mNMService.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces", e);
+            return;
+        }
+        for (String iface : ifaces) {
+            if (isUsb(iface)) {
+                int result = (enable ? tether(iface) : untether(iface));
+                if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    return;
+                }
+            }
+        }
+        Log.e(TAG, "unable start or stop USB tethering");
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean configureUsbIface(boolean enabled) {
+        if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
+
+        // toggle the USB interfaces
+        String[] ifaces = new String[0];
+        try {
+            ifaces = mNMService.listInterfaces();
+        } catch (Exception e) {
+            Log.e(TAG, "Error listing Interfaces", e);
+            return false;
+        }
+        for (String iface : ifaces) {
+            if (isUsb(iface)) {
+                InterfaceConfiguration ifcg = null;
+                try {
+                    ifcg = mNMService.getInterfaceConfig(iface);
+                    if (ifcg != null) {
+                        InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
+                        ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
+                        if (enabled) {
+                            ifcg.setInterfaceUp();
+                        } else {
+                            ifcg.setInterfaceDown();
+                        }
+                        ifcg.clearFlag("running");
+                        mNMService.setInterfaceConfig(iface, ifcg);
+                    }
+                } catch (Exception e) {
+                    Log.e(TAG, "Error configuring interface " + iface, e);
+                    return false;
+                }
+            }
+         }
+
+        return true;
+    }
+
+    // TODO - return copies so people can't tamper
+    public String[] getTetherableUsbRegexs() {
+        return mTetherableUsbRegexs;
+    }
+
+    public String[] getTetherableWifiRegexs() {
+        return mTetherableWifiRegexs;
+    }
+
+    public String[] getTetherableBluetoothRegexs() {
+        return mTetherableBluetoothRegexs;
+    }
+
+    public int setUsbTethering(boolean enable) {
+        if (VDBG) Log.d(TAG, "setUsbTethering(" + enable + ")");
+        UsbManager usbManager = (UsbManager)mContext.getSystemService(Context.USB_SERVICE);
+
+        synchronized (mPublicSync) {
+            if (enable) {
+                if (mRndisEnabled) {
+                    tetherUsb(true);
+                } else {
+                    mUsbTetherRequested = true;
+                    usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false);
+                }
+            } else {
+                tetherUsb(false);
+                if (mRndisEnabled) {
+                    usbManager.setCurrentFunction(null, false);
+                }
+                mUsbTetherRequested = false;
+            }
+        }
+        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+    }
+
+    public int[] getUpstreamIfaceTypes() {
+        int values[];
+        synchronized (mPublicSync) {
+            updateConfiguration();  // TODO - remove?
+            values = new int[mUpstreamIfaceTypes.size()];
+            Iterator<Integer> iterator = mUpstreamIfaceTypes.iterator();
+            for (int i=0; i < mUpstreamIfaceTypes.size(); i++) {
+                values[i] = iterator.next();
+            }
+        }
+        return values;
+    }
+
+    public void checkDunRequired() {
+        int secureSetting = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.TETHER_DUN_REQUIRED, 2);
+        synchronized (mPublicSync) {
+            // 2 = not set, 0 = DUN not required, 1 = DUN required
+            if (secureSetting != 2) {
+                int requiredApn = (secureSetting == 1 ?
+                        ConnectivityManager.TYPE_MOBILE_DUN :
+                        ConnectivityManager.TYPE_MOBILE_HIPRI);
+                if (requiredApn == ConnectivityManager.TYPE_MOBILE_DUN) {
+                    while (mUpstreamIfaceTypes.contains(MOBILE_TYPE)) {
+                        mUpstreamIfaceTypes.remove(MOBILE_TYPE);
+                    }
+                    while (mUpstreamIfaceTypes.contains(HIPRI_TYPE)) {
+                        mUpstreamIfaceTypes.remove(HIPRI_TYPE);
+                    }
+                    if (mUpstreamIfaceTypes.contains(DUN_TYPE) == false) {
+                        mUpstreamIfaceTypes.add(DUN_TYPE);
+                    }
+                } else {
+                    while (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
+                        mUpstreamIfaceTypes.remove(DUN_TYPE);
+                    }
+                    if (mUpstreamIfaceTypes.contains(MOBILE_TYPE) == false) {
+                        mUpstreamIfaceTypes.add(MOBILE_TYPE);
+                    }
+                    if (mUpstreamIfaceTypes.contains(HIPRI_TYPE) == false) {
+                        mUpstreamIfaceTypes.add(HIPRI_TYPE);
+                    }
+                }
+            }
+            if (mUpstreamIfaceTypes.contains(DUN_TYPE)) {
+                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_DUN;
+            } else {
+                mPreferredUpstreamMobileApn = ConnectivityManager.TYPE_MOBILE_HIPRI;
+            }
+        }
+    }
+
+    // TODO review API - maybe return ArrayList<String> here and below?
+    public String[] getTetheredIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mPublicSync) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isTethered()) {
+                    list.add((String)key);
+                }
+            }
+        }
+        String[] retVal = new String[list.size()];
+        for (int i=0; i < list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
+    public String[] getTetherableIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mPublicSync) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isAvailable()) {
+                    list.add((String)key);
+                }
+            }
+        }
+        String[] retVal = new String[list.size()];
+        for (int i=0; i < list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
+    public String[] getErroredIfaces() {
+        ArrayList<String> list = new ArrayList<String>();
+        synchronized (mPublicSync) {
+            Set keys = mIfaces.keySet();
+            for (Object key : keys) {
+                TetherInterfaceSM sm = mIfaces.get(key);
+                if (sm.isErrored()) {
+                    list.add((String)key);
+                }
+            }
+        }
+        String[] retVal = new String[list.size()];
+        for (int i= 0; i< list.size(); i++) {
+            retVal[i] = list.get(i);
+        }
+        return retVal;
+    }
+
+    //TODO: Temporary handling upstream change triggered without
+    //      CONNECTIVITY_ACTION. Only to accomodate interface
+    //      switch during HO.
+    //      @see bug/4455071
+    public void handleTetherIfaceChange() {
+        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
+    }
+
+    class TetherInterfaceSM extends StateMachine {
+        // notification from the master SM that it's not in tether mode
+        static final int CMD_TETHER_MODE_DEAD            =  1;
+        // request from the user that it wants to tether
+        static final int CMD_TETHER_REQUESTED            =  2;
+        // request from the user that it wants to untether
+        static final int CMD_TETHER_UNREQUESTED          =  3;
+        // notification that this interface is down
+        static final int CMD_INTERFACE_DOWN              =  4;
+        // notification that this interface is up
+        static final int CMD_INTERFACE_UP                =  5;
+        // notification from the master SM that it had an error turning on cellular dun
+        static final int CMD_CELL_DUN_ERROR              =  6;
+        // notification from the master SM that it had trouble enabling IP Forwarding
+        static final int CMD_IP_FORWARDING_ENABLE_ERROR  =  7;
+        // notification from the master SM that it had trouble disabling IP Forwarding
+        static final int CMD_IP_FORWARDING_DISABLE_ERROR =  8;
+        // notification from the master SM that it had trouble staring tethering
+        static final int CMD_START_TETHERING_ERROR       =  9;
+        // notification from the master SM that it had trouble stopping tethering
+        static final int CMD_STOP_TETHERING_ERROR        = 10;
+        // notification from the master SM that it had trouble setting the DNS forwarders
+        static final int CMD_SET_DNS_FORWARDERS_ERROR    = 11;
+        // the upstream connection has changed
+        static final int CMD_TETHER_CONNECTION_CHANGED   = 12;
+
+        private State mDefaultState;
+
+        private State mInitialState;
+        private State mStartingState;
+        private State mTetheredState;
+
+        private State mUnavailableState;
+
+        private boolean mAvailable;
+        private boolean mTethered;
+        int mLastError;
+
+        String mIfaceName;
+        String mMyUpstreamIfaceName;  // may change over time
+
+        boolean mUsb;
+
+        TetherInterfaceSM(String name, Looper looper, boolean usb) {
+            super(name, looper);
+            mIfaceName = name;
+            mUsb = usb;
+            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+
+            mInitialState = new InitialState();
+            addState(mInitialState);
+            mStartingState = new StartingState();
+            addState(mStartingState);
+            mTetheredState = new TetheredState();
+            addState(mTetheredState);
+            mUnavailableState = new UnavailableState();
+            addState(mUnavailableState);
+
+            setInitialState(mInitialState);
+        }
+
+        public String toString() {
+            String res = new String();
+            res += mIfaceName + " - ";
+            IState current = getCurrentState();
+            if (current == mInitialState) res += "InitialState";
+            if (current == mStartingState) res += "StartingState";
+            if (current == mTetheredState) res += "TetheredState";
+            if (current == mUnavailableState) res += "UnavailableState";
+            if (mAvailable) res += " - Available";
+            if (mTethered) res += " - Tethered";
+            res += " - lastError =" + mLastError;
+            return res;
+        }
+
+        public int getLastError() {
+            synchronized (Tethering.this.mPublicSync) {
+                return mLastError;
+            }
+        }
+
+        private void setLastError(int error) {
+            synchronized (Tethering.this.mPublicSync) {
+                mLastError = error;
+
+                if (isErrored()) {
+                    if (mUsb) {
+                        // note everything's been unwound by this point so nothing to do on
+                        // further error..
+                        Tethering.this.configureUsbIface(false);
+                    }
+                }
+            }
+        }
+
+        public boolean isAvailable() {
+            synchronized (Tethering.this.mPublicSync) {
+                return mAvailable;
+            }
+        }
+
+        private void setAvailable(boolean available) {
+            synchronized (Tethering.this.mPublicSync) {
+                mAvailable = available;
+            }
+        }
+
+        public boolean isTethered() {
+            synchronized (Tethering.this.mPublicSync) {
+                return mTethered;
+            }
+        }
+
+        private void setTethered(boolean tethered) {
+            synchronized (Tethering.this.mPublicSync) {
+                mTethered = tethered;
+            }
+        }
+
+        public boolean isErrored() {
+            synchronized (Tethering.this.mPublicSync) {
+                return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
+            }
+        }
+
+        class InitialState extends State {
+            @Override
+            public void enter() {
+                setAvailable(true);
+                setTethered(false);
+                sendTetherStateChangedBroadcast();
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, "InitialState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_REQUESTED:
+                        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED,
+                                TetherInterfaceSM.this);
+                        transitionTo(mStartingState);
+                        break;
+                    case CMD_INTERFACE_DOWN:
+                        transitionTo(mUnavailableState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class StartingState extends State {
+            @Override
+            public void enter() {
+                setAvailable(false);
+                if (mUsb) {
+                    if (!Tethering.this.configureUsbIface(true)) {
+                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
+                                TetherInterfaceSM.this);
+                        setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+
+                        transitionTo(mInitialState);
+                        return;
+                    }
+                }
+                sendTetherStateChangedBroadcast();
+
+                // Skipping StartingState
+                transitionTo(mTetheredState);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, "StartingState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    // maybe a parent class?
+                    case CMD_TETHER_UNREQUESTED:
+                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
+                                TetherInterfaceSM.this);
+                        if (mUsb) {
+                            if (!Tethering.this.configureUsbIface(false)) {
+                                setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                                break;
+                            }
+                        }
+                        transitionTo(mInitialState);
+                        break;
+                    case CMD_CELL_DUN_ERROR:
+                    case CMD_IP_FORWARDING_ENABLE_ERROR:
+                    case CMD_IP_FORWARDING_DISABLE_ERROR:
+                    case CMD_START_TETHERING_ERROR:
+                    case CMD_STOP_TETHERING_ERROR:
+                    case CMD_SET_DNS_FORWARDERS_ERROR:
+                        setLastErrorAndTransitionToInitialState(
+                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                        break;
+                    case CMD_INTERFACE_DOWN:
+                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
+                                TetherInterfaceSM.this);
+                        transitionTo(mUnavailableState);
+                        break;
+                    default:
+                        retValue = false;
+                }
+                return retValue;
+            }
+        }
+
+        class TetheredState extends State {
+            @Override
+            public void enter() {
+                try {
+                    mNMService.tetherInterface(mIfaceName);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error Tethering: " + e.toString());
+                    setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
+
+                    transitionTo(mInitialState);
+                    return;
+                }
+                if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+                setAvailable(false);
+                setTethered(true);
+                sendTetherStateChangedBroadcast();
+            }
+
+            private void cleanupUpstream() {
+                if (mMyUpstreamIfaceName != null) {
+                    // note that we don't care about errors here.
+                    // sometimes interfaces are gone before we get
+                    // to remove their rules, which generates errors.
+                    // just do the best we can.
+                    try {
+                        // about to tear down NAT; gather remaining statistics
+                        mStatsService.forceUpdate();
+                    } catch (Exception e) {
+                        if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                    }
+                    try {
+                        mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
+                    } catch (Exception e) {
+                        if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                    }
+                    mMyUpstreamIfaceName = null;
+                }
+                return;
+            }
+
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, "TetheredState.processMessage what=" + message.what);
+                boolean retValue = true;
+                boolean error = false;
+                switch (message.what) {
+                    case CMD_TETHER_UNREQUESTED:
+                    case CMD_INTERFACE_DOWN:
+                        cleanupUpstream();
+                        try {
+                            mNMService.untetherInterface(mIfaceName);
+                        } catch (Exception e) {
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
+                            break;
+                        }
+                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
+                                TetherInterfaceSM.this);
+                        if (message.what == CMD_TETHER_UNREQUESTED) {
+                            if (mUsb) {
+                                if (!Tethering.this.configureUsbIface(false)) {
+                                    setLastError(
+                                            ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                                }
+                            }
+                            transitionTo(mInitialState);
+                        } else if (message.what == CMD_INTERFACE_DOWN) {
+                            transitionTo(mUnavailableState);
+                        }
+                        if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
+                        break;
+                    case CMD_TETHER_CONNECTION_CHANGED:
+                        String newUpstreamIfaceName = (String)(message.obj);
+                        if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+                                (mMyUpstreamIfaceName != null &&
+                                mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+                            if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+                            break;
+                        }
+                        cleanupUpstream();
+                        if (newUpstreamIfaceName != null) {
+                            try {
+                                mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
+                            } catch (Exception e) {
+                                Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                                try {
+                                    mNMService.untetherInterface(mIfaceName);
+                                } catch (Exception ee) {}
+
+                                setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
+                                transitionTo(mInitialState);
+                                return true;
+                            }
+                        }
+                        mMyUpstreamIfaceName = newUpstreamIfaceName;
+                        break;
+                    case CMD_CELL_DUN_ERROR:
+                    case CMD_IP_FORWARDING_ENABLE_ERROR:
+                    case CMD_IP_FORWARDING_DISABLE_ERROR:
+                    case CMD_START_TETHERING_ERROR:
+                    case CMD_STOP_TETHERING_ERROR:
+                    case CMD_SET_DNS_FORWARDERS_ERROR:
+                        error = true;
+                        // fall through
+                    case CMD_TETHER_MODE_DEAD:
+                        cleanupUpstream();
+                        try {
+                            mNMService.untetherInterface(mIfaceName);
+                        } catch (Exception e) {
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
+                            break;
+                        }
+                        if (error) {
+                            setLastErrorAndTransitionToInitialState(
+                                    ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                            break;
+                        }
+                        if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
+                        sendTetherStateChangedBroadcast();
+                        if (mUsb) {
+                            if (!Tethering.this.configureUsbIface(false)) {
+                                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
+                            }
+                        }
+                        transitionTo(mInitialState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class UnavailableState extends State {
+            @Override
+            public void enter() {
+                setAvailable(false);
+                setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
+                setTethered(false);
+                sendTetherStateChangedBroadcast();
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_INTERFACE_UP:
+                        transitionTo(mInitialState);
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        void setLastErrorAndTransitionToInitialState(int error) {
+            setLastError(error);
+            transitionTo(mInitialState);
+        }
+
+    }
+
+    class TetherMasterSM extends StateMachine {
+        // an interface SM has requested Tethering
+        static final int CMD_TETHER_MODE_REQUESTED   = 1;
+        // an interface SM has unrequested Tethering
+        static final int CMD_TETHER_MODE_UNREQUESTED = 2;
+        // upstream connection change - do the right thing
+        static final int CMD_UPSTREAM_CHANGED        = 3;
+        // we received notice that the cellular DUN connection is up
+        static final int CMD_CELL_CONNECTION_RENEW   = 4;
+        // we don't have a valid upstream conn, check again after a delay
+        static final int CMD_RETRY_UPSTREAM          = 5;
+
+        // This indicates what a timeout event relates to.  A state that
+        // sends itself a delayed timeout event and handles incoming timeout events
+        // should inc this when it is entered and whenever it sends a new timeout event.
+        // We do not flush the old ones.
+        private int mSequenceNumber;
+
+        private State mInitialState;
+        private State mTetherModeAliveState;
+
+        private State mSetIpForwardingEnabledErrorState;
+        private State mSetIpForwardingDisabledErrorState;
+        private State mStartTetheringErrorState;
+        private State mStopTetheringErrorState;
+        private State mSetDnsForwardersErrorState;
+
+        private ArrayList<TetherInterfaceSM> mNotifyList;
+
+        private int mCurrentConnectionSequence;
+        private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+
+        private String mUpstreamIfaceName = null;
+
+        private static final int UPSTREAM_SETTLE_TIME_MS     = 10000;
+        private static final int CELL_CONNECTION_RENEW_MS    = 40000;
+
+        TetherMasterSM(String name, Looper looper) {
+            super(name, looper);
+
+            //Add states
+            mInitialState = new InitialState();
+            addState(mInitialState);
+            mTetherModeAliveState = new TetherModeAliveState();
+            addState(mTetherModeAliveState);
+
+            mSetIpForwardingEnabledErrorState = new SetIpForwardingEnabledErrorState();
+            addState(mSetIpForwardingEnabledErrorState);
+            mSetIpForwardingDisabledErrorState = new SetIpForwardingDisabledErrorState();
+            addState(mSetIpForwardingDisabledErrorState);
+            mStartTetheringErrorState = new StartTetheringErrorState();
+            addState(mStartTetheringErrorState);
+            mStopTetheringErrorState = new StopTetheringErrorState();
+            addState(mStopTetheringErrorState);
+            mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
+            addState(mSetDnsForwardersErrorState);
+
+            mNotifyList = new ArrayList<TetherInterfaceSM>();
+            setInitialState(mInitialState);
+        }
+
+        class TetherMasterUtilState extends State {
+            protected final static boolean TRY_TO_SETUP_MOBILE_CONNECTION = true;
+            protected final static boolean WAIT_FOR_NETWORK_TO_SETTLE     = false;
+
+            @Override
+            public boolean processMessage(Message m) {
+                return false;
+            }
+            protected String enableString(int apnType) {
+                switch (apnType) {
+                case ConnectivityManager.TYPE_MOBILE_DUN:
+                    return Phone.FEATURE_ENABLE_DUN_ALWAYS;
+                case ConnectivityManager.TYPE_MOBILE:
+                case ConnectivityManager.TYPE_MOBILE_HIPRI:
+                    return Phone.FEATURE_ENABLE_HIPRI;
+                }
+                return null;
+            }
+            protected boolean turnOnUpstreamMobileConnection(int apnType) {
+                boolean retValue = true;
+                if (apnType == ConnectivityManager.TYPE_NONE) return false;
+                if (apnType != mMobileApnReserved) turnOffUpstreamMobileConnection();
+                int result = PhoneConstants.APN_REQUEST_FAILED;
+                String enableString = enableString(apnType);
+                if (enableString == null) return false;
+                try {
+                    result = mConnService.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                            enableString, new Binder());
+                } catch (Exception e) {
+                }
+                switch (result) {
+                case PhoneConstants.APN_ALREADY_ACTIVE:
+                case PhoneConstants.APN_REQUEST_STARTED:
+                    mMobileApnReserved = apnType;
+                    Message m = obtainMessage(CMD_CELL_CONNECTION_RENEW);
+                    m.arg1 = ++mCurrentConnectionSequence;
+                    sendMessageDelayed(m, CELL_CONNECTION_RENEW_MS);
+                    break;
+                case PhoneConstants.APN_REQUEST_FAILED:
+                default:
+                    retValue = false;
+                    break;
+                }
+
+                return retValue;
+            }
+            protected boolean turnOffUpstreamMobileConnection() {
+                // ignore pending renewal requests
+                ++mCurrentConnectionSequence;
+                if (mMobileApnReserved != ConnectivityManager.TYPE_NONE) {
+                    try {
+                        mConnService.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                                enableString(mMobileApnReserved));
+                    } catch (Exception e) {
+                        return false;
+                    }
+                    mMobileApnReserved = ConnectivityManager.TYPE_NONE;
+                }
+                return true;
+            }
+            protected boolean turnOnMasterTetherSettings() {
+                try {
+                    mNMService.setIpForwardingEnabled(true);
+                } catch (Exception e) {
+                    transitionTo(mSetIpForwardingEnabledErrorState);
+                    return false;
+                }
+                try {
+                    mNMService.startTethering(mDhcpRange);
+                } catch (Exception e) {
+                    try {
+                        mNMService.stopTethering();
+                        mNMService.startTethering(mDhcpRange);
+                    } catch (Exception ee) {
+                        transitionTo(mStartTetheringErrorState);
+                        return false;
+                    }
+                }
+                try {
+                    mNMService.setDnsForwarders(mDefaultDnsServers);
+                } catch (Exception e) {
+                    transitionTo(mSetDnsForwardersErrorState);
+                    return false;
+                }
+                return true;
+            }
+            protected boolean turnOffMasterTetherSettings() {
+                try {
+                    mNMService.stopTethering();
+                } catch (Exception e) {
+                    transitionTo(mStopTetheringErrorState);
+                    return false;
+                }
+                try {
+                    mNMService.setIpForwardingEnabled(false);
+                } catch (Exception e) {
+                    transitionTo(mSetIpForwardingDisabledErrorState);
+                    return false;
+                }
+                transitionTo(mInitialState);
+                return true;
+            }
+
+            protected void chooseUpstreamType(boolean tryCell) {
+                int upType = ConnectivityManager.TYPE_NONE;
+                String iface = null;
+
+                updateConfiguration(); // TODO - remove?
+
+                synchronized (mPublicSync) {
+                    if (VDBG) {
+                        Log.d(TAG, "chooseUpstreamType has upstream iface types:");
+                        for (Integer netType : mUpstreamIfaceTypes) {
+                            Log.d(TAG, " " + netType);
+                        }
+                    }
+
+                    for (Integer netType : mUpstreamIfaceTypes) {
+                        NetworkInfo info = null;
+                        try {
+                            info = mConnService.getNetworkInfo(netType.intValue());
+                        } catch (RemoteException e) { }
+                        if ((info != null) && info.isConnected()) {
+                            upType = netType.intValue();
+                            break;
+                        }
+                    }
+                }
+
+                if (DBG) {
+                    Log.d(TAG, "chooseUpstreamType(" + tryCell + "), preferredApn ="
+                            + mPreferredUpstreamMobileApn + ", got type=" + upType);
+                }
+
+                // if we're on DUN, put our own grab on it
+                if (upType == ConnectivityManager.TYPE_MOBILE_DUN ||
+                        upType == ConnectivityManager.TYPE_MOBILE_HIPRI) {
+                    turnOnUpstreamMobileConnection(upType);
+                } else if (upType != ConnectivityManager.TYPE_NONE) {
+                    /* If we've found an active upstream connection that's not DUN/HIPRI
+                     * we should stop any outstanding DUN/HIPRI start requests.
+                     *
+                     * If we found NONE we don't want to do this as we want any previous
+                     * requests to keep trying to bring up something we can use.
+                     */
+                    turnOffUpstreamMobileConnection();
+                }
+
+                if (upType == ConnectivityManager.TYPE_NONE) {
+                    boolean tryAgainLater = true;
+                    if ((tryCell == TRY_TO_SETUP_MOBILE_CONNECTION) &&
+                            (turnOnUpstreamMobileConnection(mPreferredUpstreamMobileApn) == true)) {
+                        // we think mobile should be coming up - don't set a retry
+                        tryAgainLater = false;
+                    }
+                    if (tryAgainLater) {
+                        sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
+                    }
+                } else {
+                    LinkProperties linkProperties = null;
+                    try {
+                        linkProperties = mConnService.getLinkProperties(upType);
+                    } catch (RemoteException e) { }
+                    if (linkProperties != null) {
+                        // Find the interface with the default IPv4 route. It may be the
+                        // interface described by linkProperties, or one of the interfaces
+                        // stacked on top of it.
+                        Log.i(TAG, "Finding IPv4 upstream interface on: " + linkProperties);
+                        RouteInfo ipv4Default = RouteInfo.selectBestRoute(
+                            linkProperties.getAllRoutes(), Inet4Address.ANY);
+                        if (ipv4Default != null) {
+                            iface = ipv4Default.getInterface();
+                            Log.i(TAG, "Found interface " + ipv4Default.getInterface());
+                        } else {
+                            Log.i(TAG, "No IPv4 upstream interface, giving up.");
+                        }
+                    }
+
+                    if (iface != null) {
+                        String[] dnsServers = mDefaultDnsServers;
+                        Collection<InetAddress> dnses = linkProperties.getDnses();
+                        if (dnses != null) {
+                            // we currently only handle IPv4
+                            ArrayList<InetAddress> v4Dnses =
+                                    new ArrayList<InetAddress>(dnses.size());
+                            for (InetAddress dnsAddress : dnses) {
+                                if (dnsAddress instanceof Inet4Address) {
+                                    v4Dnses.add(dnsAddress);
+                                }
+                            }
+                            if (v4Dnses.size() > 0) {
+                                dnsServers = NetworkUtils.makeStrings(v4Dnses);
+                            }
+                        }
+                        try {
+                            mNMService.setDnsForwarders(dnsServers);
+                        } catch (Exception e) {
+                            transitionTo(mSetDnsForwardersErrorState);
+                        }
+                    }
+                }
+                notifyTetheredOfNewUpstreamIface(iface);
+            }
+
+            protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
+                if (DBG) Log.d(TAG, "notifying tethered with iface =" + ifaceName);
+                mUpstreamIfaceName = ifaceName;
+                for (TetherInterfaceSM sm : mNotifyList) {
+                    sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                            ifaceName);
+                }
+            }
+        }
+
+        class InitialState extends TetherMasterUtilState {
+            @Override
+            public void enter() {
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, "MasterInitialState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
+                        mNotifyList.add(who);
+                        transitionTo(mTetherModeAliveState);
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            mNotifyList.remove(who);
+                        }
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class TetherModeAliveState extends TetherMasterUtilState {
+            boolean mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+            @Override
+            public void enter() {
+                turnOnMasterTetherSettings(); // may transition us out
+
+                mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE; // better try something first pass
+                                                        // or crazy tests cases will fail
+                chooseUpstreamType(mTryCell);
+                mTryCell = !mTryCell;
+            }
+            @Override
+            public void exit() {
+                turnOffUpstreamMobileConnection();
+                notifyTetheredOfNewUpstreamIface(null);
+            }
+            @Override
+            public boolean processMessage(Message message) {
+                if (DBG) Log.d(TAG, "TetherModeAliveState.processMessage what=" + message.what);
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
+                        mNotifyList.add(who);
+                        who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                                mUpstreamIfaceName);
+                        break;
+                    case CMD_TETHER_MODE_UNREQUESTED:
+                        who = (TetherInterfaceSM)message.obj;
+                        if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
+                        int index = mNotifyList.indexOf(who);
+                        if (index != -1) {
+                            if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
+                            mNotifyList.remove(index);
+                            if (mNotifyList.isEmpty()) {
+                                turnOffMasterTetherSettings(); // transitions appropriately
+                            } else {
+                                if (DBG) {
+                                    Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
+                                            " live requests:");
+                                    for (Object o : mNotifyList) Log.d(TAG, "  " + o);
+                                }
+                            }
+                        } else {
+                           Log.e(TAG, "TetherModeAliveState UNREQUESTED has unknown who: " + who);
+                        }
+                        break;
+                    case CMD_UPSTREAM_CHANGED:
+                        // need to try DUN immediately if Wifi goes down
+                        mTryCell = !WAIT_FOR_NETWORK_TO_SETTLE;
+                        chooseUpstreamType(mTryCell);
+                        mTryCell = !mTryCell;
+                        break;
+                    case CMD_CELL_CONNECTION_RENEW:
+                        // make sure we're still using a requested connection - may have found
+                        // wifi or something since then.
+                        if (mCurrentConnectionSequence == message.arg1) {
+                            if (VDBG) {
+                                Log.d(TAG, "renewing mobile connection - requeuing for another " +
+                                        CELL_CONNECTION_RENEW_MS + "ms");
+                            }
+                            turnOnUpstreamMobileConnection(mMobileApnReserved);
+                        }
+                        break;
+                    case CMD_RETRY_UPSTREAM:
+                        chooseUpstreamType(mTryCell);
+                        mTryCell = !mTryCell;
+                        break;
+                    default:
+                        retValue = false;
+                        break;
+                }
+                return retValue;
+            }
+        }
+
+        class ErrorState extends State {
+            int mErrorNotification;
+            @Override
+            public boolean processMessage(Message message) {
+                boolean retValue = true;
+                switch (message.what) {
+                    case CMD_TETHER_MODE_REQUESTED:
+                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        who.sendMessage(mErrorNotification);
+                        break;
+                    default:
+                       retValue = false;
+                }
+                return retValue;
+            }
+            void notify(int msgType) {
+                mErrorNotification = msgType;
+                for (Object o : mNotifyList) {
+                    TetherInterfaceSM sm = (TetherInterfaceSM)o;
+                    sm.sendMessage(msgType);
+                }
+            }
+
+        }
+        class SetIpForwardingEnabledErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setIpForwardingEnabled");
+                notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR);
+            }
+        }
+
+        class SetIpForwardingDisabledErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setIpForwardingDisabled");
+                notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR);
+            }
+        }
+
+        class StartTetheringErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in startTethering");
+                notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR);
+                try {
+                    mNMService.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+
+        class StopTetheringErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in stopTethering");
+                notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR);
+                try {
+                    mNMService.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+
+        class SetDnsForwardersErrorState extends ErrorState {
+            @Override
+            public void enter() {
+                Log.e(TAG, "Error in setDnsForwarders");
+                notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR);
+                try {
+                    mNMService.stopTethering();
+                } catch (Exception e) {}
+                try {
+                    mNMService.setIpForwardingEnabled(false);
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    public 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 ConnectivityService.Tether " +
+                    "from from pid=" + Binder.getCallingPid() + ", uid=" +
+                    Binder.getCallingUid());
+                    return;
+        }
+
+        synchronized (mPublicSync) {
+            pw.println("mUpstreamIfaceTypes: ");
+            for (Integer netType : mUpstreamIfaceTypes) {
+                pw.println(" " + netType);
+            }
+
+            pw.println();
+            pw.println("Tether state:");
+            for (Object o : mIfaces.values()) {
+                pw.println(" " + o);
+            }
+        }
+        pw.println();
+        return;
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
new file mode 100644
index 0000000..2ca2cc5
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -0,0 +1,1205 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.Manifest.permission.BIND_VPN_SERVICE;
+
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.net.BaseNetworkStateTracker;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.NetworkInfo;
+import android.net.RouteInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemService;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+import android.widget.Toast;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.R;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.Preconditions;
+import com.android.server.ConnectivityService.VpnCallback;
+import com.android.server.net.BaseNetworkObserver;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import libcore.io.IoUtils;
+
+/**
+ * @hide
+ */
+public class Vpn extends BaseNetworkStateTracker {
+    private static final String TAG = "Vpn";
+    private static final boolean LOGD = true;
+    
+    // TODO: create separate trackers for each unique VPN to support
+    // automated reconnection
+
+    private final VpnCallback mCallback;
+
+    private String mPackage = VpnConfig.LEGACY_VPN;
+    private String mInterface;
+    private Connection mConnection;
+    private LegacyVpnRunner mLegacyVpnRunner;
+    private PendingIntent mStatusIntent;
+    private volatile boolean mEnableNotif = true;
+    private volatile boolean mEnableTeardown = true;
+    private final IConnectivityManager mConnService;
+    private VpnConfig mConfig;
+
+    /* list of users using this VPN. */
+    @GuardedBy("this")
+    private SparseBooleanArray mVpnUsers = null;
+    private BroadcastReceiver mUserIntentReceiver = null;
+
+    private final int mUserId;
+
+    public Vpn(Context context, VpnCallback callback, INetworkManagementService netService,
+            IConnectivityManager connService, int userId) {
+        // TODO: create dedicated TYPE_VPN network type
+        super(ConnectivityManager.TYPE_DUMMY);
+        mContext = context;
+        mCallback = callback;
+        mConnService = connService;
+        mUserId = userId;
+
+        try {
+            netService.registerObserver(mObserver);
+        } catch (RemoteException e) {
+            Log.wtf(TAG, "Problem registering observer", e);
+        }
+        if (userId == UserHandle.USER_OWNER) {
+            // Owner's VPN also needs to handle restricted users
+            mUserIntentReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    final String action = intent.getAction();
+                    final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                            UserHandle.USER_NULL);
+                    if (userId == UserHandle.USER_NULL) return;
+
+                    if (Intent.ACTION_USER_ADDED.equals(action)) {
+                        onUserAdded(userId);
+                    } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                        onUserRemoved(userId);
+                    }
+                }
+            };
+
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(Intent.ACTION_USER_ADDED);
+            intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+            mContext.registerReceiverAsUser(
+                    mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+        }
+    }
+
+    /**
+     * Set if this object is responsible for showing its own notifications. When
+     * {@code false}, notifications are handled externally by someone else.
+     */
+    public void setEnableNotifications(boolean enableNotif) {
+        mEnableNotif = enableNotif;
+    }
+
+    /**
+     * Set if this object is responsible for watching for {@link NetworkInfo}
+     * teardown. When {@code false}, teardown is handled externally by someone
+     * else.
+     */
+    public void setEnableTeardown(boolean enableTeardown) {
+        mEnableTeardown = enableTeardown;
+    }
+
+    @Override
+    protected void startMonitoringInternal() {
+        // Ignored; events are sent through callbacks for now
+    }
+
+    @Override
+    public boolean teardown() {
+        // TODO: finish migration to unique tracker for each VPN
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean reconnect() {
+        // TODO: finish migration to unique tracker for each VPN
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public String getTcpBufferSizesPropName() {
+        return PROP_TCP_BUFFER_UNKNOWN;
+    }
+
+    /**
+     * Update current state, dispaching event to listeners.
+     */
+    private void updateState(DetailedState detailedState, String reason) {
+        if (LOGD) Log.d(TAG, "setting state=" + detailedState + ", reason=" + reason);
+        mNetworkInfo.setDetailedState(detailedState, reason, null);
+        mCallback.onStateChanged(new NetworkInfo(mNetworkInfo));
+    }
+
+    /**
+     * Prepare for a VPN application. This method is designed to solve
+     * race conditions. It first compares the current prepared package
+     * with {@code oldPackage}. If they are the same, the prepared
+     * package is revoked and replaced with {@code newPackage}. If
+     * {@code oldPackage} is {@code null}, the comparison is omitted.
+     * If {@code newPackage} is the same package or {@code null}, the
+     * revocation is omitted. This method returns {@code true} if the
+     * operation is succeeded.
+     *
+     * Legacy VPN is handled specially since it is not a real package.
+     * It uses {@link VpnConfig#LEGACY_VPN} as its package name, and
+     * it can be revoked by itself.
+     *
+     * @param oldPackage The package name of the old VPN application.
+     * @param newPackage The package name of the new VPN application.
+     * @return true if the operation is succeeded.
+     */
+    public synchronized boolean prepare(String oldPackage, String newPackage) {
+        // Return false if the package does not match.
+        if (oldPackage != null && !oldPackage.equals(mPackage)) {
+            return false;
+        }
+
+        // Return true if we do not need to revoke.
+        if (newPackage == null ||
+                (newPackage.equals(mPackage) && !newPackage.equals(VpnConfig.LEGACY_VPN))) {
+            return true;
+        }
+
+        // Check if the caller is authorized.
+        enforceControlPermission();
+
+        // Reset the interface and hide the notification.
+        if (mInterface != null) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mCallback.restore();
+                final int size = mVpnUsers.size();
+                final boolean forwardDns = (mConfig.dnsServers != null &&
+                        mConfig.dnsServers.size() != 0);
+                for (int i = 0; i < size; i++) {
+                    int user = mVpnUsers.keyAt(i);
+                    mCallback.clearUserForwarding(mInterface, user, forwardDns);
+                    hideNotification(user);
+                }
+
+                mCallback.clearMarkedForwarding(mInterface);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+            jniReset(mInterface);
+            mInterface = null;
+            mVpnUsers = null;
+        }
+
+        // Revoke the connection or stop LegacyVpnRunner.
+        if (mConnection != null) {
+            try {
+                mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
+                        Parcel.obtain(), null, IBinder.FLAG_ONEWAY);
+            } catch (Exception e) {
+                // ignore
+            }
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        } else if (mLegacyVpnRunner != null) {
+            mLegacyVpnRunner.exit();
+            mLegacyVpnRunner = null;
+        }
+
+        Log.i(TAG, "Switched from " + mPackage + " to " + newPackage);
+        mPackage = newPackage;
+        mConfig = null;
+        updateState(DetailedState.IDLE, "prepare");
+        return true;
+    }
+
+    /**
+     * Protect a socket from routing changes by binding it to the given
+     * interface. The socket is NOT closed by this method.
+     *
+     * @param socket The socket to be bound.
+     * @param interfaze The name of the interface.
+     */
+    public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception {
+
+        PackageManager pm = mContext.getPackageManager();
+        int appUid = pm.getPackageUid(mPackage, mUserId);
+        if (Binder.getCallingUid() != appUid) {
+            throw new SecurityException("Unauthorized Caller");
+        }
+        // protect the socket from routing rules
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mCallback.protect(socket);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        // bind the socket to the interface
+        jniProtect(socket.getFd(), interfaze);
+
+    }
+
+    /**
+     * Establish a VPN network and return the file descriptor of the VPN
+     * interface. This methods returns {@code null} if the application is
+     * revoked or not prepared.
+     *
+     * @param config The parameters to configure the network.
+     * @return The file descriptor of the VPN interface.
+     */
+    public synchronized ParcelFileDescriptor establish(VpnConfig config) {
+        // Check if the caller is already prepared.
+        UserManager mgr = UserManager.get(mContext);
+        PackageManager pm = mContext.getPackageManager();
+        ApplicationInfo app = null;
+        try {
+            app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
+            if (Binder.getCallingUid() != app.uid) {
+                return null;
+            }
+        } catch (Exception e) {
+            return null;
+        }
+        // Check if the service is properly declared.
+        Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE);
+        intent.setClassName(mPackage, config.user);
+        long token = Binder.clearCallingIdentity();
+        try {
+            // Restricted users are not allowed to create VPNs, they are tied to Owner
+            UserInfo user = mgr.getUserInfo(mUserId);
+            if (user.isRestricted()) {
+                throw new SecurityException("Restricted users cannot establish VPNs");
+            }
+
+            ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
+                                                                        null, 0, mUserId);
+            if (info == null) {
+                throw new SecurityException("Cannot find " + config.user);
+            }
+            if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) {
+                throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE);
+            }
+        } catch (RemoteException e) {
+                throw new SecurityException("Cannot find " + config.user);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        // Configure the interface. Abort if any of these steps fails.
+        ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu));
+        try {
+            updateState(DetailedState.CONNECTING, "establish");
+            String interfaze = jniGetName(tun.getFd());
+
+            // TEMP use the old jni calls until there is support for netd address setting
+            StringBuilder builder = new StringBuilder();
+            for (LinkAddress address : config.addresses) {
+                builder.append(" " + address);
+            }
+            if (jniSetAddresses(interfaze, builder.toString()) < 1) {
+                throw new IllegalArgumentException("At least one address must be specified");
+            }
+            Connection connection = new Connection();
+            if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+                        new UserHandle(mUserId))) {
+                throw new IllegalStateException("Cannot bind " + config.user);
+            }
+            if (mConnection != null) {
+                mContext.unbindService(mConnection);
+            }
+            if (mInterface != null && !mInterface.equals(interfaze)) {
+                jniReset(mInterface);
+            }
+            mConnection = connection;
+            mInterface = interfaze;
+
+            // Fill more values.
+            config.user = mPackage;
+            config.interfaze = mInterface;
+            config.startTime = SystemClock.elapsedRealtime();
+            mConfig = config;
+            // Set up forwarding and DNS rules.
+            mVpnUsers = new SparseBooleanArray();
+            token = Binder.clearCallingIdentity();
+            try {
+                mCallback.setMarkedForwarding(mInterface);
+                mCallback.setRoutes(interfaze, config.routes);
+                mCallback.override(mInterface, config.dnsServers, config.searchDomains);
+                addVpnUserLocked(mUserId);
+
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+
+        } catch (RuntimeException e) {
+            updateState(DetailedState.FAILED, "establish");
+            IoUtils.closeQuietly(tun);
+            // make sure marked forwarding is cleared if it was set
+            try {
+                mCallback.clearMarkedForwarding(mInterface);
+            } catch (Exception ingored) {
+                // ignored
+            }
+            throw e;
+        }
+        Log.i(TAG, "Established by " + config.user + " on " + mInterface);
+
+
+        // If we are owner assign all Restricted Users to this VPN
+        if (mUserId == UserHandle.USER_OWNER) {
+            token = Binder.clearCallingIdentity();
+            try {
+                for (UserInfo user : mgr.getUsers()) {
+                    if (user.isRestricted()) {
+                        try {
+                            addVpnUserLocked(user.id);
+                        } catch (Exception e) {
+                            Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN");
+                        }
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+        // TODO: ensure that contract class eventually marks as connected
+        updateState(DetailedState.AUTHENTICATING, "establish");
+        return tun;
+    }
+
+    private boolean isRunningLocked() {
+        return mVpnUsers != null;
+    }
+
+    private void addVpnUserLocked(int user) {
+        enforceControlPermission();
+
+        if (!isRunningLocked()) {
+            throw new IllegalStateException("VPN is not active");
+        }
+
+        final boolean forwardDns = (mConfig.dnsServers != null &&
+                mConfig.dnsServers.size() != 0);
+
+        // add the user
+        mCallback.addUserForwarding(mInterface, user, forwardDns);
+        mVpnUsers.put(user, true);
+
+        // show the notification
+        if (!mPackage.equals(VpnConfig.LEGACY_VPN)) {
+            // Load everything for the user's notification
+            PackageManager pm = mContext.getPackageManager();
+            ApplicationInfo app = null;
+            try {
+                app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId);
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Invalid application");
+            }
+            String label = app.loadLabel(pm).toString();
+            // Load the icon and convert it into a bitmap.
+            Drawable icon = app.loadIcon(pm);
+            Bitmap bitmap = null;
+            if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) {
+                int width = mContext.getResources().getDimensionPixelSize(
+                        android.R.dimen.notification_large_icon_width);
+                int height = mContext.getResources().getDimensionPixelSize(
+                        android.R.dimen.notification_large_icon_height);
+                icon.setBounds(0, 0, width, height);
+                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+                Canvas c = new Canvas(bitmap);
+                icon.draw(c);
+                c.setBitmap(null);
+            }
+            showNotification(label, bitmap, user);
+        } else {
+            showNotification(null, null, user);
+        }
+    }
+
+    private void removeVpnUserLocked(int user) {
+            enforceControlPermission();
+
+            if (!isRunningLocked()) {
+                throw new IllegalStateException("VPN is not active");
+            }
+            final boolean forwardDns = (mConfig.dnsServers != null &&
+                    mConfig.dnsServers.size() != 0);
+            mCallback.clearUserForwarding(mInterface, user, forwardDns);
+            mVpnUsers.delete(user);
+            hideNotification(user);
+    }
+
+    private void onUserAdded(int userId) {
+        // If the user is restricted tie them to the owner's VPN
+        synchronized(Vpn.this) {
+            UserManager mgr = UserManager.get(mContext);
+            UserInfo user = mgr.getUserInfo(userId);
+            if (user.isRestricted()) {
+                try {
+                    addVpnUserLocked(userId);
+                } catch (Exception e) {
+                    Log.wtf(TAG, "Failed to add restricted user to owner", e);
+                }
+            }
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        // clean up if restricted
+        synchronized(Vpn.this) {
+            UserManager mgr = UserManager.get(mContext);
+            UserInfo user = mgr.getUserInfo(userId);
+            if (user.isRestricted()) {
+                try {
+                    removeVpnUserLocked(userId);
+                } catch (Exception e) {
+                    Log.wtf(TAG, "Failed to remove restricted user to owner", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the configuration of the currently running VPN.
+     */
+    public VpnConfig getVpnConfig() {
+        enforceControlPermission();
+        return mConfig;
+    }
+
+    @Deprecated
+    public synchronized void interfaceStatusChanged(String iface, boolean up) {
+        try {
+            mObserver.interfaceStatusChanged(iface, up);
+        } catch (RemoteException e) {
+            // ignored; target is local
+        }
+    }
+
+    private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
+        @Override
+        public void interfaceStatusChanged(String interfaze, boolean up) {
+            synchronized (Vpn.this) {
+                if (!up && mLegacyVpnRunner != null) {
+                    mLegacyVpnRunner.check(interfaze);
+                }
+            }
+        }
+
+        @Override
+        public void interfaceRemoved(String interfaze) {
+            synchronized (Vpn.this) {
+                if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        final int size = mVpnUsers.size();
+                        final boolean forwardDns = (mConfig.dnsServers != null &&
+                                mConfig.dnsServers.size() != 0);
+                        for (int i = 0; i < size; i++) {
+                            int user = mVpnUsers.keyAt(i);
+                            mCallback.clearUserForwarding(mInterface, user, forwardDns);
+                            hideNotification(user);
+                        }
+                        mVpnUsers = null;
+                        mCallback.clearMarkedForwarding(mInterface);
+
+                        mCallback.restore();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    mInterface = null;
+                    if (mConnection != null) {
+                        mContext.unbindService(mConnection);
+                        mConnection = null;
+                        updateState(DetailedState.DISCONNECTED, "interfaceRemoved");
+                    } else if (mLegacyVpnRunner != null) {
+                        mLegacyVpnRunner.exit();
+                        mLegacyVpnRunner = null;
+                    }
+                }
+            }
+        }
+    };
+
+    private void enforceControlPermission() {
+        // System user is allowed to control VPN.
+        if (Binder.getCallingUid() == Process.SYSTEM_UID) {
+            return;
+        }
+        int appId = UserHandle.getAppId(Binder.getCallingUid());
+        final long token = Binder.clearCallingIdentity();
+        try {
+            // System VPN dialogs are also allowed to control VPN.
+            PackageManager pm = mContext.getPackageManager();
+            ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0);
+            if (((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) && (appId == app.uid)) {
+                return;
+            }
+        } catch (Exception e) {
+            // ignore
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        throw new SecurityException("Unauthorized Caller");
+    }
+
+    private class Connection implements ServiceConnection {
+        private IBinder mService;
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = service;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mService = null;
+        }
+    }
+
+    private void showNotification(String label, Bitmap icon, int user) {
+        if (!mEnableNotif) return;
+        mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext);
+
+        NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (nm != null) {
+            String title = (label == null) ? mContext.getString(R.string.vpn_title) :
+                    mContext.getString(R.string.vpn_title_long, label);
+            String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) :
+                    mContext.getString(R.string.vpn_text_long, mConfig.session);
+
+            Notification notification = new Notification.Builder(mContext)
+                    .setSmallIcon(R.drawable.vpn_connected)
+                    .setLargeIcon(icon)
+                    .setContentTitle(title)
+                    .setContentText(text)
+                    .setContentIntent(mStatusIntent)
+                    .setDefaults(0)
+                    .setOngoing(true)
+                    .build();
+            nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user));
+        }
+    }
+
+    private void hideNotification(int user) {
+        if (!mEnableNotif) return;
+        mStatusIntent = null;
+
+        NotificationManager nm = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (nm != null) {
+            nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user));
+        }
+    }
+
+    private native int jniCreate(int mtu);
+    private native String jniGetName(int tun);
+    private native int jniSetAddresses(String interfaze, String addresses);
+    private native int jniSetRoutes(String interfaze, String routes);
+    private native void jniReset(String interfaze);
+    private native int jniCheck(String interfaze);
+    private native void jniProtect(int socket, String interfaze);
+
+    private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
+        for (RouteInfo route : prop.getAllRoutes()) {
+            // Currently legacy VPN only works on IPv4.
+            if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
+                return route;
+            }
+        }
+
+        throw new IllegalStateException("Unable to find IPv4 default gateway");
+    }
+
+    /**
+     * Start legacy VPN, controlling native daemons as needed. Creates a
+     * secondary thread to perform connection work, returning quickly.
+     */
+    public void startLegacyVpn(VpnProfile profile, KeyStore keyStore, LinkProperties egress) {
+        enforceControlPermission();
+        if (!keyStore.isUnlocked()) {
+            throw new IllegalStateException("KeyStore isn't unlocked");
+        }
+
+        final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress);
+        final String gateway = ipv4DefaultRoute.getGateway().getHostAddress();
+        final String iface = ipv4DefaultRoute.getInterface();
+
+        // Load certificates.
+        String privateKey = "";
+        String userCert = "";
+        String caCert = "";
+        String serverCert = "";
+        if (!profile.ipsecUserCert.isEmpty()) {
+            privateKey = Credentials.USER_PRIVATE_KEY + profile.ipsecUserCert;
+            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecUserCert);
+            userCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
+        }
+        if (!profile.ipsecCaCert.isEmpty()) {
+            byte[] value = keyStore.get(Credentials.CA_CERTIFICATE + profile.ipsecCaCert);
+            caCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
+        }
+        if (!profile.ipsecServerCert.isEmpty()) {
+            byte[] value = keyStore.get(Credentials.USER_CERTIFICATE + profile.ipsecServerCert);
+            serverCert = (value == null) ? null : new String(value, StandardCharsets.UTF_8);
+        }
+        if (privateKey == null || userCert == null || caCert == null || serverCert == null) {
+            throw new IllegalStateException("Cannot load credentials");
+        }
+
+        // Prepare arguments for racoon.
+        String[] racoon = null;
+        switch (profile.type) {
+            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+                racoon = new String[] {
+                    iface, profile.server, "udppsk", profile.ipsecIdentifier,
+                    profile.ipsecSecret, "1701",
+                };
+                break;
+            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+                racoon = new String[] {
+                    iface, profile.server, "udprsa", privateKey, userCert,
+                    caCert, serverCert, "1701",
+                };
+                break;
+            case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
+                racoon = new String[] {
+                    iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
+                    profile.ipsecSecret, profile.username, profile.password, "", gateway,
+                };
+                break;
+            case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
+                racoon = new String[] {
+                    iface, profile.server, "xauthrsa", privateKey, userCert,
+                    caCert, serverCert, profile.username, profile.password, "", gateway,
+                };
+                break;
+            case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
+                racoon = new String[] {
+                    iface, profile.server, "hybridrsa",
+                    caCert, serverCert, profile.username, profile.password, "", gateway,
+                };
+                break;
+        }
+
+        // Prepare arguments for mtpd.
+        String[] mtpd = null;
+        switch (profile.type) {
+            case VpnProfile.TYPE_PPTP:
+                mtpd = new String[] {
+                    iface, "pptp", profile.server, "1723",
+                    "name", profile.username, "password", profile.password,
+                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
+                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+                    (profile.mppe ? "+mppe" : "nomppe"),
+                };
+                break;
+            case VpnProfile.TYPE_L2TP_IPSEC_PSK:
+            case VpnProfile.TYPE_L2TP_IPSEC_RSA:
+                mtpd = new String[] {
+                    iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
+                    "name", profile.username, "password", profile.password,
+                    "linkname", "vpn", "refuse-eap", "nodefaultroute",
+                    "usepeerdns", "idle", "1800", "mtu", "1400", "mru", "1400",
+                };
+                break;
+        }
+
+        VpnConfig config = new VpnConfig();
+        config.legacy = true;
+        config.user = profile.key;
+        config.interfaze = iface;
+        config.session = profile.name;
+
+        config.addLegacyRoutes(profile.routes);
+        if (!profile.dnsServers.isEmpty()) {
+            config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
+        }
+        if (!profile.searchDomains.isEmpty()) {
+            config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
+        }
+        startLegacyVpn(config, racoon, mtpd);
+    }
+
+    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+        stopLegacyVpn();
+
+        // Prepare for the new request. This also checks the caller.
+        prepare(null, VpnConfig.LEGACY_VPN);
+        updateState(DetailedState.CONNECTING, "startLegacyVpn");
+
+        // Start a new LegacyVpnRunner and we are done!
+        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
+        mLegacyVpnRunner.start();
+    }
+
+    public synchronized void stopLegacyVpn() {
+        if (mLegacyVpnRunner != null) {
+            mLegacyVpnRunner.exit();
+            mLegacyVpnRunner = null;
+
+            synchronized (LegacyVpnRunner.TAG) {
+                // wait for old thread to completely finish before spinning up
+                // new instance, otherwise state updates can be out of order.
+            }
+        }
+    }
+
+    /**
+     * Return the information of the current ongoing legacy VPN.
+     */
+    public synchronized LegacyVpnInfo getLegacyVpnInfo() {
+        // Check if the caller is authorized.
+        enforceControlPermission();
+        if (mLegacyVpnRunner == null) return null;
+
+        final LegacyVpnInfo info = new LegacyVpnInfo();
+        info.key = mConfig.user;
+        info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo);
+        if (mNetworkInfo.isConnected()) {
+            info.intent = mStatusIntent;
+        }
+        return info;
+    }
+
+    public VpnConfig getLegacyVpnConfig() {
+        if (mLegacyVpnRunner != null) {
+            return mConfig;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Bringing up a VPN connection takes time, and that is all this thread
+     * does. Here we have plenty of time. The only thing we need to take
+     * care of is responding to interruptions as soon as possible. Otherwise
+     * requests will be piled up. This can be done in a Handler as a state
+     * machine, but it is much easier to read in the current form.
+     */
+    private class LegacyVpnRunner extends Thread {
+        private static final String TAG = "LegacyVpnRunner";
+
+        private final String[] mDaemons;
+        private final String[][] mArguments;
+        private final LocalSocket[] mSockets;
+        private final String mOuterInterface;
+        private final AtomicInteger mOuterConnection =
+                new AtomicInteger(ConnectivityManager.TYPE_NONE);
+
+        private long mTimer = -1;
+
+        /**
+         * Watch for the outer connection (passing in the constructor) going away.
+         */
+        private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (!mEnableTeardown) return;
+
+                if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                    if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
+                            ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
+                        NetworkInfo info = (NetworkInfo)intent.getExtra(
+                                ConnectivityManager.EXTRA_NETWORK_INFO);
+                        if (info != null && !info.isConnectedOrConnecting()) {
+                            try {
+                                mObserver.interfaceStatusChanged(mOuterInterface, false);
+                            } catch (RemoteException e) {}
+                        }
+                    }
+                }
+            }
+        };
+
+        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
+            super(TAG);
+            mConfig = config;
+            mDaemons = new String[] {"racoon", "mtpd"};
+            // TODO: clear arguments from memory once launched
+            mArguments = new String[][] {racoon, mtpd};
+            mSockets = new LocalSocket[mDaemons.length];
+
+            // This is the interface which VPN is running on,
+            // mConfig.interfaze will change to point to OUR
+            // internal interface soon. TODO - add inner/outer to mconfig
+            // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
+            // we will leave the VPN up.  We should check that it's still there/connected after
+            // registering
+            mOuterInterface = mConfig.interfaze;
+
+            try {
+                mOuterConnection.set(
+                        mConnService.findConnectionTypeForIface(mOuterInterface));
+            } catch (Exception e) {
+                mOuterConnection.set(ConnectivityManager.TYPE_NONE);
+            }
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+            mContext.registerReceiver(mBroadcastReceiver, filter);
+        }
+
+        public void check(String interfaze) {
+            if (interfaze.equals(mOuterInterface)) {
+                Log.i(TAG, "Legacy VPN is going down with " + interfaze);
+                exit();
+            }
+        }
+
+        public void exit() {
+            // We assume that everything is reset after stopping the daemons.
+            interrupt();
+            for (LocalSocket socket : mSockets) {
+                IoUtils.closeQuietly(socket);
+            }
+            updateState(DetailedState.DISCONNECTED, "exit");
+            try {
+                mContext.unregisterReceiver(mBroadcastReceiver);
+            } catch (IllegalArgumentException e) {}
+        }
+
+        @Override
+        public void run() {
+            // Wait for the previous thread since it has been interrupted.
+            Log.v(TAG, "Waiting");
+            synchronized (TAG) {
+                Log.v(TAG, "Executing");
+                execute();
+                monitorDaemons();
+            }
+        }
+
+        private void checkpoint(boolean yield) throws InterruptedException {
+            long now = SystemClock.elapsedRealtime();
+            if (mTimer == -1) {
+                mTimer = now;
+                Thread.sleep(1);
+            } else if (now - mTimer <= 60000) {
+                Thread.sleep(yield ? 200 : 1);
+            } else {
+                updateState(DetailedState.FAILED, "checkpoint");
+                throw new IllegalStateException("Time is up");
+            }
+        }
+
+        private void execute() {
+            // Catch all exceptions so we can clean up few things.
+            boolean initFinished = false;
+            try {
+                // Initialize the timer.
+                checkpoint(false);
+
+                // Wait for the daemons to stop.
+                for (String daemon : mDaemons) {
+                    while (!SystemService.isStopped(daemon)) {
+                        checkpoint(true);
+                    }
+                }
+
+                // Clear the previous state.
+                File state = new File("/data/misc/vpn/state");
+                state.delete();
+                if (state.exists()) {
+                    throw new IllegalStateException("Cannot delete the state");
+                }
+                new File("/data/misc/vpn/abort").delete();
+                initFinished = true;
+
+                // Check if we need to restart any of the daemons.
+                boolean restart = false;
+                for (String[] arguments : mArguments) {
+                    restart = restart || (arguments != null);
+                }
+                if (!restart) {
+                    updateState(DetailedState.DISCONNECTED, "execute");
+                    return;
+                }
+                updateState(DetailedState.CONNECTING, "execute");
+
+                // Start the daemon with arguments.
+                for (int i = 0; i < mDaemons.length; ++i) {
+                    String[] arguments = mArguments[i];
+                    if (arguments == null) {
+                        continue;
+                    }
+
+                    // Start the daemon.
+                    String daemon = mDaemons[i];
+                    SystemService.start(daemon);
+
+                    // Wait for the daemon to start.
+                    while (!SystemService.isRunning(daemon)) {
+                        checkpoint(true);
+                    }
+
+                    // Create the control socket.
+                    mSockets[i] = new LocalSocket();
+                    LocalSocketAddress address = new LocalSocketAddress(
+                            daemon, LocalSocketAddress.Namespace.RESERVED);
+
+                    // Wait for the socket to connect.
+                    while (true) {
+                        try {
+                            mSockets[i].connect(address);
+                            break;
+                        } catch (Exception e) {
+                            // ignore
+                        }
+                        checkpoint(true);
+                    }
+                    mSockets[i].setSoTimeout(500);
+
+                    // Send over the arguments.
+                    OutputStream out = mSockets[i].getOutputStream();
+                    for (String argument : arguments) {
+                        byte[] bytes = argument.getBytes(StandardCharsets.UTF_8);
+                        if (bytes.length >= 0xFFFF) {
+                            throw new IllegalArgumentException("Argument is too large");
+                        }
+                        out.write(bytes.length >> 8);
+                        out.write(bytes.length);
+                        out.write(bytes);
+                        checkpoint(false);
+                    }
+                    out.write(0xFF);
+                    out.write(0xFF);
+                    out.flush();
+
+                    // Wait for End-of-File.
+                    InputStream in = mSockets[i].getInputStream();
+                    while (true) {
+                        try {
+                            if (in.read() == -1) {
+                                break;
+                            }
+                        } catch (Exception e) {
+                            // ignore
+                        }
+                        checkpoint(true);
+                    }
+                }
+
+                // Wait for the daemons to create the new state.
+                while (!state.exists()) {
+                    // Check if a running daemon is dead.
+                    for (int i = 0; i < mDaemons.length; ++i) {
+                        String daemon = mDaemons[i];
+                        if (mArguments[i] != null && !SystemService.isRunning(daemon)) {
+                            throw new IllegalStateException(daemon + " is dead");
+                        }
+                    }
+                    checkpoint(true);
+                }
+
+                // Now we are connected. Read and parse the new state.
+                String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
+                if (parameters.length != 6) {
+                    throw new IllegalStateException("Cannot parse the state");
+                }
+
+                // Set the interface and the addresses in the config.
+                mConfig.interfaze = parameters[0].trim();
+
+                mConfig.addLegacyAddresses(parameters[1]);
+                // Set the routes if they are not set in the config.
+                if (mConfig.routes == null || mConfig.routes.isEmpty()) {
+                    mConfig.addLegacyRoutes(parameters[2]);
+                }
+
+                // Set the DNS servers if they are not set in the config.
+                if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
+                    String dnsServers = parameters[3].trim();
+                    if (!dnsServers.isEmpty()) {
+                        mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
+                    }
+                }
+
+                // Set the search domains if they are not set in the config.
+                if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
+                    String searchDomains = parameters[4].trim();
+                    if (!searchDomains.isEmpty()) {
+                        mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
+                    }
+                }
+
+                // Set the routes.
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mCallback.setMarkedForwarding(mConfig.interfaze);
+                    mCallback.setRoutes(mConfig.interfaze, mConfig.routes);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+
+                // Here is the last step and it must be done synchronously.
+                synchronized (Vpn.this) {
+                    // Set the start time
+                    mConfig.startTime = SystemClock.elapsedRealtime();
+
+                    // Check if the thread is interrupted while we are waiting.
+                    checkpoint(false);
+
+                    // Check if the interface is gone while we are waiting.
+                    if (jniCheck(mConfig.interfaze) == 0) {
+                        throw new IllegalStateException(mConfig.interfaze + " is gone");
+                    }
+
+                    // Now INetworkManagementEventObserver is watching our back.
+                    mInterface = mConfig.interfaze;
+                    mVpnUsers = new SparseBooleanArray();
+
+                    token = Binder.clearCallingIdentity();
+                    try {
+                        mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains);
+                        addVpnUserLocked(mUserId);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+
+                    // Assign all restircted users to this VPN
+                    // (Legacy VPNs are Owner only)
+                    UserManager mgr = UserManager.get(mContext);
+                    token = Binder.clearCallingIdentity();
+                    try {
+                        for (UserInfo user : mgr.getUsers()) {
+                            if (user.isRestricted()) {
+                                try {
+                                    addVpnUserLocked(user.id);
+                                } catch (Exception e) {
+                                    Log.wtf(TAG, "Failed to add user " + user.id
+                                            + " to owner's VPN");
+                                }
+                            }
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                    Log.i(TAG, "Connected!");
+                    updateState(DetailedState.CONNECTED, "execute");
+                }
+            } catch (Exception e) {
+                Log.i(TAG, "Aborting", e);
+                // make sure the routing is cleared
+                try {
+                    mCallback.clearMarkedForwarding(mConfig.interfaze);
+                } catch (Exception ignored) {
+                }
+                exit();
+            } finally {
+                // Kill the daemons if they fail to stop.
+                if (!initFinished) {
+                    for (String daemon : mDaemons) {
+                        SystemService.stop(daemon);
+                    }
+                }
+
+                // Do not leave an unstable state.
+                if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) {
+                    updateState(DetailedState.FAILED, "execute");
+                }
+            }
+        }
+
+        /**
+         * Monitor the daemons we started, moving to disconnected state if the
+         * underlying services fail.
+         */
+        private void monitorDaemons() {
+            if (!mNetworkInfo.isConnected()) {
+                return;
+            }
+
+            try {
+                while (true) {
+                    Thread.sleep(2000);
+                    for (int i = 0; i < mDaemons.length; i++) {
+                        if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) {
+                            return;
+                        }
+                    }
+                }
+            } catch (InterruptedException e) {
+                Log.d(TAG, "interrupted during monitorDaemons(); stopping services");
+            } finally {
+                for (String daemon : mDaemons) {
+                    SystemService.stop(daemon);
+                }
+
+                updateState(DetailedState.DISCONNECTED, "babysit");
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java
new file mode 100644
index 0000000..023bf2b
--- /dev/null
+++ b/services/core/java/com/android/server/content/ContentService.java
@@ -0,0 +1,975 @@
+/*
+ * Copyright (C) 2006 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.content;
+
+import android.Manifest;
+import android.accounts.Account;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentService;
+import android.content.ISyncStatusObserver;
+import android.content.PeriodicSync;
+import android.content.SyncAdapterType;
+import android.content.SyncInfo;
+import android.content.SyncRequest;
+import android.content.SyncStatusInfo;
+import android.database.IContentObserver;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseIntArray;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * {@hide}
+ */
+public final class ContentService extends IContentService.Stub {
+    private static final String TAG = "ContentService";
+    private Context mContext;
+    private boolean mFactoryTest;
+    private final ObserverNode mRootNode = new ObserverNode("");
+    private SyncManager mSyncManager = null;
+    private final Object mSyncManagerLock = new Object();
+
+    private SyncManager getSyncManager() {
+        if (SystemProperties.getBoolean("config.disable_network", false)) {
+            return null;
+        }
+
+        synchronized(mSyncManagerLock) {
+            try {
+                // Try to create the SyncManager, return null if it fails (e.g. the disk is full).
+                if (mSyncManager == null) mSyncManager = new SyncManager(mContext, mFactoryTest);
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Can't create SyncManager", e);
+            }
+            return mSyncManager;
+        }
+    }
+
+    @Override
+    protected synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.DUMP,
+                "caller doesn't have the DUMP permission");
+
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            if (mSyncManager == null) {
+                pw.println("No SyncManager created!  (Disk full?)");
+            } else {
+                mSyncManager.dump(fd, pw);
+            }
+            pw.println();
+            pw.println("Observer tree:");
+            synchronized (mRootNode) {
+                int[] counts = new int[2];
+                final SparseIntArray pidCounts = new SparseIntArray();
+                mRootNode.dumpLocked(fd, pw, args, "", "  ", counts, pidCounts);
+                pw.println();
+                ArrayList<Integer> sorted = new ArrayList<Integer>();
+                for (int i=0; i<pidCounts.size(); i++) {
+                    sorted.add(pidCounts.keyAt(i));
+                }
+                Collections.sort(sorted, new Comparator<Integer>() {
+                    @Override
+                    public int compare(Integer lhs, Integer rhs) {
+                        int lc = pidCounts.get(lhs);
+                        int rc = pidCounts.get(rhs);
+                        if (lc < rc) {
+                            return 1;
+                        } else if (lc > rc) {
+                            return -1;
+                        }
+                        return 0;
+                    }
+
+                });
+                for (int i=0; i<sorted.size(); i++) {
+                    int pid = sorted.get(i);
+                    pw.print("  pid "); pw.print(pid); pw.print(": ");
+                            pw.print(pidCounts.get(pid)); pw.println(" observers");
+                }
+                pw.println();
+                pw.print(" Total number of nodes: "); pw.println(counts[0]);
+                pw.print(" Total number of observers: "); pw.println(counts[1]);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The content service only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Content Service Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    /*package*/ ContentService(Context context, boolean factoryTest) {
+        mContext = context;
+        mFactoryTest = factoryTest;
+    }
+
+    public void systemReady() {
+        getSyncManager();
+    }
+
+    /**
+     * Register a content observer tied to a specific user's view of the provider.
+     * @param userHandle the user whose view of the provider is to be observed.  May be
+     *     the calling user without requiring any permission, otherwise the caller needs to
+     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
+     *     USER_CURRENT are properly handled; all other pseudousers are forbidden.
+     */
+    @Override
+    public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            IContentObserver observer, int userHandle) {
+        if (observer == null || uri == null) {
+            throw new IllegalArgumentException("You must pass a valid uri and observer");
+        }
+
+        final int callingUser = UserHandle.getCallingUserId();
+        if (callingUser != userHandle) {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "no permission to observe other users' provider view");
+        }
+
+        if (userHandle < 0) {
+            if (userHandle == UserHandle.USER_CURRENT) {
+                userHandle = ActivityManager.getCurrentUser();
+            } else if (userHandle != UserHandle.USER_ALL) {
+                throw new InvalidParameterException("Bad user handle for registerContentObserver: "
+                        + userHandle);
+            }
+        }
+
+        synchronized (mRootNode) {
+            mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
+                    Binder.getCallingUid(), Binder.getCallingPid(), userHandle);
+            if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
+                    " with notifyForDescendants " + notifyForDescendants);
+        }
+    }
+
+    public void registerContentObserver(Uri uri, boolean notifyForDescendants,
+            IContentObserver observer) {
+        registerContentObserver(uri, notifyForDescendants, observer,
+                UserHandle.getCallingUserId());
+    }
+
+    public void unregisterContentObserver(IContentObserver observer) {
+        if (observer == null) {
+            throw new IllegalArgumentException("You must pass a valid observer");
+        }
+        synchronized (mRootNode) {
+            mRootNode.removeObserverLocked(observer);
+            if (false) Log.v(TAG, "Unregistered observer " + observer);
+        }
+    }
+
+    /**
+     * Notify observers of a particular user's view of the provider.
+     * @param userHandle the user whose view of the provider is to be notified.  May be
+     *     the calling user without requiring any permission, otherwise the caller needs to
+     *     hold the INTERACT_ACROSS_USERS_FULL permission.  Pseudousers USER_ALL and
+     *     USER_CURRENT are properly interpreted; no other pseudousers are allowed.
+     */
+    @Override
+    public void notifyChange(Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork,
+            int userHandle) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "Notifying update of " + uri + " for user " + userHandle
+                    + " from observer " + observer + ", syncToNetwork " + syncToNetwork);
+        }
+
+        // Notify for any user other than the caller's own requires permission.
+        final int callingUserHandle = UserHandle.getCallingUserId();
+        if (userHandle != callingUserHandle) {
+            mContext.enforceCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "no permission to notify other users");
+        }
+
+        // We passed the permission check; resolve pseudouser targets as appropriate
+        if (userHandle < 0) {
+            if (userHandle == UserHandle.USER_CURRENT) {
+                userHandle = ActivityManager.getCurrentUser();
+            } else if (userHandle != UserHandle.USER_ALL) {
+                throw new InvalidParameterException("Bad user handle for notifyChange: "
+                        + userHandle);
+            }
+        }
+
+        final int uid = Binder.getCallingUid();
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
+            synchronized (mRootNode) {
+                mRootNode.collectObserversLocked(uri, 0, observer, observerWantsSelfNotifications,
+                        userHandle, calls);
+            }
+            final int numCalls = calls.size();
+            for (int i=0; i<numCalls; i++) {
+                ObserverCall oc = calls.get(i);
+                try {
+                    oc.mObserver.onChange(oc.mSelfChange, uri);
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Notified " + oc.mObserver + " of " + "update at " + uri);
+                    }
+                } catch (RemoteException ex) {
+                    synchronized (mRootNode) {
+                        Log.w(TAG, "Found dead observer, removing");
+                        IBinder binder = oc.mObserver.asBinder();
+                        final ArrayList<ObserverNode.ObserverEntry> list
+                                = oc.mNode.mObservers;
+                        int numList = list.size();
+                        for (int j=0; j<numList; j++) {
+                            ObserverNode.ObserverEntry oe = list.get(j);
+                            if (oe.observer.asBinder() == binder) {
+                                list.remove(j);
+                                j--;
+                                numList--;
+                            }
+                        }
+                    }
+                }
+            }
+            if (syncToNetwork) {
+                SyncManager syncManager = getSyncManager();
+                if (syncManager != null) {
+                    syncManager.scheduleLocalSync(null /* all accounts */, callingUserHandle, uid,
+                            uri.getAuthority());
+                }
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public void notifyChange(Uri uri, IContentObserver observer,
+            boolean observerWantsSelfNotifications, boolean syncToNetwork) {
+        notifyChange(uri, observer, observerWantsSelfNotifications, syncToNetwork,
+                UserHandle.getCallingUserId());
+    }
+
+    /**
+     * Hide this class since it is not part of api,
+     * but current unittest framework requires it to be public
+     * @hide
+     *
+     */
+    public static final class ObserverCall {
+        final ObserverNode mNode;
+        final IContentObserver mObserver;
+        final boolean mSelfChange;
+
+        ObserverCall(ObserverNode node, IContentObserver observer, boolean selfChange) {
+            mNode = node;
+            mObserver = observer;
+            mSelfChange = selfChange;
+        }
+    }
+
+    @Override
+    public void requestSync(Account account, String authority, Bundle extras) {
+        ContentResolver.validateSyncExtrasBundle(extras);
+        int userId = UserHandle.getCallingUserId();
+        int uId = Binder.getCallingUid();
+
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.scheduleSync(account, userId, uId, authority, extras,
+                        0 /* no delay */, 0 /* no delay */,
+                        false /* onlyThoseWithUnkownSyncableState */);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Request a sync with a generic {@link android.content.SyncRequest} object. This will be
+     * either:
+     *   periodic OR one-off sync.
+     * and
+     *   anonymous OR provider sync.
+     * Depending on the request, we enqueue to suit in the SyncManager.
+     * @param request The request object. Validation of this object is done by its builder.
+     */
+    @Override
+    public void sync(SyncRequest request) {
+        Bundle extras = request.getBundle();
+        long flextime = request.getSyncFlexTime();
+        long runAtTime = request.getSyncRunTime();
+        int userId = UserHandle.getCallingUserId();
+        int uId = Binder.getCallingUid();
+
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                if (request.hasAuthority()) {
+                    // Sync Adapter registered with the system - old API.
+                    final  Account account = request.getAccount();
+                    final String provider = request.getProvider();
+                    if (request.isPeriodic()) {
+                        mContext.enforceCallingOrSelfPermission(
+                                Manifest.permission.WRITE_SYNC_SETTINGS,
+                                "no permission to write the sync settings");
+                        if (runAtTime < 60) {
+                            Slog.w(TAG, "Requested poll frequency of " + runAtTime
+                                    + " seconds being rounded up to 60 seconds.");
+                            runAtTime = 60;
+                        }
+                        PeriodicSync syncToAdd =
+                                new PeriodicSync(account, provider, extras, runAtTime, flextime);
+                        getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+                    } else {
+                        long beforeRuntimeMillis = (flextime) * 1000;
+                        long runtimeMillis = runAtTime * 1000;
+                        syncManager.scheduleSync(
+                                account, userId, uId, provider, extras,
+                                beforeRuntimeMillis, runtimeMillis,
+                                false /* onlyThoseWithUnknownSyncableState */);
+                    }
+                } else {
+                    Log.w(TAG, "Unrecognised sync parameters, doing nothing.");
+                }
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Clear all scheduled sync operations that match the uri and cancel the active sync
+     * if they match the authority and account, if they are present.
+     * @param account filter the pending and active syncs to cancel using this account
+     * @param authority filter the pending and active syncs to cancel using this authority
+     */
+    @Override
+    public void cancelSync(Account account, String authority) {
+        if (authority != null && authority.length() == 0) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
+
+        int userId = UserHandle.getCallingUserId();
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.clearScheduledSyncOperations(account, userId, authority);
+                syncManager.cancelActiveSync(account, userId, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * Get information about the SyncAdapters that are known to the system.
+     * @return an array of SyncAdapters that have registered with the system
+     */
+    @Override
+    public SyncAdapterType[] getSyncAdapterTypes() {
+        // This makes it so that future permission checks will be in the context of this
+        // process rather than the caller's process. We will restore this before returning.
+        final int userId = UserHandle.getCallingUserId();
+        final long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            return syncManager.getSyncAdapterTypes(userId);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public boolean getSyncAutomatically(Account account, String providerName) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getSyncAutomatically(
+                        account, userId, providerName);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
+    @Override
+    public void setSyncAutomatically(Account account, String providerName, boolean sync) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must be non-empty");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().setSyncAutomatically(
+                        account, userId, providerName, sync);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /** Old API. Schedule periodic sync with default flex time. */
+    @Override
+    public void addPeriodicSync(Account account, String authority, Bundle extras,
+            long pollFrequency) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty.");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        if (pollFrequency < 60) {
+            Slog.w(TAG, "Requested poll frequency of " + pollFrequency
+                    + " seconds being rounded up to 60 seconds.");
+            pollFrequency = 60;
+        }
+
+        long identityToken = clearCallingIdentity();
+        try {
+            // Add default flex time to this sync.
+            PeriodicSync syncToAdd =
+                    new PeriodicSync(account, authority, extras,
+                            pollFrequency,
+                            SyncStorageEngine.calculateDefaultFlexTime(pollFrequency));
+            getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public void removePeriodicSync(Account account, String authority, Bundle extras) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
+                    0 /* Not read for removal */, 0 /* Not read for removal */);
+            getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    /**
+     * TODO: Implement.
+     * @param request Sync to remove.
+     */
+    public void removeSync(SyncRequest request) {
+
+    }
+
+    @Override
+    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+        if (account == null) {
+            throw new IllegalArgumentException("Account must not be null");
+        }
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
+                    account, userId, providerName);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public int getIsSyncable(Account account, String providerName) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+        int userId = UserHandle.getCallingUserId();
+
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getIsSyncable(
+                        account, userId, providerName);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return -1;
+    }
+
+    @Override
+    public void setIsSyncable(Account account, String providerName, int syncable) {
+        if (TextUtils.isEmpty(providerName)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().setIsSyncable(
+                        account, userId, providerName, syncable);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    @Override
+    public boolean getMasterSyncAutomatically() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
+                "no permission to read the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
+    @Override
+    public void setMasterSyncAutomatically(boolean flag) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
+                "no permission to write the sync settings");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public boolean isSyncActive(Account account, String authority) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+        int userId = UserHandle.getCallingUserId();
+
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().isSyncActive(
+                        account, userId, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
+    public List<SyncInfo> getCurrentSyncs() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            return getSyncManager().getSyncStorageEngine().getCurrentSyncsCopy(userId);
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public SyncStatusInfo getSyncStatus(Account account, String authority) {
+        if (TextUtils.isEmpty(authority)) {
+            throw new IllegalArgumentException("Authority must not be empty");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
+                        account, userId, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return null;
+    }
+
+    public boolean isSyncPending(Account account, String authority) {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
+                "no permission to read the sync stats");
+
+        int userId = UserHandle.getCallingUserId();
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null) {
+                return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+        return false;
+    }
+
+    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null && callback != null) {
+                syncManager.getSyncStorageEngine().addStatusChangeListener(mask, callback);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public void removeStatusChangeListener(ISyncStatusObserver callback) {
+        long identityToken = clearCallingIdentity();
+        try {
+            SyncManager syncManager = getSyncManager();
+            if (syncManager != null && callback != null) {
+                syncManager.getSyncStorageEngine().removeStatusChangeListener(callback);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
+    public static ContentService main(Context context, boolean factoryTest) {
+        ContentService service = new ContentService(context, factoryTest);
+        ServiceManager.addService(ContentResolver.CONTENT_SERVICE_NAME, service);
+        return service;
+    }
+
+    /**
+     * Hide this class since it is not part of api,
+     * but current unittest framework requires it to be public
+     * @hide
+     */
+    public static final class ObserverNode {
+        private class ObserverEntry implements IBinder.DeathRecipient {
+            public final IContentObserver observer;
+            public final int uid;
+            public final int pid;
+            public final boolean notifyForDescendants;
+            private final int userHandle;
+            private final Object observersLock;
+
+            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
+                    int _uid, int _pid, int _userHandle) {
+                this.observersLock = observersLock;
+                observer = o;
+                uid = _uid;
+                pid = _pid;
+                userHandle = _userHandle;
+                notifyForDescendants = n;
+                try {
+                    observer.asBinder().linkToDeath(this, 0);
+                } catch (RemoteException e) {
+                    binderDied();
+                }
+            }
+
+            public void binderDied() {
+                synchronized (observersLock) {
+                    removeObserverLocked(observer);
+                }
+            }
+
+            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+                    String name, String prefix, SparseIntArray pidCounts) {
+                pidCounts.put(pid, pidCounts.get(pid)+1);
+                pw.print(prefix); pw.print(name); pw.print(": pid=");
+                        pw.print(pid); pw.print(" uid=");
+                        pw.print(uid); pw.print(" user=");
+                        pw.print(userHandle); pw.print(" target=");
+                        pw.println(Integer.toHexString(System.identityHashCode(
+                                observer != null ? observer.asBinder() : null)));
+            }
+        }
+
+        public static final int INSERT_TYPE = 0;
+        public static final int UPDATE_TYPE = 1;
+        public static final int DELETE_TYPE = 2;
+
+        private String mName;
+        private ArrayList<ObserverNode> mChildren = new ArrayList<ObserverNode>();
+        private ArrayList<ObserverEntry> mObservers = new ArrayList<ObserverEntry>();
+
+        public ObserverNode(String name) {
+            mName = name;
+        }
+
+        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
+            String innerName = null;
+            if (mObservers.size() > 0) {
+                if ("".equals(name)) {
+                    innerName = mName;
+                } else {
+                    innerName = name + "/" + mName;
+                }
+                for (int i=0; i<mObservers.size(); i++) {
+                    counts[1]++;
+                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+                            pidCounts);
+                }
+            }
+            if (mChildren.size() > 0) {
+                if (innerName == null) {
+                    if ("".equals(name)) {
+                        innerName = mName;
+                    } else {
+                        innerName = name + "/" + mName;
+                    }
+                }
+                for (int i=0; i<mChildren.size(); i++) {
+                    counts[0]++;
+                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+                            counts, pidCounts);
+                }
+            }
+        }
+
+        private String getUriSegment(Uri uri, int index) {
+            if (uri != null) {
+                if (index == 0) {
+                    return uri.getAuthority();
+                } else {
+                    return uri.getPathSegments().get(index - 1);
+                }
+            } else {
+                return null;
+            }
+        }
+
+        private int countUriSegments(Uri uri) {
+            if (uri == null) {
+                return 0;
+            }
+            return uri.getPathSegments().size() + 1;
+        }
+
+        // Invariant:  userHandle is either a hard user number or is USER_ALL
+        public void addObserverLocked(Uri uri, IContentObserver observer,
+                boolean notifyForDescendants, Object observersLock,
+                int uid, int pid, int userHandle) {
+            addObserverLocked(uri, 0, observer, notifyForDescendants, observersLock,
+                    uid, pid, userHandle);
+        }
+
+        private void addObserverLocked(Uri uri, int index, IContentObserver observer,
+                boolean notifyForDescendants, Object observersLock,
+                int uid, int pid, int userHandle) {
+            // If this is the leaf node add the observer
+            if (index == countUriSegments(uri)) {
+                mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
+                        uid, pid, userHandle));
+                return;
+            }
+
+            // Look to see if the proper child already exists
+            String segment = getUriSegment(uri, index);
+            if (segment == null) {
+                throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
+            }
+            int N = mChildren.size();
+            for (int i = 0; i < N; i++) {
+                ObserverNode node = mChildren.get(i);
+                if (node.mName.equals(segment)) {
+                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+                            observersLock, uid, pid, userHandle);
+                    return;
+                }
+            }
+
+            // No child found, create one
+            ObserverNode node = new ObserverNode(segment);
+            mChildren.add(node);
+            node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
+                    observersLock, uid, pid, userHandle);
+        }
+
+        public boolean removeObserverLocked(IContentObserver observer) {
+            int size = mChildren.size();
+            for (int i = 0; i < size; i++) {
+                boolean empty = mChildren.get(i).removeObserverLocked(observer);
+                if (empty) {
+                    mChildren.remove(i);
+                    i--;
+                    size--;
+                }
+            }
+
+            IBinder observerBinder = observer.asBinder();
+            size = mObservers.size();
+            for (int i = 0; i < size; i++) {
+                ObserverEntry entry = mObservers.get(i);
+                if (entry.observer.asBinder() == observerBinder) {
+                    mObservers.remove(i);
+                    // We no longer need to listen for death notifications. Remove it.
+                    observerBinder.unlinkToDeath(entry, 0);
+                    break;
+                }
+            }
+
+            if (mChildren.size() == 0 && mObservers.size() == 0) {
+                return true;
+            }
+            return false;
+        }
+
+        private void collectMyObserversLocked(boolean leaf, IContentObserver observer,
+                boolean observerWantsSelfNotifications, int targetUserHandle,
+                ArrayList<ObserverCall> calls) {
+            int N = mObservers.size();
+            IBinder observerBinder = observer == null ? null : observer.asBinder();
+            for (int i = 0; i < N; i++) {
+                ObserverEntry entry = mObservers.get(i);
+
+                // Don't notify the observer if it sent the notification and isn't interested
+                // in self notifications
+                boolean selfChange = (entry.observer.asBinder() == observerBinder);
+                if (selfChange && !observerWantsSelfNotifications) {
+                    continue;
+                }
+
+                // Does this observer match the target user?
+                if (targetUserHandle == UserHandle.USER_ALL
+                        || entry.userHandle == UserHandle.USER_ALL
+                        || targetUserHandle == entry.userHandle) {
+                    // Make sure the observer is interested in the notification
+                    if (leaf || (!leaf && entry.notifyForDescendants)) {
+                        calls.add(new ObserverCall(this, entry.observer, selfChange));
+                    }
+                }
+            }
+        }
+
+        /**
+         * targetUserHandle is either a hard user handle or is USER_ALL
+         */
+        public void collectObserversLocked(Uri uri, int index, IContentObserver observer,
+                boolean observerWantsSelfNotifications, int targetUserHandle,
+                ArrayList<ObserverCall> calls) {
+            String segment = null;
+            int segmentCount = countUriSegments(uri);
+            if (index >= segmentCount) {
+                // This is the leaf node, notify all observers
+                collectMyObserversLocked(true, observer, observerWantsSelfNotifications,
+                        targetUserHandle, calls);
+            } else if (index < segmentCount){
+                segment = getUriSegment(uri, index);
+                // Notify any observers at this level who are interested in descendants
+                collectMyObserversLocked(false, observer, observerWantsSelfNotifications,
+                        targetUserHandle, calls);
+            }
+
+            int N = mChildren.size();
+            for (int i = 0; i < N; i++) {
+                ObserverNode node = mChildren.get(i);
+                if (segment == null || node.mName.equals(segment)) {
+                    // We found the child,
+                    node.collectObserversLocked(uri, index + 1,
+                            observer, observerWantsSelfNotifications, targetUserHandle, calls);
+                    if (segment != null) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
new file mode 100644
index 0000000..71d8d99
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -0,0 +1,2940 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.content;
+
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.accounts.AccountManager;
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AppGlobals;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ISyncAdapter;
+import android.content.ISyncContext;
+import android.content.ISyncStatusObserver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.PeriodicSync;
+import android.content.ServiceConnection;
+import android.content.SyncActivityTooManyDeletes;
+import android.content.SyncAdapterType;
+import android.content.SyncAdaptersCache;
+import android.content.SyncInfo;
+import android.content.SyncResult;
+import android.content.SyncStatusInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.RegisteredServicesCache;
+import android.content.pm.RegisteredServicesCacheListener;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.accounts.AccountManagerService;
+import com.android.server.content.SyncStorageEngine.AuthorityInfo;
+import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * @hide
+ */
+public class SyncManager {
+    private static final String TAG = "SyncManager";
+
+    /** Delay a sync due to local changes this long. In milliseconds */
+    private static final long LOCAL_SYNC_DELAY;
+
+    /**
+     * If a sync takes longer than this and the sync queue is not empty then we will
+     * cancel it and add it back to the end of the sync queue. In milliseconds.
+     */
+    private static final long MAX_TIME_PER_SYNC;
+
+    static {
+        final boolean isLargeRAM = !ActivityManager.isLowRamDeviceStatic();
+        int defaultMaxInitSyncs = isLargeRAM ? 5 : 2;
+        int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1;
+        MAX_SIMULTANEOUS_INITIALIZATION_SYNCS =
+                SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs);
+        MAX_SIMULTANEOUS_REGULAR_SYNCS =
+                SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs);
+        LOCAL_SYNC_DELAY =
+                SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
+        MAX_TIME_PER_SYNC =
+                SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
+        SYNC_NOTIFICATION_DELAY =
+                SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
+    }
+
+    private static final long SYNC_NOTIFICATION_DELAY;
+
+    /**
+     * When retrying a sync for the first time use this delay. After that
+     * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
+     * In milliseconds.
+     */
+    private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
+
+    /**
+     * Default the max sync retry time to this value.
+     */
+    private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
+
+    /**
+     * How long to wait before retrying a sync that failed due to one already being in progress.
+     */
+    private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
+
+    private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
+
+    private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*";
+    private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
+    private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
+
+    private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
+    private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
+
+    private Context mContext;
+
+    private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
+
+    // TODO: add better locking around mRunningAccounts
+    private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
+
+    volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
+    volatile private PowerManager.WakeLock mSyncManagerWakeLock;
+    volatile private boolean mDataConnectionIsConnected = false;
+    volatile private boolean mStorageIsLow = false;
+
+    private final NotificationManager mNotificationMgr;
+    private AlarmManager mAlarmService = null;
+
+    private SyncStorageEngine mSyncStorageEngine;
+
+    @GuardedBy("mSyncQueue")
+    private final SyncQueue mSyncQueue;
+
+    protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
+
+    // set if the sync active indicator should be reported
+    private boolean mNeedSyncActiveNotification = false;
+
+    private final PendingIntent mSyncAlarmIntent;
+    // Synchronized on "this". Instead of using this directly one should instead call
+    // its accessor, getConnManager().
+    private ConnectivityManager mConnManagerDoNotUseDirectly;
+
+    protected SyncAdaptersCache mSyncAdapters;
+
+    private BroadcastReceiver mStorageIntentReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    String action = intent.getAction();
+                    if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Internal storage is low.");
+                        }
+                        mStorageIsLow = true;
+                        cancelActiveSync(null /* any account */, UserHandle.USER_ALL,
+                                null /* any authority */);
+                    } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "Internal storage is ok.");
+                        }
+                        mStorageIsLow = false;
+                        sendCheckAlarmsMessage();
+                    }
+                }
+            };
+
+    private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mSyncHandler.onBootCompleted();
+        }
+    };
+
+    private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getConnectivityManager().getBackgroundDataSetting()) {
+                scheduleSync(null /* account */, UserHandle.USER_ALL,
+                        SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED,
+                        null /* authority */,
+                        new Bundle(), 0 /* delay */, 0 /* delay */,
+                        false /* onlyThoseWithUnknownSyncableState */);
+            }
+        }
+    };
+
+    private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateRunningAccounts();
+
+            // Kick off sync for everyone, since this was a radical account change
+            scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null,
+                    null, 0 /* no delay */, 0/* no delay */, false);
+        }
+    };
+
+    private final PowerManager mPowerManager;
+
+    // Use this as a random offset to seed all periodic syncs.
+    private int mSyncRandomOffsetMillis;
+
+    private final UserManager mUserManager;
+
+    private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
+    private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
+
+    private List<UserInfo> getAllUsers() {
+        return mUserManager.getUsers();
+    }
+
+    private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
+        boolean found = false;
+        for (int i = 0; i < accounts.length; i++) {
+            if (accounts[i].userId == userId
+                    && accounts[i].account.equals(account)) {
+                found = true;
+                break;
+            }
+        }
+        return found;
+    }
+
+    public void updateRunningAccounts() {
+        mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
+
+        if (mBootCompleted) {
+            doDatabaseCleanup();
+        }
+
+        for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+            if (!containsAccountAndUser(mRunningAccounts,
+                    currentSyncContext.mSyncOperation.account,
+                    currentSyncContext.mSyncOperation.userId)) {
+                Log.d(TAG, "canceling sync since the account is no longer running");
+                sendSyncFinishedOrCanceledMessage(currentSyncContext,
+                        null /* no result since this is a cancel */);
+            }
+        }
+
+        // we must do this since we don't bother scheduling alarms when
+        // the accounts are not set yet
+        sendCheckAlarmsMessage();
+    }
+
+    private void doDatabaseCleanup() {
+        for (UserInfo user : mUserManager.getUsers(true)) {
+            // Skip any partially created/removed users
+            if (user.partial) continue;
+            Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
+            mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
+        }
+    }
+
+    private BroadcastReceiver mConnectivityIntentReceiver =
+            new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final boolean wasConnected = mDataConnectionIsConnected;
+
+            // don't use the intent to figure out if network is connected, just check
+            // ConnectivityManager directly.
+            mDataConnectionIsConnected = readDataConnectionState();
+            if (mDataConnectionIsConnected) {
+                if (!wasConnected) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "Reconnection detected: clearing all backoffs");
+                    }
+                    synchronized(mSyncQueue) {
+                        mSyncStorageEngine.clearAllBackoffsLocked(mSyncQueue);
+                    }
+                }
+                sendCheckAlarmsMessage();
+            }
+        }
+    };
+
+    private boolean readDataConnectionState() {
+        NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo();
+        return (networkInfo != null) && networkInfo.isConnected();
+    }
+
+    private BroadcastReceiver mShutdownIntentReceiver =
+            new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.w(TAG, "Writing sync state before shutdown...");
+            getSyncStorageEngine().writeAllState();
+        }
+    };
+
+    private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+            if (userId == UserHandle.USER_NULL) return;
+
+            if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                onUserRemoved(userId);
+            } else if (Intent.ACTION_USER_STARTING.equals(action)) {
+                onUserStarting(userId);
+            } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+                onUserStopping(userId);
+            }
+        }
+    };
+
+    private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
+    private final SyncHandler mSyncHandler;
+
+    private volatile boolean mBootCompleted = false;
+
+    private ConnectivityManager getConnectivityManager() {
+        synchronized (this) {
+            if (mConnManagerDoNotUseDirectly == null) {
+                mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
+                        Context.CONNECTIVITY_SERVICE);
+            }
+            return mConnManagerDoNotUseDirectly;
+        }
+    }
+
+    /**
+     * Should only be created after {@link ContentService#systemReady()} so that
+     * {@link PackageManager} is ready to query.
+     */
+    public SyncManager(Context context, boolean factoryTest) {
+        // Initialize the SyncStorageEngine first, before registering observers
+        // and creating threads and so on; it may fail if the disk is full.
+        mContext = context;
+
+        SyncStorageEngine.init(context);
+        mSyncStorageEngine = SyncStorageEngine.getSingleton();
+        mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
+            @Override
+            public void onSyncRequest(Account account, int userId, int reason, String authority,
+                    Bundle extras) {
+                scheduleSync(account, userId, reason, authority, extras,
+                    0 /* no delay */,
+                    0 /* no delay */,
+                    false);
+            }
+        });
+
+        mSyncAdapters = new SyncAdaptersCache(mContext);
+        mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters);
+
+        mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper());
+
+        mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
+            @Override
+            public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
+                if (!removed) {
+                    scheduleSync(null, UserHandle.USER_ALL,
+                            SyncOperation.REASON_SERVICE_CHANGED,
+                            type.authority, null, 0 /* no delay */, 0 /* no delay */,
+                            false /* onlyThoseWithUnkownSyncableState */);
+                }
+            }
+        }, mSyncHandler);
+
+        mSyncAlarmIntent = PendingIntent.getBroadcast(
+                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
+
+        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
+
+        if (!factoryTest) {
+            intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
+            context.registerReceiver(mBootCompletedReceiver, intentFilter);
+        }
+
+        intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+        context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
+
+        intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
+        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
+        context.registerReceiver(mStorageIntentReceiver, intentFilter);
+
+        intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
+        intentFilter.setPriority(100);
+        context.registerReceiver(mShutdownIntentReceiver, intentFilter);
+
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_STARTING);
+        intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+        mContext.registerReceiverAsUser(
+                mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+
+        if (!factoryTest) {
+            mNotificationMgr = (NotificationManager)
+                context.getSystemService(Context.NOTIFICATION_SERVICE);
+            context.registerReceiver(new SyncAlarmIntentReceiver(),
+                    new IntentFilter(ACTION_SYNC_ALARM));
+        } else {
+            mNotificationMgr = null;
+        }
+        mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+        // This WakeLock is used to ensure that we stay awake between the time that we receive
+        // a sync alarm notification and when we finish processing it. We need to do this
+        // because we don't do the work in the alarm handler, rather we do it in a message
+        // handler.
+        mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                HANDLE_SYNC_ALARM_WAKE_LOCK);
+        mHandleAlarmWakeLock.setReferenceCounted(false);
+
+        // This WakeLock is used to ensure that we stay awake while running the sync loop
+        // message handler. Normally we will hold a sync adapter wake lock while it is being
+        // synced but during the execution of the sync loop it might finish a sync for
+        // one sync adapter before starting the sync for the other sync adapter and we
+        // don't want the device to go to sleep during that window.
+        mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                SYNC_LOOP_WAKE_LOCK);
+        mSyncManagerWakeLock.setReferenceCounted(false);
+
+        mSyncStorageEngine.addStatusChangeListener(
+                ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
+            @Override
+            public void onStatusChanged(int which) {
+                // force the sync loop to run if the settings change
+                sendCheckAlarmsMessage();
+            }
+        });
+
+        if (!factoryTest) {
+            // Register for account list updates for all users
+            mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
+                    UserHandle.ALL,
+                    new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
+                    null, null);
+        }
+
+        // Pick a random second in a day to seed all periodic syncs
+        mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
+    }
+
+    /**
+     * Return a random value v that satisfies minValue <= v < maxValue. The difference between
+     * maxValue and minValue must be less than Integer.MAX_VALUE.
+     */
+    private long jitterize(long minValue, long maxValue) {
+        Random random = new Random(SystemClock.elapsedRealtime());
+        long spread = maxValue - minValue;
+        if (spread > Integer.MAX_VALUE) {
+            throw new IllegalArgumentException("the difference between the maxValue and the "
+                    + "minValue must be less than " + Integer.MAX_VALUE);
+        }
+        return minValue + random.nextInt((int)spread);
+    }
+
+    public SyncStorageEngine getSyncStorageEngine() {
+        return mSyncStorageEngine;
+    }
+
+    public int getIsSyncable(Account account, int userId, String providerName) {
+        int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
+        UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
+
+        // If it's not a restricted user, return isSyncable
+        if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
+
+        // Else check if the sync adapter has opted-in or not
+        RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
+                mSyncAdapters.getServiceInfo(
+                SyncAdapterType.newKey(providerName, account.type), userId);
+        if (syncAdapterInfo == null) return isSyncable;
+
+        PackageInfo pInfo = null;
+        try {
+            pInfo = AppGlobals.getPackageManager().getPackageInfo(
+                syncAdapterInfo.componentName.getPackageName(), 0, userId);
+            if (pInfo == null) return isSyncable;
+        } catch (RemoteException re) {
+            // Shouldn't happen
+            return isSyncable;
+        }
+        if (pInfo.restrictedAccountType != null
+                && pInfo.restrictedAccountType.equals(account.type)) {
+            return isSyncable;
+        } else {
+            return 0;
+        }
+    }
+
+    private void ensureAlarmService() {
+        if (mAlarmService == null) {
+            mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        }
+    }
+
+    /**
+     * Initiate a sync. This can start a sync for all providers
+     * (pass null to url, set onlyTicklable to false), only those
+     * providers that are marked as ticklable (pass null to url,
+     * set onlyTicklable to true), or a specific provider (set url
+     * to the content url of the provider).
+     *
+     * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
+     * true then initiate a sync that just checks for local changes to send
+     * to the server, otherwise initiate a sync that first gets any
+     * changes from the server before sending local changes back to
+     * the server.
+     *
+     * <p>If a specific provider is being synced (the url is non-null)
+     * then the extras can contain SyncAdapter-specific information
+     * to control what gets synced (e.g. which specific feed to sync).
+     *
+     * <p>You'll start getting callbacks after this.
+     *
+     * @param requestedAccount the account to sync, may be null to signify all accounts
+     * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
+     *          then all users' accounts are considered.
+     * @param reason for sync request. If this is a positive integer, it is the Linux uid
+     * assigned to the process that requested the sync. If it's negative, the sync was requested by
+     * the SyncManager itself and could be one of the following:
+     *      {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED}
+     *      {@link SyncOperation#REASON_ACCOUNTS_UPDATED}
+     *      {@link SyncOperation#REASON_SERVICE_CHANGED}
+     *      {@link SyncOperation#REASON_PERIODIC}
+     *      {@link SyncOperation#REASON_IS_SYNCABLE}
+     *      {@link SyncOperation#REASON_SYNC_AUTO}
+     *      {@link SyncOperation#REASON_MASTER_SYNC_AUTO}
+     *      {@link SyncOperation#REASON_USER_START}
+     * @param requestedAuthority the authority to sync, may be null to indicate all authorities
+     * @param extras a Map of SyncAdapter-specific information to control
+     *          syncs of a specific provider. Can be null. Is ignored
+     *          if the url is null.
+     * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run.
+     * @param runtimeMillis maximum milliseconds in the future to wait before performing sync.
+     * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state.
+     */
+    public void scheduleSync(Account requestedAccount, int userId, int reason,
+            String requestedAuthority, Bundle extras, long beforeRuntimeMillis,
+            long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
+        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+
+        final boolean backgroundDataUsageAllowed = !mBootCompleted ||
+                getConnectivityManager().getBackgroundDataSetting();
+
+        if (extras == null) {
+            extras = new Bundle();
+        }
+        if (isLoggable) {
+            Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " "
+                    + requestedAuthority);
+        }
+        Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+        if (expedited) {
+            runtimeMillis = -1; // this means schedule at the front of the queue
+        }
+
+        AccountAndUser[] accounts;
+        if (requestedAccount != null && userId != UserHandle.USER_ALL) {
+            accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
+        } else {
+            // if the accounts aren't configured yet then we can't support an account-less
+            // sync request
+            accounts = mRunningAccounts;
+            if (accounts.length == 0) {
+                if (isLoggable) {
+                    Log.v(TAG, "scheduleSync: no accounts configured, dropping");
+                }
+                return;
+            }
+        }
+
+        final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+        final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+        if (manualSync) {
+            extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+            extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+        }
+        final boolean ignoreSettings =
+                extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+
+        int source;
+        if (uploadOnly) {
+            source = SyncStorageEngine.SOURCE_LOCAL;
+        } else if (manualSync) {
+            source = SyncStorageEngine.SOURCE_USER;
+        } else if (requestedAuthority == null) {
+            source = SyncStorageEngine.SOURCE_POLL;
+        } else {
+            // this isn't strictly server, since arbitrary callers can (and do) request
+            // a non-forced two-way sync on a specific url
+            source = SyncStorageEngine.SOURCE_SERVER;
+        }
+
+        for (AccountAndUser account : accounts) {
+            // Compile a list of authorities that have sync adapters.
+            // For each authority sync each account that matches a sync adapter.
+            final HashSet<String> syncableAuthorities = new HashSet<String>();
+            for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
+                    mSyncAdapters.getAllServices(account.userId)) {
+                syncableAuthorities.add(syncAdapter.type.authority);
+            }
+
+            // if the url was specified then replace the list of authorities
+            // with just this authority or clear it if this authority isn't
+            // syncable
+            if (requestedAuthority != null) {
+                final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
+                syncableAuthorities.clear();
+                if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
+            }
+
+            for (String authority : syncableAuthorities) {
+                int isSyncable = getIsSyncable(account.account, account.userId,
+                        authority);
+                if (isSyncable == 0) {
+                    continue;
+                }
+                final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+                syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                        SyncAdapterType.newKey(authority, account.account.type), account.userId);
+                if (syncAdapterInfo == null) {
+                    continue;
+                }
+                final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
+                final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
+                if (isSyncable < 0 && isAlwaysSyncable) {
+                    mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
+                    isSyncable = 1;
+                }
+                if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
+                    continue;
+                }
+                if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
+                    continue;
+                }
+
+                // always allow if the isSyncable state is unknown
+                boolean syncAllowed =
+                        (isSyncable < 0)
+                        || ignoreSettings
+                        || (backgroundDataUsageAllowed
+                                && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+                                && mSyncStorageEngine.getSyncAutomatically(account.account,
+                                        account.userId, authority));
+                if (!syncAllowed) {
+                    if (isLoggable) {
+                        Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+                                + " is not allowed, dropping request");
+                    }
+                    continue;
+                }
+
+                Pair<Long, Long> backoff = mSyncStorageEngine
+                        .getBackoff(account.account, account.userId, authority);
+                long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
+                        account.userId, authority);
+                final long backoffTime = backoff != null ? backoff.first : 0;
+                if (isSyncable < 0) {
+                    // Initialisation sync.
+                    Bundle newExtras = new Bundle();
+                    newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+                    if (isLoggable) {
+                        Log.v(TAG, "schedule initialisation Sync:"
+                                + ", delay until " + delayUntil
+                                + ", run by " + 0
+                                + ", source " + source
+                                + ", account " + account
+                                + ", authority " + authority
+                                + ", extras " + newExtras);
+                    }
+                    scheduleSyncOperation(
+                            new SyncOperation(account.account, account.userId, reason, source,
+                                    authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
+                                    backoffTime, delayUntil, allowParallelSyncs));
+                }
+                if (!onlyThoseWithUnkownSyncableState) {
+                    if (isLoggable) {
+                        Log.v(TAG, "scheduleSync:"
+                                + " delay until " + delayUntil
+                                + " run by " + runtimeMillis
+                                + " flex " + beforeRuntimeMillis
+                                + ", source " + source
+                                + ", account " + account
+                                + ", authority " + authority
+                                + ", extras " + extras);
+                    }
+                    scheduleSyncOperation(
+                            new SyncOperation(account.account, account.userId, reason, source,
+                                    authority, extras, runtimeMillis, beforeRuntimeMillis,
+                                    backoffTime, delayUntil, allowParallelSyncs));
+                }
+            }
+        }
+    }
+
+    /**
+     * Schedule sync based on local changes to a provider. Occurs within interval
+     * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY].
+     */
+    public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
+        final Bundle extras = new Bundle();
+        extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
+        scheduleSync(account, userId, reason, authority, extras,
+                LOCAL_SYNC_DELAY /* earliest run time */,
+                2 * LOCAL_SYNC_DELAY /* latest sync time. */,
+                false /* onlyThoseWithUnkownSyncableState */);
+    }
+
+    public SyncAdapterType[] getSyncAdapterTypes(int userId) {
+        final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
+        serviceInfos = mSyncAdapters.getAllServices(userId);
+        SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
+        int i = 0;
+        for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
+            types[i] = serviceInfo.type;
+            ++i;
+        }
+        return types;
+    }
+
+    private void sendSyncAlarmMessage() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
+        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
+    }
+
+    private void sendCheckAlarmsMessage() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
+        mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
+        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
+    }
+
+    private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
+            SyncResult syncResult) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
+        Message msg = mSyncHandler.obtainMessage();
+        msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
+        msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
+        mSyncHandler.sendMessage(msg);
+    }
+
+    private void sendCancelSyncsMessage(final Account account, final int userId,
+            final String authority) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
+        Message msg = mSyncHandler.obtainMessage();
+        msg.what = SyncHandler.MESSAGE_CANCEL;
+        msg.obj = Pair.create(account, authority);
+        msg.arg1 = userId;
+        mSyncHandler.sendMessage(msg);
+    }
+
+    class SyncHandlerMessagePayload {
+        public final ActiveSyncContext activeSyncContext;
+        public final SyncResult syncResult;
+
+        SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
+            this.activeSyncContext = syncContext;
+            this.syncResult = syncResult;
+        }
+    }
+
+    class SyncAlarmIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mHandleAlarmWakeLock.acquire();
+            sendSyncAlarmMessage();
+        }
+    }
+
+    private void clearBackoffSetting(SyncOperation op) {
+        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
+        }
+    }
+
+    private void increaseBackoffSetting(SyncOperation op) {
+        // TODO: Use this function to align it to an already scheduled sync
+        //       operation in the specified window
+        final long now = SystemClock.elapsedRealtime();
+
+        final Pair<Long, Long> previousSettings =
+                mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
+        long newDelayInMs = -1;
+        if (previousSettings != null) {
+            // don't increase backoff before current backoff is expired. This will happen for op's
+            // with ignoreBackoff set.
+            if (now < previousSettings.first) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "Still in backoff, do not increase it. "
+                        + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds.");
+                }
+                return;
+            }
+            // Subsequent delays are the double of the previous delay
+            newDelayInMs = previousSettings.second * 2;
+        }
+        if (newDelayInMs <= 0) {
+            // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
+            newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
+                    (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
+        }
+
+        // Cap the delay
+        long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
+                DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
+        if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
+            newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
+        }
+
+        final long backoff = now + newDelayInMs;
+
+        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
+                backoff, newDelayInMs);
+
+        op.backoff = backoff;
+        op.updateEffectiveRunTime();
+
+        synchronized (mSyncQueue) {
+            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
+        }
+    }
+
+    private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
+        final long delayUntil = delayUntilSeconds * 1000;
+        final long absoluteNow = System.currentTimeMillis();
+        long newDelayUntilTime;
+        if (delayUntil > absoluteNow) {
+            newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
+        } else {
+            newDelayUntilTime = 0;
+        }
+        mSyncStorageEngine
+                .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
+        synchronized (mSyncQueue) {
+            mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
+        }
+    }
+
+    /**
+     * Cancel the active sync if it matches the authority and account.
+     * @param account limit the cancelations to syncs with this account, if non-null
+     * @param authority limit the cancelations to syncs with this authority, if non-null
+     */
+    public void cancelActiveSync(Account account, int userId, String authority) {
+        sendCancelSyncsMessage(account, userId, authority);
+    }
+
+    /**
+     * Create and schedule a SyncOperation.
+     *
+     * @param syncOperation the SyncOperation to schedule
+     */
+    public void scheduleSyncOperation(SyncOperation syncOperation) {
+        boolean queueChanged;
+        synchronized (mSyncQueue) {
+            queueChanged = mSyncQueue.add(syncOperation);
+        }
+
+        if (queueChanged) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
+            }
+            sendCheckAlarmsMessage();
+        } else {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
+                        + syncOperation);
+            }
+        }
+    }
+
+    /**
+     * Remove scheduled sync operations.
+     * @param account limit the removals to operations with this account, if non-null
+     * @param authority limit the removals to operations with this authority, if non-null
+     */
+    public void clearScheduledSyncOperations(Account account, int userId, String authority) {
+        synchronized (mSyncQueue) {
+            mSyncQueue.remove(account, userId, authority);
+        }
+        mSyncStorageEngine.setBackoff(account, userId, authority,
+                SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
+    }
+
+    void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
+        boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
+        if (isLoggable) {
+            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
+        }
+
+        operation = new SyncOperation(operation);
+
+        // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
+        // request. Retries of the request will always honor the backoff, so clear the
+        // flag in case we retry this request.
+        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
+            operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+        }
+
+        // If this sync aborted because the internal sync loop retried too many times then
+        //   don't reschedule. Otherwise we risk getting into a retry loop.
+        // If the operation succeeded to some extent then retry immediately.
+        // If this was a two-way sync then retry soft errors with an exponential backoff.
+        // If this was an upward sync then schedule a two-way sync immediately.
+        // Otherwise do not reschedule.
+        if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
+            Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
+                    + operation);
+        } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
+                && !syncResult.syncAlreadyInProgress) {
+            operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
+            Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
+                    + "encountered an error: " + operation);
+            scheduleSyncOperation(operation);
+        } else if (syncResult.tooManyRetries) {
+            Log.d(TAG, "not retrying sync operation because it retried too many times: "
+                    + operation);
+        } else if (syncResult.madeSomeProgress()) {
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation because even though it had an error "
+                        + "it achieved some success");
+            }
+            scheduleSyncOperation(operation);
+        } else if (syncResult.syncAlreadyInProgress) {
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation that failed because there was already a "
+                        + "sync in progress: " + operation);
+            }
+            scheduleSyncOperation(
+                new SyncOperation(
+                    operation.account, operation.userId,
+                    operation.reason,
+                    operation.syncSource,
+                    operation.authority, operation.extras,
+                    DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime,
+                    operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
+        } else if (syncResult.hasSoftError()) {
+            if (isLoggable) {
+                Log.d(TAG, "retrying sync operation because it encountered a soft error: "
+                        + operation);
+            }
+            scheduleSyncOperation(operation);
+        } else {
+            Log.d(TAG, "not retrying sync operation because the error is a hard error: "
+                    + operation);
+        }
+    }
+
+    private void onUserStarting(int userId) {
+        // Make sure that accounts we're about to use are valid
+        AccountManagerService.getSingleton().validateAccounts(userId);
+
+        mSyncAdapters.invalidateCache(userId);
+
+        updateRunningAccounts();
+
+        synchronized (mSyncQueue) {
+            mSyncQueue.addPendingOperations(userId);
+        }
+
+        // Schedule sync for any accounts under started user
+        final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
+        for (Account account : accounts) {
+            scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
+                    0 /* no delay */, 0 /* No flex */,
+                    true /* onlyThoseWithUnknownSyncableState */);
+        }
+
+        sendCheckAlarmsMessage();
+    }
+
+    private void onUserStopping(int userId) {
+        updateRunningAccounts();
+
+        cancelActiveSync(
+                null /* any account */,
+                userId,
+                null /* any authority */);
+    }
+
+    private void onUserRemoved(int userId) {
+        updateRunningAccounts();
+
+        // Clean up the storage engine database
+        mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+        synchronized (mSyncQueue) {
+            mSyncQueue.removeUser(userId);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    class ActiveSyncContext extends ISyncContext.Stub
+            implements ServiceConnection, IBinder.DeathRecipient {
+        final SyncOperation mSyncOperation;
+        final long mHistoryRowId;
+        ISyncAdapter mSyncAdapter;
+        final long mStartTime;
+        long mTimeoutStartTime;
+        boolean mBound;
+        final PowerManager.WakeLock mSyncWakeLock;
+        final int mSyncAdapterUid;
+        SyncInfo mSyncInfo;
+        boolean mIsLinkedToDeath = false;
+
+        /**
+         * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
+         * sync adapter. Since this grabs the wakelock you need to be sure to call
+         * close() when you are done with this ActiveSyncContext, whether the sync succeeded
+         * or not.
+         * @param syncOperation the SyncOperation we are about to sync
+         * @param historyRowId the row in which to record the history info for this sync
+         * @param syncAdapterUid the UID of the application that contains the sync adapter
+         * for this sync. This is used to attribute the wakelock hold to that application.
+         */
+        public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
+                int syncAdapterUid) {
+            super();
+            mSyncAdapterUid = syncAdapterUid;
+            mSyncOperation = syncOperation;
+            mHistoryRowId = historyRowId;
+            mSyncAdapter = null;
+            mStartTime = SystemClock.elapsedRealtime();
+            mTimeoutStartTime = mStartTime;
+            mSyncWakeLock = mSyncHandler.getSyncWakeLock(
+                    mSyncOperation.account, mSyncOperation.authority);
+            mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
+            mSyncWakeLock.acquire();
+        }
+
+        public void sendHeartbeat() {
+            // heartbeats are no longer used
+        }
+
+        public void onFinished(SyncResult result) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
+            // include "this" in the message so that the handler can ignore it if this
+            // ActiveSyncContext is no longer the mActiveSyncContext at message handling
+            // time
+            sendSyncFinishedOrCanceledMessage(this, result);
+        }
+
+        public void toString(StringBuilder sb) {
+            sb.append("startTime ").append(mStartTime)
+                    .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
+                    .append(", mHistoryRowId ").append(mHistoryRowId)
+                    .append(", syncOperation ").append(mSyncOperation);
+        }
+
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Message msg = mSyncHandler.obtainMessage();
+            msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
+            msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
+            mSyncHandler.sendMessage(msg);
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            Message msg = mSyncHandler.obtainMessage();
+            msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
+            msg.obj = new ServiceConnectionData(this, null);
+            mSyncHandler.sendMessage(msg);
+        }
+
+        boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info, int userId) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
+            }
+            Intent intent = new Intent();
+            intent.setAction("android.content.SyncAdapter");
+            intent.setComponent(info.componentName);
+            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                    com.android.internal.R.string.sync_binding_label);
+            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
+                    mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
+                    null, new UserHandle(userId)));
+            mBound = true;
+            final boolean bindResult = mContext.bindServiceAsUser(intent, this,
+                    Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+                    | Context.BIND_ALLOW_OOM_MANAGEMENT,
+                    new UserHandle(mSyncOperation.userId));
+            if (!bindResult) {
+                mBound = false;
+            }
+            return bindResult;
+        }
+
+        /**
+         * Performs the required cleanup, which is the releasing of the wakelock and
+         * unbinding from the sync adapter (if actually bound).
+         */
+        protected void close() {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
+            }
+            if (mBound) {
+                mBound = false;
+                mContext.unbindService(this);
+            }
+            mSyncWakeLock.release();
+            mSyncWakeLock.setWorkSource(null);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            toString(sb);
+            return sb.toString();
+        }
+
+        @Override
+        public void binderDied() {
+            sendSyncFinishedOrCanceledMessage(this, null);
+        }
+    }
+
+    protected void dump(FileDescriptor fd, PrintWriter pw) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        dumpSyncState(ipw);
+        dumpSyncHistory(ipw);
+        dumpSyncAdapters(ipw);
+    }
+
+    static String formatTime(long time) {
+        Time tobj = new Time();
+        tobj.set(time);
+        return tobj.format("%Y-%m-%d %H:%M:%S");
+    }
+
+    protected void dumpSyncState(PrintWriter pw) {
+        pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
+        pw.print("auto sync: ");
+        List<UserInfo> users = getAllUsers();
+        if (users != null) {
+            for (UserInfo user : users) {
+                pw.print("u" + user.id + "="
+                        + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " ");
+            }
+            pw.println();
+        }
+        pw.print("memory low: "); pw.println(mStorageIsLow);
+
+        final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
+
+        pw.print("accounts: ");
+        if (accounts != INITIAL_ACCOUNTS_ARRAY) {
+            pw.println(accounts.length);
+        } else {
+            pw.println("not known yet");
+        }
+        final long now = SystemClock.elapsedRealtime();
+        pw.print("now: "); pw.print(now);
+        pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
+        pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
+        pw.println(" (HH:MM:SS)");
+        pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
+                pw.println(" (HH:MM:SS)");
+        pw.print("time spent syncing: ");
+                pw.print(DateUtils.formatElapsedTime(
+                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
+                pw.print(" (HH:MM:SS), sync ");
+                pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
+                pw.println("in progress");
+        if (mSyncHandler.mAlarmScheduleTime != null) {
+            pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
+                    pw.print(" (");
+                    pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
+                    pw.println(" (HH:MM:SS) from now)");
+        } else {
+            pw.println("no alarm is scheduled (there had better not be any pending syncs)");
+        }
+
+        pw.print("notification info: ");
+        final StringBuilder sb = new StringBuilder();
+        mSyncHandler.mSyncNotificationInfo.toString(sb);
+        pw.println(sb.toString());
+
+        pw.println();
+        pw.println("Active Syncs: " + mActiveSyncContexts.size());
+        final PackageManager pm = mContext.getPackageManager();
+        for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+            final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
+            pw.print("  ");
+            pw.print(DateUtils.formatElapsedTime(durationInSeconds));
+            pw.print(" - ");
+            pw.print(activeSyncContext.mSyncOperation.dump(pm, false));
+            pw.println();
+        }
+
+        synchronized (mSyncQueue) {
+            sb.setLength(0);
+            mSyncQueue.dump(sb);
+            // Dump Pending Operations.
+            getSyncStorageEngine().dumpPendingOperations(sb);
+        }
+
+        pw.println();
+        pw.print(sb.toString());
+
+        // join the installed sync adapter with the accounts list and emit for everything
+        pw.println();
+        pw.println("Sync Status");
+        for (AccountAndUser account : accounts) {
+            pw.printf("Account %s u%d %s\n",
+                    account.account.name, account.userId, account.account.type);
+
+            pw.println("=======================================================================");
+            final PrintTable table = new PrintTable(13);
+            table.set(0, 0,
+                    "Authority", // 0
+                    "Syncable",  // 1
+                    "Enabled",   // 2
+                    "Delay",     // 3
+                    "Loc",       // 4
+                    "Poll",      // 5
+                    "Per",       // 6
+                    "Serv",      // 7
+                    "User",      // 8
+                    "Tot",       // 9
+                    "Time",      // 10
+                    "Last Sync", // 11
+                    "Periodic"   // 12
+            );
+
+            final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted =
+                    Lists.newArrayList();
+            sorted.addAll(mSyncAdapters.getAllServices(account.userId));
+            Collections.sort(sorted,
+                    new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() {
+                @Override
+                public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs,
+                        RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) {
+                    return lhs.type.authority.compareTo(rhs.type.authority);
+                }
+            });
+            for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) {
+                if (!syncAdapterType.type.accountType.equals(account.account.type)) {
+                    continue;
+                }
+                int row = table.getNumRows();
+                Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus = 
+                        mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus(
+                                account.account, account.userId, syncAdapterType.type.authority);
+                SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first;
+                SyncStatusInfo status = syncAuthoritySyncStatus.second;
+
+                String authority = settings.authority;
+                if (authority.length() > 50) {
+                    authority = authority.substring(authority.length() - 50);
+                }
+                table.set(row, 0, authority, settings.syncable, settings.enabled);
+                table.set(row, 4,
+                        status.numSourceLocal,
+                        status.numSourcePoll,
+                        status.numSourcePeriodic,
+                        status.numSourceServer,
+                        status.numSourceUser,
+                        status.numSyncs,
+                        DateUtils.formatElapsedTime(status.totalElapsedTime / 1000));
+
+
+                for (int i = 0; i < settings.periodicSyncs.size(); i++) {
+                    final PeriodicSync sync = settings.periodicSyncs.get(i);
+                    final String period =
+                            String.format("[p:%d s, f: %d s]", sync.period, sync.flexTime);
+                    final String extras =
+                            sync.extras.size() > 0 ?
+                                    sync.extras.toString() : "Bundle[]";
+                    final String next = "Next sync: " + formatTime(status.getPeriodicSyncTime(i)
+                            + sync.period * 1000);
+                    table.set(row + i * 2, 12, period + " " + extras);
+                    table.set(row + i * 2 + 1, 12, next);
+                }
+
+                int row1 = row;
+                if (settings.delayUntil > now) {
+                    table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000);
+                    if (settings.backoffTime > now) {
+                        table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000);
+                        table.set(row1++, 12, settings.backoffDelay / 1000);
+                    }
+                }
+
+                if (status.lastSuccessTime != 0) {
+                    table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource]
+                            + " " + "SUCCESS");
+                    table.set(row1++, 11, formatTime(status.lastSuccessTime));
+                }
+                if (status.lastFailureTime != 0) {
+                    table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource]
+                            + " " + "FAILURE");
+                    table.set(row1++, 11, formatTime(status.lastFailureTime));
+                    //noinspection UnusedAssignment
+                    table.set(row1++, 11, status.lastFailureMesg);
+                }
+            }
+            table.writeTo(pw);
+        }
+    }
+
+    private String getLastFailureMessage(int code) {
+        switch (code) {
+            case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS:
+                return "sync already in progress";
+
+            case ContentResolver.SYNC_ERROR_AUTHENTICATION:
+                return "authentication error";
+
+            case ContentResolver.SYNC_ERROR_IO:
+                return "I/O error";
+
+            case ContentResolver.SYNC_ERROR_PARSE:
+                return "parse error";
+
+            case ContentResolver.SYNC_ERROR_CONFLICT:
+                return "conflict error";
+
+            case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS:
+                return "too many deletions error";
+
+            case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES:
+                return "too many retries error";
+
+            case ContentResolver.SYNC_ERROR_INTERNAL:
+                return "internal error";
+
+            default:
+                return "unknown";
+        }
+    }
+
+    private void dumpTimeSec(PrintWriter pw, long time) {
+        pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
+        pw.print('s');
+    }
+
+    private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
+        pw.print("Success ("); pw.print(ds.successCount);
+        if (ds.successCount > 0) {
+            pw.print(" for "); dumpTimeSec(pw, ds.successTime);
+            pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
+        }
+        pw.print(") Failure ("); pw.print(ds.failureCount);
+        if (ds.failureCount > 0) {
+            pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
+            pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
+        }
+        pw.println(")");
+    }
+
+    protected void dumpSyncHistory(PrintWriter pw) {
+        dumpRecentHistory(pw);
+        dumpDayStatistics(pw);
+    }
+
+    private void dumpRecentHistory(PrintWriter pw) {
+        final ArrayList<SyncStorageEngine.SyncHistoryItem> items
+                = mSyncStorageEngine.getSyncHistory();
+        if (items != null && items.size() > 0) {
+            final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap();
+            long totalElapsedTime = 0;
+            long totalTimes = 0;
+            final int N = items.size();
+
+            int maxAuthority = 0;
+            int maxAccount = 0;
+            for (SyncStorageEngine.SyncHistoryItem item : items) {
+                SyncStorageEngine.AuthorityInfo authority
+                        = mSyncStorageEngine.getAuthority(item.authorityId);
+                final String authorityName;
+                final String accountKey;
+                if (authority != null) {
+                    authorityName = authority.authority;
+                    accountKey = authority.account.name + "/" + authority.account.type
+                            + " u" + authority.userId;
+                } else {
+                    authorityName = "Unknown";
+                    accountKey = "Unknown";
+                }
+
+                int length = authorityName.length();
+                if (length > maxAuthority) {
+                    maxAuthority = length;
+                }
+                length = accountKey.length();
+                if (length > maxAccount) {
+                    maxAccount = length;
+                }
+
+                final long elapsedTime = item.elapsedTime;
+                totalElapsedTime += elapsedTime;
+                totalTimes++;
+                AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName);
+                if (authoritySyncStats == null) {
+                    authoritySyncStats = new AuthoritySyncStats(authorityName);
+                    authorityMap.put(authorityName, authoritySyncStats);
+                }
+                authoritySyncStats.elapsedTime += elapsedTime;
+                authoritySyncStats.times++;
+                final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap;
+                AccountSyncStats accountSyncStats = accountMap.get(accountKey);
+                if (accountSyncStats == null) {
+                    accountSyncStats = new AccountSyncStats(accountKey);
+                    accountMap.put(accountKey, accountSyncStats);
+                }
+                accountSyncStats.elapsedTime += elapsedTime;
+                accountSyncStats.times++;
+
+            }
+
+            if (totalElapsedTime > 0) {
+                pw.println();
+                pw.printf("Detailed Statistics (Recent history):  "
+                        + "%d (# of times) %ds (sync time)\n",
+                        totalTimes, totalElapsedTime / 1000);
+
+                final List<AuthoritySyncStats> sortedAuthorities =
+                        new ArrayList<AuthoritySyncStats>(authorityMap.values());
+                Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() {
+                    @Override
+                    public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) {
+                        // reverse order
+                        int compare = Integer.compare(rhs.times, lhs.times);
+                        if (compare == 0) {
+                            compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
+                        }
+                        return compare;
+                    }
+                });
+
+                final int maxLength = Math.max(maxAuthority, maxAccount + 3);
+                final int padLength = 2 + 2 + maxLength + 2 + 10 + 11;
+                final char chars[] = new char[padLength];
+                Arrays.fill(chars, '-');
+                final String separator = new String(chars);
+
+                final String authorityFormat =
+                        String.format("  %%-%ds: %%-9s  %%-11s\n", maxLength + 2);
+                final String accountFormat =
+                        String.format("    %%-%ds:   %%-9s  %%-11s\n", maxLength);
+
+                pw.println(separator);
+                for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) {
+                    String name = authoritySyncStats.name;
+                    long elapsedTime;
+                    int times;
+                    String timeStr;
+                    String timesStr;
+
+                    elapsedTime = authoritySyncStats.elapsedTime;
+                    times = authoritySyncStats.times;
+                    timeStr = String.format("%ds/%d%%",
+                            elapsedTime / 1000,
+                            elapsedTime * 100 / totalElapsedTime);
+                    timesStr = String.format("%d/%d%%",
+                            times,
+                            times * 100 / totalTimes);
+                    pw.printf(authorityFormat, name, timesStr, timeStr);
+
+                    final List<AccountSyncStats> sortedAccounts =
+                            new ArrayList<AccountSyncStats>(
+                                    authoritySyncStats.accountMap.values());
+                    Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
+                        @Override
+                        public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
+                            // reverse order
+                            int compare = Integer.compare(rhs.times, lhs.times);
+                            if (compare == 0) {
+                                compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
+                            }
+                            return compare;
+                        }
+                    });
+                    for (AccountSyncStats stats: sortedAccounts) {
+                        elapsedTime = stats.elapsedTime;
+                        times = stats.times;
+                        timeStr = String.format("%ds/%d%%",
+                                elapsedTime / 1000,
+                                elapsedTime * 100 / totalElapsedTime);
+                        timesStr = String.format("%d/%d%%",
+                                times,
+                                times * 100 / totalTimes);
+                        pw.printf(accountFormat, stats.name, timesStr, timeStr);
+                    }
+                    pw.println(separator);
+                }
+            }
+
+            pw.println();
+            pw.println("Recent Sync History");
+            final String format = "  %-" + maxAccount + "s  %-" + maxAuthority + "s %s\n";
+            final Map<String, Long> lastTimeMap = Maps.newHashMap();
+            final PackageManager pm = mContext.getPackageManager();
+            for (int i = 0; i < N; i++) {
+                SyncStorageEngine.SyncHistoryItem item = items.get(i);
+                SyncStorageEngine.AuthorityInfo authority
+                        = mSyncStorageEngine.getAuthority(item.authorityId);
+                final String authorityName;
+                final String accountKey;
+                if (authority != null) {
+                    authorityName = authority.authority;
+                    accountKey = authority.account.name + "/" + authority.account.type
+                            + " u" + authority.userId;
+                } else {
+                    authorityName = "Unknown";
+                    accountKey = "Unknown";
+                }
+                final long elapsedTime = item.elapsedTime;
+                final Time time = new Time();
+                final long eventTime = item.eventTime;
+                time.set(eventTime);
+
+                final String key = authorityName + "/" + accountKey;
+                final Long lastEventTime = lastTimeMap.get(key);
+                final String diffString;
+                if (lastEventTime == null) {
+                    diffString = "";
+                } else {
+                    final long diff = (lastEventTime - eventTime) / 1000;
+                    if (diff < 60) {
+                        diffString = String.valueOf(diff);
+                    } else if (diff < 3600) {
+                        diffString = String.format("%02d:%02d", diff / 60, diff % 60);
+                    } else {
+                        final long sec = diff % 3600;
+                        diffString = String.format("%02d:%02d:%02d",
+                                diff / 3600, sec / 60, sec % 60);
+                    }
+                }
+                lastTimeMap.put(key, eventTime);
+
+                pw.printf("  #%-3d: %s %8s  %5.1fs  %8s",
+                        i + 1,
+                        formatTime(eventTime),
+                        SyncStorageEngine.SOURCES[item.source],
+                        ((float) elapsedTime) / 1000,
+                        diffString);
+                pw.printf(format, accountKey, authorityName,
+                        SyncOperation.reasonToString(pm, item.reason));
+
+                if (item.event != SyncStorageEngine.EVENT_STOP
+                        || item.upstreamActivity != 0
+                        || item.downstreamActivity != 0) {
+                    pw.printf("    event=%d upstreamActivity=%d downstreamActivity=%d\n",
+                            item.event,
+                            item.upstreamActivity,
+                            item.downstreamActivity);
+                }
+                if (item.mesg != null
+                        && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
+                    pw.printf("    mesg=%s\n", item.mesg);
+                }
+            }
+            pw.println();
+            pw.println("Recent Sync History Extras");
+            for (int i = 0; i < N; i++) {
+                final SyncStorageEngine.SyncHistoryItem item = items.get(i);
+                final Bundle extras = item.extras;
+                if (extras == null || extras.size() == 0) {
+                    continue;
+                }
+                final SyncStorageEngine.AuthorityInfo authority
+                        = mSyncStorageEngine.getAuthority(item.authorityId);
+                final String authorityName;
+                final String accountKey;
+                if (authority != null) {
+                    authorityName = authority.authority;
+                    accountKey = authority.account.name + "/" + authority.account.type
+                            + " u" + authority.userId;
+                } else {
+                    authorityName = "Unknown";
+                    accountKey = "Unknown";
+                }
+                final Time time = new Time();
+                final long eventTime = item.eventTime;
+                time.set(eventTime);
+
+                pw.printf("  #%-3d: %s %8s ",
+                        i + 1,
+                        formatTime(eventTime),
+                        SyncStorageEngine.SOURCES[item.source]);
+
+                pw.printf(format, accountKey, authorityName, extras);
+            }
+        }
+    }
+
+    private void dumpDayStatistics(PrintWriter pw) {
+        SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
+        if (dses != null && dses[0] != null) {
+            pw.println();
+            pw.println("Sync Statistics");
+            pw.print("  Today:  "); dumpDayStatistic(pw, dses[0]);
+            int today = dses[0].day;
+            int i;
+            SyncStorageEngine.DayStats ds;
+
+            // Print each day in the current week.
+            for (i=1; i<=6 && i < dses.length; i++) {
+                ds = dses[i];
+                if (ds == null) break;
+                int delta = today-ds.day;
+                if (delta > 6) break;
+
+                pw.print("  Day-"); pw.print(delta); pw.print(":  ");
+                dumpDayStatistic(pw, ds);
+            }
+
+            // Aggregate all following days into weeks and print totals.
+            int weekDay = today;
+            while (i < dses.length) {
+                SyncStorageEngine.DayStats aggr = null;
+                weekDay -= 7;
+                while (i < dses.length) {
+                    ds = dses[i];
+                    if (ds == null) {
+                        i = dses.length;
+                        break;
+                    }
+                    int delta = weekDay-ds.day;
+                    if (delta > 6) break;
+                    i++;
+
+                    if (aggr == null) {
+                        aggr = new SyncStorageEngine.DayStats(weekDay);
+                    }
+                    aggr.successCount += ds.successCount;
+                    aggr.successTime += ds.successTime;
+                    aggr.failureCount += ds.failureCount;
+                    aggr.failureTime += ds.failureTime;
+                }
+                if (aggr != null) {
+                    pw.print("  Week-"); pw.print((today-weekDay)/7); pw.print(": ");
+                    dumpDayStatistic(pw, aggr);
+                }
+            }
+        }
+    }
+
+    private void dumpSyncAdapters(IndentingPrintWriter pw) {
+        pw.println();
+        final List<UserInfo> users = getAllUsers();
+        if (users != null) {
+            for (UserInfo user : users) {
+                pw.println("Sync adapters for " + user + ":");
+                pw.increaseIndent();
+                for (RegisteredServicesCache.ServiceInfo<?> info :
+                        mSyncAdapters.getAllServices(user.id)) {
+                    pw.println(info);
+                }
+                pw.decreaseIndent();
+                pw.println();
+            }
+        }
+    }
+
+    private static class AuthoritySyncStats {
+        String name;
+        long elapsedTime;
+        int times;
+        Map<String, AccountSyncStats> accountMap = Maps.newHashMap();
+
+        private AuthoritySyncStats(String name) {
+            this.name = name;
+        }
+    }
+
+    private static class AccountSyncStats {
+        String name;
+        long elapsedTime;
+        int times;
+
+        private AccountSyncStats(String name) {
+            this.name = name;
+        }
+    }
+
+    /**
+     * A helper object to keep track of the time we have spent syncing since the last boot
+     */
+    private class SyncTimeTracker {
+        /** True if a sync was in progress on the most recent call to update() */
+        boolean mLastWasSyncing = false;
+        /** Used to track when lastWasSyncing was last set */
+        long mWhenSyncStarted = 0;
+        /** The cumulative time we have spent syncing */
+        private long mTimeSpentSyncing;
+
+        /** Call to let the tracker know that the sync state may have changed */
+        public synchronized void update() {
+            final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
+            if (isSyncInProgress == mLastWasSyncing) return;
+            final long now = SystemClock.elapsedRealtime();
+            if (isSyncInProgress) {
+                mWhenSyncStarted = now;
+            } else {
+                mTimeSpentSyncing += now - mWhenSyncStarted;
+            }
+            mLastWasSyncing = isSyncInProgress;
+        }
+
+        /** Get how long we have been syncing, in ms */
+        public synchronized long timeSpentSyncing() {
+            if (!mLastWasSyncing) return mTimeSpentSyncing;
+
+            final long now = SystemClock.elapsedRealtime();
+            return mTimeSpentSyncing + (now - mWhenSyncStarted);
+        }
+    }
+
+    class ServiceConnectionData {
+        public final ActiveSyncContext activeSyncContext;
+        public final ISyncAdapter syncAdapter;
+        ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
+            this.activeSyncContext = activeSyncContext;
+            this.syncAdapter = syncAdapter;
+        }
+    }
+
+    /**
+     * Handles SyncOperation Messages that are posted to the associated
+     * HandlerThread.
+     */
+    class SyncHandler extends Handler {
+        // Messages that can be sent on mHandler
+        private static final int MESSAGE_SYNC_FINISHED = 1;
+        private static final int MESSAGE_SYNC_ALARM = 2;
+        private static final int MESSAGE_CHECK_ALARMS = 3;
+        private static final int MESSAGE_SERVICE_CONNECTED = 4;
+        private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
+        private static final int MESSAGE_CANCEL = 6;
+
+        public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
+        private Long mAlarmScheduleTime = null;
+        public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
+        private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks =
+                Maps.newHashMap();
+        private List<Message> mBootQueue = new ArrayList<Message>();
+
+        public void onBootCompleted() {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "Boot completed, clearing boot queue.");
+            }
+            doDatabaseCleanup();
+            synchronized(this) {
+                // Dispatch any stashed messages.
+                for (Message message : mBootQueue) {
+                    sendMessage(message);
+                }
+                mBootQueue = null;
+                mBootCompleted = true;
+            }
+        }
+
+        private PowerManager.WakeLock getSyncWakeLock(Account account, String authority) {
+            final Pair<Account, String> wakeLockKey = Pair.create(account, authority);
+            PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
+            if (wakeLock == null) {
+                final String name = SYNC_WAKE_LOCK_PREFIX + "/" + authority + "/" + account.type
+                        + "/" + account.name;
+                wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
+                wakeLock.setReferenceCounted(false);
+                mWakeLocks.put(wakeLockKey, wakeLock);
+            }
+            return wakeLock;
+        }
+
+        /**
+         * Stash any messages that come to the handler before boot is complete.
+         * {@link #onBootCompleted()} will disable this and dispatch all the messages collected.
+         * @param msg Message to dispatch at a later point.
+         * @return true if a message was enqueued, false otherwise. This is to avoid losing the
+         * message if we manage to acquire the lock but by the time we do boot has completed.
+         */
+        private boolean tryEnqueueMessageUntilReadyToRun(Message msg) {
+            synchronized (this) {
+                if (!mBootCompleted) {
+                    // Need to copy the message bc looper will recycle it.
+                    mBootQueue.add(Message.obtain(msg));
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        /**
+         * Used to keep track of whether a sync notification is active and who it is for.
+         */
+        class SyncNotificationInfo {
+            // true iff the notification manager has been asked to send the notification
+            public boolean isActive = false;
+
+            // Set when we transition from not running a sync to running a sync, and cleared on
+            // the opposite transition.
+            public Long startTime = null;
+
+            public void toString(StringBuilder sb) {
+                sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
+            }
+
+            @Override
+            public String toString() {
+                StringBuilder sb = new StringBuilder();
+                toString(sb);
+                return sb.toString();
+            }
+        }
+
+        public SyncHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (tryEnqueueMessageUntilReadyToRun(msg)) {
+                return;
+            }
+
+            long earliestFuturePollTime = Long.MAX_VALUE;
+            long nextPendingSyncTime = Long.MAX_VALUE;
+            // Setting the value here instead of a method because we want the dumpsys logs
+            // to have the most recent value used.
+            try {
+                mDataConnectionIsConnected = readDataConnectionState();
+                mSyncManagerWakeLock.acquire();
+                // Always do this first so that we be sure that any periodic syncs that
+                // are ready to run have been converted into pending syncs. This allows the
+                // logic that considers the next steps to take based on the set of pending syncs
+                // to also take into account the periodic syncs.
+                earliestFuturePollTime = scheduleReadyPeriodicSyncs();
+                switch (msg.what) {
+                    case SyncHandler.MESSAGE_CANCEL: {
+                        Pair<Account, String> payload = (Pair<Account, String>) msg.obj;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+                                    + payload.first + ", " + payload.second);
+                        }
+                        cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
+                        break;
+                    }
+
+                    case SyncHandler.MESSAGE_SYNC_FINISHED:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
+                        }
+                        SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
+                        if (!isSyncStillActive(payload.activeSyncContext)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
+                                    + "sync is no longer active: "
+                                    + payload.activeSyncContext);
+                            break;
+                        }
+                        runSyncFinishedOrCanceledLocked(payload.syncResult, payload.activeSyncContext);
+
+                        // since a sync just finished check if it is time to start a new sync
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
+                        break;
+
+                    case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
+                        ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
+                                    + msgData.activeSyncContext);
+                        }
+                        // check that this isn't an old message
+                        if (isSyncStillActive(msgData.activeSyncContext)) {
+                            runBoundToSyncAdapter(msgData.activeSyncContext, msgData.syncAdapter);
+                        }
+                        break;
+                    }
+
+                    case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
+                        final ActiveSyncContext currentSyncContext =
+                                ((ServiceConnectionData)msg.obj).activeSyncContext;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
+                                    + currentSyncContext);
+                        }
+                        // check that this isn't an old message
+                        if (isSyncStillActive(currentSyncContext)) {
+                            // cancel the sync if we have a syncadapter, which means one is
+                            // outstanding
+                            if (currentSyncContext.mSyncAdapter != null) {
+                                try {
+                                    currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
+                                } catch (RemoteException e) {
+                                    // we don't need to retry this in this case
+                                }
+                            }
+
+                            // pretend that the sync failed with an IOException,
+                            // which is a soft error
+                            SyncResult syncResult = new SyncResult();
+                            syncResult.stats.numIoExceptions++;
+                            runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
+
+                            // since a sync just finished check if it is time to start a new sync
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
+                        }
+
+                        break;
+                    }
+
+                    case SyncHandler.MESSAGE_SYNC_ALARM: {
+                        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+                        if (isLoggable) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
+                        }
+                        mAlarmScheduleTime = null;
+                        try {
+                            nextPendingSyncTime = maybeStartNextSyncLocked();
+                        } finally {
+                            mHandleAlarmWakeLock.release();
+                        }
+                        break;
+                    }
+
+                    case SyncHandler.MESSAGE_CHECK_ALARMS:
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
+                        }
+                        nextPendingSyncTime = maybeStartNextSyncLocked();
+                        break;
+                }
+            } finally {
+                manageSyncNotificationLocked();
+                manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
+                mSyncTimeTracker.update();
+                mSyncManagerWakeLock.release();
+            }
+        }
+
+        /**
+         * Turn any periodic sync operations that are ready to run into pending sync operations.
+         * @return the desired start time of the earliest future  periodic sync operation,
+         * in milliseconds since boot
+         */
+        private long scheduleReadyPeriodicSyncs() {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) {
+                Log.v(TAG, "scheduleReadyPeriodicSyncs");
+            }
+            final boolean backgroundDataUsageAllowed =
+                    getConnectivityManager().getBackgroundDataSetting();
+            long earliestFuturePollTime = Long.MAX_VALUE;
+            if (!backgroundDataUsageAllowed) {
+                return earliestFuturePollTime;
+            }
+
+            AccountAndUser[] accounts = mRunningAccounts;
+
+            final long nowAbsolute = System.currentTimeMillis();
+            final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
+                    ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
+
+            ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = mSyncStorageEngine
+                    .getCopyOfAllAuthoritiesWithSyncStatus();
+            for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
+                final AuthorityInfo authorityInfo = info.first;
+                final SyncStatusInfo status = info.second;
+                if (TextUtils.isEmpty(authorityInfo.authority)) {
+                    Log.e(TAG, "Got an empty provider string. Skipping: " + authorityInfo);
+                    continue;
+                }
+                // skip the sync if the account of this operation no longer exists
+                if (!containsAccountAndUser(
+                        accounts, authorityInfo.account, authorityInfo.userId)) {
+                    continue;
+                }
+
+                if (!mSyncStorageEngine.getMasterSyncAutomatically(authorityInfo.userId)
+                        || !mSyncStorageEngine.getSyncAutomatically(
+                                authorityInfo.account, authorityInfo.userId,
+                                authorityInfo.authority)) {
+                    continue;
+                }
+
+                if (getIsSyncable(
+                        authorityInfo.account, authorityInfo.userId, authorityInfo.authority)
+                        == 0) {
+                    continue;
+                }
+
+                for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) {
+                    final PeriodicSync sync = authorityInfo.periodicSyncs.get(i);
+                    final Bundle extras = sync.extras;
+                    final long periodInMillis = sync.period * 1000;
+                    final long flexInMillis = sync.flexTime * 1000;
+                    // Skip if the period is invalid.
+                    if (periodInMillis <= 0) {
+                        continue;
+                    }
+                    // Find when this periodic sync was last scheduled to run.
+                    final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
+                    long remainingMillis
+                        = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+                    long timeSinceLastRunMillis
+                        = (nowAbsolute - lastPollTimeAbsolute);
+                    // Schedule this periodic sync to run early if it's close enough to its next
+                    // runtime, and far enough from its last run time.
+                    // If we are early, there will still be time remaining in this period.
+                    boolean runEarly = remainingMillis <= flexInMillis
+                            && timeSinceLastRunMillis > periodInMillis - flexInMillis;
+                    if (isLoggable) {
+                        Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "."
+                        + " period: " + (periodInMillis)
+                        + " flex: " + (flexInMillis)
+                        + " remaining: " + (remainingMillis)
+                        + " time_since_last: " + timeSinceLastRunMillis
+                        + " last poll absol: " + lastPollTimeAbsolute
+                        + " shifted now: " + shiftedNowAbsolute
+                        + " run_early: " + runEarly);
+                    }
+                    /*
+                     * Sync scheduling strategy: Set the next periodic sync
+                     * based on a random offset (in seconds). Also sync right
+                     * now if any of the following cases hold and mark it as
+                     * having been scheduled
+                     * Case 1: This sync is ready to run now.
+                     * Case 2: If the lastPollTimeAbsolute is in the
+                     * future, sync now and reinitialize. This can happen for
+                     * example if the user changed the time, synced and changed
+                     * back.
+                     * Case 3: If we failed to sync at the last scheduled
+                     * time.
+                     * Case 4: This sync is close enough to the time that we can schedule it.
+                     */
+                    if (runEarly // Case 4
+                            || remainingMillis == periodInMillis // Case 1
+                            || lastPollTimeAbsolute > nowAbsolute // Case 2
+                            || timeSinceLastRunMillis >= periodInMillis) { // Case 3
+                        // Sync now
+                        
+                        final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
+                                authorityInfo.account, authorityInfo.userId,
+                                authorityInfo.authority);
+                        final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+                        syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                                SyncAdapterType.newKey(
+                                        authorityInfo.authority, authorityInfo.account.type),
+                                authorityInfo.userId);
+                        if (syncAdapterInfo == null) {
+                            continue;
+                        }
+                        mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
+                                authorityInfo.periodicSyncs.get(i), nowAbsolute);
+                        scheduleSyncOperation(
+                                new SyncOperation(authorityInfo.account, authorityInfo.userId,
+                                        SyncOperation.REASON_PERIODIC,
+                                        SyncStorageEngine.SOURCE_PERIODIC,
+                                        authorityInfo.authority, extras,
+                                        0 /* runtime */, 0 /* flex */,
+                                                backoff != null ? backoff.first : 0,
+                                        mSyncStorageEngine.getDelayUntilTime(
+                                                authorityInfo.account, authorityInfo.userId,
+                                                authorityInfo.authority),
+                                        syncAdapterInfo.type.allowParallelSyncs()));
+                        
+                    }
+                    // Compute when this periodic sync should next run.
+                    long nextPollTimeAbsolute;
+                    if (runEarly) {
+                        // Add the time remaining so we don't get out of phase.
+                        nextPollTimeAbsolute = nowAbsolute + periodInMillis + remainingMillis;
+                    } else {
+                        nextPollTimeAbsolute = nowAbsolute + remainingMillis;
+                    }
+                    if (nextPollTimeAbsolute < earliestFuturePollTime) {
+                        earliestFuturePollTime = nextPollTimeAbsolute;
+                    }
+                }
+            }
+
+            if (earliestFuturePollTime == Long.MAX_VALUE) {
+                return Long.MAX_VALUE;
+            }
+
+            // convert absolute time to elapsed time
+            return SystemClock.elapsedRealtime() +
+                ((earliestFuturePollTime < nowAbsolute) ?
+                    0 : (earliestFuturePollTime - nowAbsolute));
+        }
+
+        private long maybeStartNextSyncLocked() {
+            final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+            if (isLoggable) Log.v(TAG, "maybeStartNextSync");
+
+            // If we aren't ready to run (e.g. the data connection is down), get out.
+            if (!mDataConnectionIsConnected) {
+                if (isLoggable) {
+                    Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
+                }
+                return Long.MAX_VALUE;
+            }
+
+            if (mStorageIsLow) {
+                if (isLoggable) {
+                    Log.v(TAG, "maybeStartNextSync: memory low, skipping");
+                }
+                return Long.MAX_VALUE;
+            }
+
+            // If the accounts aren't known yet then we aren't ready to run. We will be kicked
+            // when the account lookup request does complete.
+            AccountAndUser[] accounts = mRunningAccounts;
+            if (accounts == INITIAL_ACCOUNTS_ARRAY) {
+                if (isLoggable) {
+                    Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
+                }
+                return Long.MAX_VALUE;
+            }
+
+            // Otherwise consume SyncOperations from the head of the SyncQueue until one is
+            // found that is runnable (not disabled, etc). If that one is ready to run then
+            // start it, otherwise just get out.
+            final boolean backgroundDataUsageAllowed =
+                    getConnectivityManager().getBackgroundDataSetting();
+
+            final long now = SystemClock.elapsedRealtime();
+
+            // will be set to the next time that a sync should be considered for running
+            long nextReadyToRunTime = Long.MAX_VALUE;
+
+            // order the sync queue, dropping syncs that are not allowed
+            ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
+            synchronized (mSyncQueue) {
+                if (isLoggable) {
+                    Log.v(TAG, "build the operation array, syncQueue size is "
+                        + mSyncQueue.getOperations().size());
+                }
+                final Iterator<SyncOperation> operationIterator =
+                        mSyncQueue.getOperations().iterator();
+
+                final ActivityManager activityManager
+                        = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+                final Set<Integer> removedUsers = Sets.newHashSet();
+                while (operationIterator.hasNext()) {
+                    final SyncOperation op = operationIterator.next();
+
+                    // Drop the sync if the account of this operation no longer exists.
+                    if (!containsAccountAndUser(accounts, op.account, op.userId)) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        if (isLoggable) {
+                            Log.v(TAG, "    Dropping sync operation: account doesn't exist.");
+                        }
+                        continue;
+                    }
+
+                    // Drop this sync request if it isn't syncable.
+                    int syncableState = getIsSyncable(
+                            op.account, op.userId, op.authority);
+                    if (syncableState == 0) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        if (isLoggable) {
+                            Log.v(TAG, "    Dropping sync operation: isSyncable == 0.");
+                        }
+                        continue;
+                    }
+
+                    // If the user is not running, drop the request.
+                    if (!activityManager.isUserRunning(op.userId)) {
+                        final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
+                        if (userInfo == null) {
+                            removedUsers.add(op.userId);
+                        }
+                        if (isLoggable) {
+                            Log.v(TAG, "    Dropping sync operation: user not running.");
+                        }
+                        continue;
+                    }
+
+                    // If the next run time is in the future, even given the flexible scheduling,
+                    // return the time.
+                    if (op.effectiveRunTime - op.flexTime > now) {
+                        if (nextReadyToRunTime > op.effectiveRunTime) {
+                            nextReadyToRunTime = op.effectiveRunTime;
+                        }
+                        if (isLoggable) {
+                            Log.v(TAG, "    Dropping sync operation: Sync too far in future.");
+                        }
+                        continue;
+                    }
+
+                    // If the op isn't allowed on metered networks and we're on one, drop it.
+                    if (getConnectivityManager().isActiveNetworkMetered()
+                            && op.isMeteredDisallowed()) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        continue;
+                    }
+
+                    // TODO: change this behaviour for non-registered syncs.
+                    final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+                    syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                            SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
+
+                    // only proceed if network is connected for requesting UID
+                    final boolean uidNetworkConnected;
+                    if (syncAdapterInfo != null) {
+                        final NetworkInfo networkInfo = getConnectivityManager()
+                                .getActiveNetworkInfoForUid(syncAdapterInfo.uid);
+                        uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
+                    } else {
+                        uidNetworkConnected = false;
+                    }
+
+                    // skip the sync if it isn't manual, and auto sync or
+                    // background data usage is disabled or network is
+                    // disconnected for the target UID.
+                    if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+                            && (syncableState > 0)
+                            && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
+                                || !backgroundDataUsageAllowed
+                                || !uidNetworkConnected
+                                || !mSyncStorageEngine.getSyncAutomatically(
+                                       op.account, op.userId, op.authority))) {
+                        operationIterator.remove();
+                        mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+                        continue;
+                    }
+
+                    operations.add(op);
+                }
+                for (Integer user : removedUsers) {
+                    // if it's still removed
+                    if (mUserManager.getUserInfo(user) == null) {
+                        onUserRemoved(user);
+                    }
+                }
+            }
+
+            // find the next operation to dispatch, if one is ready
+            // iterate from the top, keep issuing (while potentially canceling existing syncs)
+            // until the quotas are filled.
+            // once the quotas are filled iterate once more to find when the next one would be
+            // (also considering pre-emption reasons).
+            if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
+            Collections.sort(operations);
+            if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
+            for (int i = 0, N = operations.size(); i < N; i++) {
+                final SyncOperation candidate = operations.get(i);
+                final boolean candidateIsInitialization = candidate.isInitialization();
+
+                int numInit = 0;
+                int numRegular = 0;
+                ActiveSyncContext conflict = null;
+                ActiveSyncContext longRunning = null;
+                ActiveSyncContext toReschedule = null;
+                ActiveSyncContext oldestNonExpeditedRegular = null;
+
+                for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                    final SyncOperation activeOp = activeSyncContext.mSyncOperation;
+                    if (activeOp.isInitialization()) {
+                        numInit++;
+                    } else {
+                        numRegular++;
+                        if (!activeOp.isExpedited()) {
+                            if (oldestNonExpeditedRegular == null
+                                || (oldestNonExpeditedRegular.mStartTime
+                                    > activeSyncContext.mStartTime)) {
+                                oldestNonExpeditedRegular = activeSyncContext;
+                            }
+                        }
+                    }
+                    if (activeOp.account.type.equals(candidate.account.type)
+                            && activeOp.authority.equals(candidate.authority)
+                            && activeOp.userId == candidate.userId
+                            && (!activeOp.allowParallelSyncs
+                                || activeOp.account.name.equals(candidate.account.name))) {
+                        conflict = activeSyncContext;
+                        // don't break out since we want to do a full count of the varieties
+                    } else {
+                        if (candidateIsInitialization == activeOp.isInitialization()
+                                && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
+                            longRunning = activeSyncContext;
+                            // don't break out since we want to do a full count of the varieties
+                        }
+                    }
+                }
+
+                if (isLoggable) {
+                    Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
+                    Log.v(TAG, "  numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
+                    Log.v(TAG, "  longRunning: " + longRunning);
+                    Log.v(TAG, "  conflict: " + conflict);
+                    Log.v(TAG, "  oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
+                }
+
+                final boolean roomAvailable = candidateIsInitialization
+                        ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
+                        : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
+
+                if (conflict != null) {
+                    if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
+                            && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an initialization "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else if (candidate.expedited && !conflict.mSyncOperation.expedited
+                            && (candidateIsInitialization
+                                == conflict.mSyncOperation.isInitialization())) {
+                        toReschedule = conflict;
+                        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                            Log.v(TAG, "canceling and rescheduling sync since an expedited "
+                                    + "takes higher priority, " + conflict);
+                        }
+                    } else {
+                        continue;
+                    }
+                } else if (roomAvailable) {
+                    // dispatch candidate
+                } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
+                           && !candidateIsInitialization) {
+                    // We found an active, non-expedited regular sync. We also know that the
+                    // candidate doesn't conflict with this active sync since conflict
+                    // is null. Reschedule the active sync and start the candidate.
+                    toReschedule = oldestNonExpeditedRegular;
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to run, "
+                                + oldestNonExpeditedRegular);
+                    }
+                } else if (longRunning != null
+                        && (candidateIsInitialization
+                            == longRunning.mSyncOperation.isInitialization())) {
+                    // We found an active, long-running sync. Reschedule the active
+                    // sync and start the candidate.
+                    toReschedule = longRunning;
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
+                              + longRunning);
+                    }
+                } else {
+                    // we were unable to find or make space to run this candidate, go on to
+                    // the next one
+                    continue;
+                }
+
+                if (toReschedule != null) {
+                    runSyncFinishedOrCanceledLocked(null, toReschedule);
+                    scheduleSyncOperation(toReschedule.mSyncOperation);
+                }
+                synchronized (mSyncQueue) {
+                    mSyncQueue.remove(candidate);
+                }
+                dispatchSyncOperation(candidate);
+            }
+
+            return nextReadyToRunTime;
+     }
+
+        private boolean dispatchSyncOperation(SyncOperation op) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
+                Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
+                for (ActiveSyncContext syncContext : mActiveSyncContexts) {
+                    Log.v(TAG, syncContext.toString());
+                }
+            }
+
+            // connect to the sync adapter
+            SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
+            final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+            syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, op.userId);
+            if (syncAdapterInfo == null) {
+                Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
+                        + ", removing settings for it");
+                mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
+                return false;
+            }
+
+            ActiveSyncContext activeSyncContext =
+                    new ActiveSyncContext(op, insertStartSyncEvent(op), syncAdapterInfo.uid);
+            activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
+            mActiveSyncContexts.add(activeSyncContext);
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
+            }
+            if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo, op.userId)) {
+                Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
+                closeActiveSyncContext(activeSyncContext);
+                return false;
+            }
+
+            return true;
+        }
+
+        private void runBoundToSyncAdapter(final ActiveSyncContext activeSyncContext,
+              ISyncAdapter syncAdapter) {
+            activeSyncContext.mSyncAdapter = syncAdapter;
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+            try {
+                activeSyncContext.mIsLinkedToDeath = true;
+                syncAdapter.asBinder().linkToDeath(activeSyncContext, 0);
+
+                syncAdapter.startSync(activeSyncContext, syncOperation.authority,
+                        syncOperation.account, syncOperation.extras);
+            } catch (RemoteException remoteExc) {
+                Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
+                closeActiveSyncContext(activeSyncContext);
+                increaseBackoffSetting(syncOperation);
+                scheduleSyncOperation(new SyncOperation(syncOperation));
+            } catch (RuntimeException exc) {
+                closeActiveSyncContext(activeSyncContext);
+                Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
+            }
+        }
+
+        private void cancelActiveSyncLocked(Account account, int userId, String authority) {
+            ArrayList<ActiveSyncContext> activeSyncs =
+                    new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
+            for (ActiveSyncContext activeSyncContext : activeSyncs) {
+                if (activeSyncContext != null) {
+                    // if an account was specified then only cancel the sync if it matches
+                    if (account != null) {
+                        if (!account.equals(activeSyncContext.mSyncOperation.account)) {
+                            continue;
+                        }
+                    }
+                    // if an authority was specified then only cancel the sync if it matches
+                    if (authority != null) {
+                        if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
+                            continue;
+                        }
+                    }
+                    // check if the userid matches
+                    if (userId != UserHandle.USER_ALL
+                            && userId != activeSyncContext.mSyncOperation.userId) {
+                        continue;
+                    }
+                    runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
+                            activeSyncContext);
+                }
+            }
+        }
+
+        private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
+                ActiveSyncContext activeSyncContext) {
+            boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+
+            if (activeSyncContext.mIsLinkedToDeath) {
+                activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
+                activeSyncContext.mIsLinkedToDeath = false;
+            }
+            closeActiveSyncContext(activeSyncContext);
+
+            final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
+
+            final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
+
+            String historyMessage;
+            int downstreamActivity;
+            int upstreamActivity;
+            if (syncResult != null) {
+                if (isLoggable) {
+                    Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
+                            + syncOperation + ", result " + syncResult);
+                }
+
+                if (!syncResult.hasError()) {
+                    historyMessage = SyncStorageEngine.MESG_SUCCESS;
+                    // TODO: set these correctly when the SyncResult is extended to include it
+                    downstreamActivity = 0;
+                    upstreamActivity = 0;
+                    clearBackoffSetting(syncOperation);
+                } else {
+                    Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
+                    // the operation failed so increase the backoff time
+                    if (!syncResult.syncAlreadyInProgress) {
+                        increaseBackoffSetting(syncOperation);
+                    }
+                    // reschedule the sync if so indicated by the syncResult
+                    maybeRescheduleSync(syncResult, syncOperation);
+                    historyMessage = ContentResolver.syncErrorToString(
+                            syncResultToErrorNumber(syncResult));
+                    // TODO: set these correctly when the SyncResult is extended to include it
+                    downstreamActivity = 0;
+                    upstreamActivity = 0;
+                }
+
+                setDelayUntilTime(syncOperation, syncResult.delayUntil);
+            } else {
+                if (isLoggable) {
+                    Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
+                }
+                if (activeSyncContext.mSyncAdapter != null) {
+                    try {
+                        activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
+                    } catch (RemoteException e) {
+                        // we don't need to retry this in this case
+                    }
+                }
+                historyMessage = SyncStorageEngine.MESG_CANCELED;
+                downstreamActivity = 0;
+                upstreamActivity = 0;
+            }
+
+            stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
+                    upstreamActivity, downstreamActivity, elapsedTime);
+
+            if (syncResult != null && syncResult.tooManyDeletions) {
+                installHandleTooManyDeletesNotification(syncOperation.account,
+                        syncOperation.authority, syncResult.stats.numDeletes,
+                        syncOperation.userId);
+            } else {
+                mNotificationMgr.cancelAsUser(null,
+                        syncOperation.account.hashCode() ^ syncOperation.authority.hashCode(),
+                        new UserHandle(syncOperation.userId));
+            }
+
+            if (syncResult != null && syncResult.fullSyncRequested) {
+                scheduleSyncOperation(
+                        new SyncOperation(syncOperation.account, syncOperation.userId,
+                            syncOperation.reason,
+                            syncOperation.syncSource, syncOperation.authority, new Bundle(),
+                            0 /* delay */, 0 /* flex */,
+                            syncOperation.backoff, syncOperation.delayUntil,
+                            syncOperation.allowParallelSyncs));
+            }
+            // no need to schedule an alarm, as that will be done by our caller.
+        }
+
+        private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
+            activeSyncContext.close();
+            mActiveSyncContexts.remove(activeSyncContext);
+            mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
+                    activeSyncContext.mSyncOperation.userId);
+        }
+
+        /**
+         * Convert the error-containing SyncResult into the Sync.History error number. Since
+         * the SyncResult may indicate multiple errors at once, this method just returns the
+         * most "serious" error.
+         * @param syncResult the SyncResult from which to read
+         * @return the most "serious" error set in the SyncResult
+         * @throws IllegalStateException if the SyncResult does not indicate any errors.
+         *   If SyncResult.error() is true then it is safe to call this.
+         */
+        private int syncResultToErrorNumber(SyncResult syncResult) {
+            if (syncResult.syncAlreadyInProgress)
+                return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
+            if (syncResult.stats.numAuthExceptions > 0)
+                return ContentResolver.SYNC_ERROR_AUTHENTICATION;
+            if (syncResult.stats.numIoExceptions > 0)
+                return ContentResolver.SYNC_ERROR_IO;
+            if (syncResult.stats.numParseExceptions > 0)
+                return ContentResolver.SYNC_ERROR_PARSE;
+            if (syncResult.stats.numConflictDetectedExceptions > 0)
+                return ContentResolver.SYNC_ERROR_CONFLICT;
+            if (syncResult.tooManyDeletions)
+                return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
+            if (syncResult.tooManyRetries)
+                return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
+            if (syncResult.databaseError)
+                return ContentResolver.SYNC_ERROR_INTERNAL;
+            throw new IllegalStateException("we are not in an error state, " + syncResult);
+        }
+
+        private void manageSyncNotificationLocked() {
+            boolean shouldCancel;
+            boolean shouldInstall;
+
+            if (mActiveSyncContexts.isEmpty()) {
+                mSyncNotificationInfo.startTime = null;
+
+                // we aren't syncing. if the notification is active then remember that we need
+                // to cancel it and then clear out the info
+                shouldCancel = mSyncNotificationInfo.isActive;
+                shouldInstall = false;
+            } else {
+                // we are syncing
+                final long now = SystemClock.elapsedRealtime();
+                if (mSyncNotificationInfo.startTime == null) {
+                    mSyncNotificationInfo.startTime = now;
+                }
+
+                // there are three cases:
+                // - the notification is up: do nothing
+                // - the notification is not up but it isn't time yet: don't install
+                // - the notification is not up and it is time: need to install
+
+                if (mSyncNotificationInfo.isActive) {
+                    shouldInstall = shouldCancel = false;
+                } else {
+                    // it isn't currently up, so there is nothing to cancel
+                    shouldCancel = false;
+
+                    final boolean timeToShowNotification =
+                            now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
+                    if (timeToShowNotification) {
+                        shouldInstall = true;
+                    } else {
+                        // show the notification immediately if this is a manual sync
+                        shouldInstall = false;
+                        for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
+                            final boolean manualSync = activeSyncContext.mSyncOperation.extras
+                                    .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+                            if (manualSync) {
+                                shouldInstall = true;
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (shouldCancel && !shouldInstall) {
+                mNeedSyncActiveNotification = false;
+                sendSyncStateIntent();
+                mSyncNotificationInfo.isActive = false;
+            }
+
+            if (shouldInstall) {
+                mNeedSyncActiveNotification = true;
+                sendSyncStateIntent();
+                mSyncNotificationInfo.isActive = true;
+            }
+        }
+
+        private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
+                long nextPendingEventElapsedTime) {
+            // in each of these cases the sync loop will be kicked, which will cause this
+            // method to be called again
+            if (!mDataConnectionIsConnected) return;
+            if (mStorageIsLow) return;
+
+            // When the status bar notification should be raised
+            final long notificationTime =
+                    (!mSyncHandler.mSyncNotificationInfo.isActive
+                            && mSyncHandler.mSyncNotificationInfo.startTime != null)
+                            ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
+                            : Long.MAX_VALUE;
+
+            // When we should consider canceling an active sync
+            long earliestTimeoutTime = Long.MAX_VALUE;
+            for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
+                final long currentSyncTimeoutTime =
+                        currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
+                            + currentSyncTimeoutTime);
+                }
+                if (earliestTimeoutTime > currentSyncTimeoutTime) {
+                    earliestTimeoutTime = currentSyncTimeoutTime;
+                }
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
+            }
+
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
+                        + nextPeriodicEventElapsedTime);
+            }
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
+                        + nextPendingEventElapsedTime);
+            }
+
+            long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
+            alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
+            alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
+
+            // Bound the alarm time.
+            final long now = SystemClock.elapsedRealtime();
+            if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+                }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
+            } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
+                            + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
+                }
+                alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
+            }
+
+            // determine if we need to set or cancel the alarm
+            boolean shouldSet = false;
+            boolean shouldCancel = false;
+            final boolean alarmIsActive = (mAlarmScheduleTime != null) && (now < mAlarmScheduleTime);
+            final boolean needAlarm = alarmTime != Long.MAX_VALUE;
+            if (needAlarm) {
+                // Need the alarm if
+                //  - it's currently not set
+                //  - if the alarm is set in the past.
+                if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
+                    shouldSet = true;
+                }
+            } else {
+                shouldCancel = alarmIsActive;
+            }
+
+            // Set or cancel the alarm as directed.
+            ensureAlarmService();
+            if (shouldSet) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
+                            + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
+                            + " secs from now");
+                }
+                mAlarmScheduleTime = alarmTime;
+                mAlarmService.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
+                        mSyncAlarmIntent);
+            } else if (shouldCancel) {
+                mAlarmScheduleTime = null;
+                mAlarmService.cancel(mSyncAlarmIntent);
+            }
+        }
+
+        private void sendSyncStateIntent() {
+            Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
+            syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
+            syncStateIntent.putExtra("failing", false);
+            mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER);
+        }
+
+        private void installHandleTooManyDeletesNotification(Account account, String authority,
+                long numDeletes, int userId) {
+            if (mNotificationMgr == null) return;
+
+            final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
+                    authority, 0 /* flags */);
+            if (providerInfo == null) {
+                return;
+            }
+            CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
+
+            Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
+            clickIntent.putExtra("account", account);
+            clickIntent.putExtra("authority", authority);
+            clickIntent.putExtra("provider", authorityName.toString());
+            clickIntent.putExtra("numDeletes", numDeletes);
+
+            if (!isActivityAvailable(clickIntent)) {
+                Log.w(TAG, "No activity found to handle too many deletes.");
+                return;
+            }
+
+            final PendingIntent pendingIntent = PendingIntent
+                    .getActivityAsUser(mContext, 0, clickIntent,
+                            PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(userId));
+
+            CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
+                    R.string.contentServiceTooManyDeletesNotificationDesc);
+
+            Notification notification =
+                new Notification(R.drawable.stat_notify_sync_error,
+                        mContext.getString(R.string.contentServiceSync),
+                        System.currentTimeMillis());
+            notification.setLatestEventInfo(mContext,
+                    mContext.getString(R.string.contentServiceSyncNotificationTitle),
+                    String.format(tooManyDeletesDescFormat.toString(), authorityName),
+                    pendingIntent);
+            notification.flags |= Notification.FLAG_ONGOING_EVENT;
+            mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
+                    notification, new UserHandle(userId));
+        }
+
+        /**
+         * Checks whether an activity exists on the system image for the given intent.
+         *
+         * @param intent The intent for an activity.
+         * @return Whether or not an activity exists.
+         */
+        private boolean isActivityAvailable(Intent intent) {
+            PackageManager pm = mContext.getPackageManager();
+            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            int listSize = list.size();
+            for (int i = 0; i < listSize; i++) {
+                ResolveInfo resolveInfo = list.get(i);
+                if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
+                        != 0) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        public long insertStartSyncEvent(SyncOperation syncOperation) {
+            final int source = syncOperation.syncSource;
+            final long now = System.currentTimeMillis();
+
+            EventLog.writeEvent(2720, syncOperation.authority,
+                                SyncStorageEngine.EVENT_START, source,
+                                syncOperation.account.name.hashCode());
+
+            return mSyncStorageEngine.insertStartSyncEvent(
+                    syncOperation.account, syncOperation.userId, syncOperation.reason,
+                    syncOperation.authority,
+                    now, source, syncOperation.isInitialization(), syncOperation.extras
+            );
+        }
+
+        public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
+                int upstreamActivity, int downstreamActivity, long elapsedTime) {
+            EventLog.writeEvent(2720, syncOperation.authority,
+                                SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
+                                syncOperation.account.name.hashCode());
+
+            mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
+                    resultMessage, downstreamActivity, upstreamActivity);
+        }
+    }
+
+    private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
+        for (ActiveSyncContext sync : mActiveSyncContexts) {
+            if (sync == activeSyncContext) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static class PrintTable {
+        private ArrayList<Object[]> mTable = Lists.newArrayList();
+        private final int mCols;
+
+        PrintTable(int cols) {
+            mCols = cols;
+        }
+
+        void set(int row, int col, Object... values) {
+            if (col + values.length > mCols) {
+                throw new IndexOutOfBoundsException("Table only has " + mCols +
+                        " columns. can't set " + values.length + " at column " + col);
+            }
+            for (int i = mTable.size(); i <= row; i++) {
+                final Object[] list = new Object[mCols];
+                mTable.add(list);
+                for (int j = 0; j < mCols; j++) {
+                    list[j] = "";
+                }
+            }
+            System.arraycopy(values, 0, mTable.get(row), col, values.length);
+        }
+
+        void writeTo(PrintWriter out) {
+            final String[] formats = new String[mCols];
+            int totalLength = 0;
+            for (int col = 0; col < mCols; ++col) {
+                int maxLength = 0;
+                for (Object[] row : mTable) {
+                    final int length = row[col].toString().length();
+                    if (length > maxLength) {
+                        maxLength = length;
+                    }
+                }
+                totalLength += maxLength;
+                formats[col] = String.format("%%-%ds", maxLength);
+            }
+            printRow(out, formats, mTable.get(0));
+            totalLength += (mCols - 1) * 2;
+            for (int i = 0; i < totalLength; ++i) {
+                out.print("-");
+            }
+            out.println();
+            for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) {
+                Object[] row = mTable.get(i);
+                printRow(out, formats, row);
+            }
+        }
+
+        private void printRow(PrintWriter out, String[] formats, Object[] row) {
+            for (int j = 0, rowLength = row.length; j < rowLength; j++) {
+                out.printf(String.format(formats[j], row[j].toString()));
+                out.print("  ");
+            }
+            out.println();
+        }
+
+        public int getNumRows() {
+            return mTable.size();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncOperation.java b/services/core/java/com/android/server/content/SyncOperation.java
new file mode 100644
index 0000000..48567478
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncOperation.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2010 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.content;
+
+import android.accounts.Account;
+import android.content.pm.PackageManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.SyncRequest;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Pair;
+
+/**
+ * Value type that represents a sync operation.
+ * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered,
+ * transfer-size, etc.
+ * {@hide}
+ */
+public class SyncOperation implements Comparable {
+    public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
+    public static final int REASON_ACCOUNTS_UPDATED = -2;
+    public static final int REASON_SERVICE_CHANGED = -3;
+    public static final int REASON_PERIODIC = -4;
+    public static final int REASON_IS_SYNCABLE = -5;
+    /** Sync started because it has just been set to sync automatically. */
+    public static final int REASON_SYNC_AUTO = -6;
+    /** Sync started because master sync automatically has been set to true. */
+    public static final int REASON_MASTER_SYNC_AUTO = -7;
+    public static final int REASON_USER_START = -8;
+
+    private static String[] REASON_NAMES = new String[] {
+            "DataSettingsChanged",
+            "AccountsUpdated",
+            "ServiceChanged",
+            "Periodic",
+            "IsSyncable",
+            "AutoSync",
+            "MasterSyncAuto",
+            "UserStart",
+    };
+
+    /** Account info to identify a SyncAdapter registered with the system. */
+    public final Account account;
+    /** Authority info to identify a SyncAdapter registered with the system. */
+    public final String authority;
+    /** Service to which this operation will bind to perform the sync. */
+    public final ComponentName service;
+    public final int userId;
+    public final int reason;
+    public int syncSource;
+    public final boolean allowParallelSyncs;
+    public Bundle extras;
+    public final String key;
+    public boolean expedited;
+    public SyncStorageEngine.PendingOperation pendingOperation;
+    /** Elapsed real time in millis at which to run this sync. */
+    public long latestRunTime;
+    /** Set by the SyncManager in order to delay retries. */
+    public Long backoff;
+    /** Specified by the adapter to delay subsequent sync operations. */
+    public long delayUntil;
+    /**
+     * Elapsed real time in millis when this sync will be run.
+     * Depends on max(backoff, latestRunTime, and delayUntil).
+     */
+    public long effectiveRunTime;
+    /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
+    public long flexTime;
+
+    public SyncOperation(Account account, int userId, int reason, int source, String authority,
+            Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+            long delayUntil, boolean allowParallelSyncs) {
+        this.service = null;
+        this.account = account;
+        this.authority = authority;
+        this.userId = userId;
+        this.reason = reason;
+        this.syncSource = source;
+        this.allowParallelSyncs = allowParallelSyncs;
+        this.extras = new Bundle(extras);
+        cleanBundle(this.extras);
+        this.delayUntil = delayUntil;
+        this.backoff = backoff;
+        final long now = SystemClock.elapsedRealtime();
+        // Checks the extras bundle. Must occur after we set the internal bundle.
+        if (runTimeFromNow < 0 || isExpedited()) {
+            this.expedited = true;
+            this.latestRunTime = now;
+            this.flexTime = 0;
+        } else {
+            this.expedited = false;
+            this.latestRunTime = now + runTimeFromNow;
+            this.flexTime = flexTime;
+        }
+        updateEffectiveRunTime();
+        this.key = toKey();
+    }
+
+    /**
+     * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
+     * flags set.
+     * @param bundle to clean.
+     */
+    private void cleanBundle(Bundle bundle) {
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+        removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED);
+
+        // Remove Config data.
+        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD);
+        bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD);
+    }
+
+    private void removeFalseExtra(Bundle bundle, String extraName) {
+        if (!bundle.getBoolean(extraName, false)) {
+            bundle.remove(extraName);
+        }
+    }
+
+    /** Only used to immediately reschedule a sync. */
+    SyncOperation(SyncOperation other) {
+        this.service = other.service;
+        this.account = other.account;
+        this.authority = other.authority;
+        this.userId = other.userId;
+        this.reason = other.reason;
+        this.syncSource = other.syncSource;
+        this.extras = new Bundle(other.extras);
+        this.expedited = other.expedited;
+        this.latestRunTime = SystemClock.elapsedRealtime();
+        this.flexTime = 0L;
+        this.backoff = other.backoff;
+        this.allowParallelSyncs = other.allowParallelSyncs;
+        this.updateEffectiveRunTime();
+        this.key = toKey();
+    }
+
+    @Override
+    public String toString() {
+        return dump(null, true);
+    }
+
+    public String dump(PackageManager pm, boolean useOneLine) {
+        StringBuilder sb = new StringBuilder()
+                .append(account.name)
+                .append(" u")
+                .append(userId).append(" (")
+                .append(account.type)
+                .append(")")
+                .append(", ")
+                .append(authority)
+                .append(", ")
+                .append(SyncStorageEngine.SOURCES[syncSource])
+                .append(", latestRunTime ")
+                .append(latestRunTime);
+        if (expedited) {
+            sb.append(", EXPEDITED");
+        }
+        sb.append(", reason: ");
+        sb.append(reasonToString(pm, reason));
+        if (!useOneLine && !extras.keySet().isEmpty()) {
+            sb.append("\n    ");
+            extrasToStringBuilder(extras, sb);
+        }
+        return sb.toString();
+    }
+
+    public static String reasonToString(PackageManager pm, int reason) {
+        if (reason >= 0) {
+            if (pm != null) {
+                final String[] packages = pm.getPackagesForUid(reason);
+                if (packages != null && packages.length == 1) {
+                    return packages[0];
+                }
+                final String name = pm.getNameForUid(reason);
+                if (name != null) {
+                    return name;
+                }
+                return String.valueOf(reason);
+            } else {
+                return String.valueOf(reason);
+            }
+        } else {
+            final int index = -reason - 1;
+            if (index >= REASON_NAMES.length) {
+                return String.valueOf(reason);
+            } else {
+                return REASON_NAMES[index];
+            }
+        }
+    }
+
+    public boolean isMeteredDisallowed() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false);
+    }
+
+    public boolean isInitialization() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+    }
+
+    public boolean isExpedited() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited;
+    }
+
+    public boolean ignoreBackoff() {
+        return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
+    }
+
+    /** Changed in V3. */
+    private String toKey() {
+        StringBuilder sb = new StringBuilder();
+        if (service == null) {
+            sb.append("authority: ").append(authority);
+            sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+                    + "}");
+        } else {
+            sb.append("service {package=" )
+                .append(service.getPackageName())
+                .append(" user=")
+                .append(userId)
+                .append(", class=")
+                .append(service.getClassName())
+                .append("}");
+        }
+        sb.append(" extras: ");
+        extrasToStringBuilder(extras, sb);
+        return sb.toString();
+    }
+
+    public static void extrasToStringBuilder(Bundle bundle, StringBuilder sb) {
+        sb.append("[");
+        for (String key : bundle.keySet()) {
+            sb.append(key).append("=").append(bundle.get(key)).append(" ");
+        }
+        sb.append("]");
+    }
+
+    /**
+     * Update the effective run time of this Operation based on latestRunTime (specified at
+     * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
+     * SyncManager on soft failures).
+     */
+    public void updateEffectiveRunTime() {
+        // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate
+        // the flex time provided by the developer.
+        effectiveRunTime = ignoreBackoff() ?
+                latestRunTime :
+                    Math.max(Math.max(latestRunTime, delayUntil), backoff);
+    }
+
+    /**
+     * SyncOperations are sorted based on their earliest effective run time.
+     * This comparator is used to sort the SyncOps at a given time when
+     * deciding which to run, so earliest run time is the best criteria.
+     */
+    @Override
+    public int compareTo(Object o) {
+        SyncOperation other = (SyncOperation) o;
+        if (expedited != other.expedited) {
+            return expedited ? -1 : 1;
+        }
+        long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0);
+        long otherIntervalStart = Math.max(
+            other.effectiveRunTime - other.flexTime, 0);
+        if (thisIntervalStart < otherIntervalStart) {
+            return -1;
+        } else if (otherIntervalStart < thisIntervalStart) {
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncQueue.java b/services/core/java/com/android/server/content/SyncQueue.java
new file mode 100644
index 0000000..6f3fe6e
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncQueue.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2010 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.content;
+
+import android.accounts.Account;
+import android.content.pm.PackageManager;
+import android.content.pm.RegisteredServicesCache;
+import android.content.SyncAdapterType;
+import android.content.SyncAdaptersCache;
+import android.content.pm.RegisteredServicesCache.ServiceInfo;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.google.android.collect.Maps;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Queue of pending sync operations. Not inherently thread safe, external
+ * callers are responsible for locking.
+ *
+ * @hide
+ */
+public class SyncQueue {
+    private static final String TAG = "SyncManager";
+    private final SyncStorageEngine mSyncStorageEngine;
+    private final SyncAdaptersCache mSyncAdapters;
+    private final PackageManager mPackageManager;
+
+    // A Map of SyncOperations operationKey -> SyncOperation that is designed for
+    // quick lookup of an enqueued SyncOperation.
+    private final HashMap<String, SyncOperation> mOperationsMap = Maps.newHashMap();
+
+    public SyncQueue(PackageManager packageManager, SyncStorageEngine syncStorageEngine,
+            final SyncAdaptersCache syncAdapters) {
+        mPackageManager = packageManager;
+        mSyncStorageEngine = syncStorageEngine;
+        mSyncAdapters = syncAdapters;
+    }
+
+    public void addPendingOperations(int userId) {
+        for (SyncStorageEngine.PendingOperation op : mSyncStorageEngine.getPendingOperations()) {
+            if (op.userId != userId) continue;
+
+            final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
+                    op.account, op.userId, op.authority);
+            final ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(
+                    SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
+            if (syncAdapterInfo == null) {
+                Log.w(TAG, "Missing sync adapter info for authority " + op.authority + ", userId "
+                        + op.userId);
+                continue;
+            }
+            SyncOperation syncOperation = new SyncOperation(
+                    op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
+                    0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
+                    mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
+                    syncAdapterInfo.type.allowParallelSyncs());
+            syncOperation.expedited = op.expedited;
+            syncOperation.pendingOperation = op;
+            add(syncOperation, op);
+        }
+    }
+
+    public boolean add(SyncOperation operation) {
+        return add(operation, null /* this is not coming from the database */);
+    }
+
+    /**
+     * Adds a SyncOperation to the queue and creates a PendingOperation object to track that sync.
+     * If an operation is added that already exists, the existing operation is updated if the newly
+     * added operation occurs before (or the interval overlaps).
+     */
+    private boolean add(SyncOperation operation,
+            SyncStorageEngine.PendingOperation pop) {
+        // If an operation with the same key exists and this one should run sooner/overlaps,
+        // replace the run interval of the existing operation with this new one.
+        // Complications: what if the existing operation is expedited but the new operation has an
+        // earlier run time? Will not be a problem for periodic syncs (no expedited flag), and for
+        // one-off syncs we only change it if the new sync is sooner.
+        final String operationKey = operation.key;
+        final SyncOperation existingOperation = mOperationsMap.get(operationKey);
+
+        if (existingOperation != null) {
+            boolean changed = false;
+            if (operation.compareTo(existingOperation) <= 0 ) {
+                existingOperation.expedited = operation.expedited;
+                long newRunTime =
+                        Math.min(existingOperation.latestRunTime, operation.latestRunTime);
+                // Take smaller runtime.
+                existingOperation.latestRunTime = newRunTime;
+                // Take newer flextime.
+                existingOperation.flexTime = operation.flexTime;
+                changed = true;
+            }
+            return changed;
+        }
+
+        operation.pendingOperation = pop;
+        // Don't update the PendingOp if one already exists. This really is just a placeholder,
+        // no actual scheduling info is placed here.
+        // TODO: Change this to support service components.
+        if (operation.pendingOperation == null) {
+            pop = new SyncStorageEngine.PendingOperation(
+                    operation.account, operation.userId, operation.reason, operation.syncSource,
+                    operation.authority, operation.extras, operation.expedited);
+            pop = mSyncStorageEngine.insertIntoPending(pop);
+            if (pop == null) {
+                throw new IllegalStateException("error adding pending sync operation "
+                        + operation);
+            }
+            operation.pendingOperation = pop;
+        }
+
+        mOperationsMap.put(operationKey, operation);
+        return true;
+    }
+
+    public void removeUser(int userId) {
+        ArrayList<SyncOperation> opsToRemove = new ArrayList<SyncOperation>();
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.userId == userId) {
+                opsToRemove.add(op);
+            }
+        }
+
+        for (SyncOperation op : opsToRemove) {
+            remove(op);
+        }
+    }
+
+    /**
+     * Remove the specified operation if it is in the queue.
+     * @param operation the operation to remove
+     */
+    public void remove(SyncOperation operation) {
+        SyncOperation operationToRemove = mOperationsMap.remove(operation.key);
+        if (operationToRemove == null) {
+            return;
+        }
+        if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
+            final String errorMessage = "unable to find pending row for " + operationToRemove;
+            Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+        }
+    }
+
+    public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
+        // for each op that matches the account and provider update its
+        // backoff and effectiveStartTime
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.account.equals(account) && op.authority.equals(providerName)
+                    && op.userId == userId) {
+                op.backoff = backoff;
+                op.updateEffectiveRunTime();
+            }
+        }
+    }
+
+    public void onDelayUntilTimeChanged(Account account, String providerName, long delayUntil) {
+        // for each op that matches the account and provider update its
+        // delayUntilTime and effectiveStartTime
+        for (SyncOperation op : mOperationsMap.values()) {
+            if (op.account.equals(account) && op.authority.equals(providerName)) {
+                op.delayUntil = delayUntil;
+                op.updateEffectiveRunTime();
+            }
+        }
+    }
+
+    public void remove(Account account, int userId, String authority) {
+        Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
+        while (entries.hasNext()) {
+            Map.Entry<String, SyncOperation> entry = entries.next();
+            SyncOperation syncOperation = entry.getValue();
+            if (account != null && !syncOperation.account.equals(account)) {
+                continue;
+            }
+            if (authority != null && !syncOperation.authority.equals(authority)) {
+                continue;
+            }
+            if (userId != syncOperation.userId) {
+                continue;
+            }
+            entries.remove();
+            if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
+                final String errorMessage = "unable to find pending row for " + syncOperation;
+                Log.e(TAG, errorMessage, new IllegalStateException(errorMessage));
+            }
+        }
+    }
+
+    public Collection<SyncOperation> getOperations() {
+        return mOperationsMap.values();
+    }
+
+    public void dump(StringBuilder sb) {
+        final long now = SystemClock.elapsedRealtime();
+        sb.append("SyncQueue: ").append(mOperationsMap.size()).append(" operation(s)\n");
+        for (SyncOperation operation : mOperationsMap.values()) {
+            sb.append("  ");
+            if (operation.effectiveRunTime <= now) {
+                sb.append("READY");
+            } else {
+                sb.append(DateUtils.formatElapsedTime((operation.effectiveRunTime - now) / 1000));
+            }
+            sb.append(" - ");
+            sb.append(operation.dump(mPackageManager, false)).append("\n");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
new file mode 100644
index 0000000..5ebf9ea
--- /dev/null
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -0,0 +1,2638 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.content;
+
+import android.accounts.Account;
+import android.accounts.AccountAndUser;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ISyncStatusObserver;
+import android.content.PeriodicSync;
+import android.content.SyncInfo;
+import android.content.SyncStatusInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Pair;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.TimeZone;
+
+/**
+ * Singleton that tracks the sync data and overall sync
+ * history on the device.
+ *
+ * @hide
+ */
+public class SyncStorageEngine extends Handler {
+
+    private static final String TAG = "SyncManager";
+    private static final boolean DEBUG = false;
+    private static final String TAG_FILE = "SyncManagerFile";
+
+    private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
+    private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+    private static final String XML_ATTR_SYNC_RANDOM_OFFSET = "offsetInSeconds";
+    private static final String XML_ATTR_ENABLED = "enabled";
+    private static final String XML_ATTR_USER = "user";
+    private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
+
+    /** Default time for a periodic sync. */
+    private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
+    /** Percentage of period that is flex by default, if no flex is set. */
+    private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04;
+
+    /** Lower bound on sync time from which we assign a default flex time. */
+    private static final long DEFAULT_MIN_FLEX_ALLOWED_SECS = 5;
+
+    @VisibleForTesting
+    static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
+
+    /** Enum value for a sync start event. */
+    public static final int EVENT_START = 0;
+
+    /** Enum value for a sync stop event. */
+    public static final int EVENT_STOP = 1;
+
+    // TODO: i18n -- grab these out of resources.
+    /** String names for the sync event types. */
+    public static final String[] EVENTS = { "START", "STOP" };
+
+    /** Enum value for a server-initiated sync. */
+    public static final int SOURCE_SERVER = 0;
+
+    /** Enum value for a local-initiated sync. */
+    public static final int SOURCE_LOCAL = 1;
+    /**
+     * Enum value for a poll-based sync (e.g., upon connection to
+     * network)
+     */
+    public static final int SOURCE_POLL = 2;
+
+    /** Enum value for a user-initiated sync. */
+    public static final int SOURCE_USER = 3;
+
+    /** Enum value for a periodic sync. */
+    public static final int SOURCE_PERIODIC = 4;
+
+    public static final long NOT_IN_BACKOFF_MODE = -1;
+
+    // TODO: i18n -- grab these out of resources.
+    /** String names for the sync source types. */
+    public static final String[] SOURCES = { "SERVER",
+                                             "LOCAL",
+                                             "POLL",
+                                             "USER",
+                                             "PERIODIC" };
+
+    // The MESG column will contain one of these or one of the Error types.
+    public static final String MESG_SUCCESS = "success";
+    public static final String MESG_CANCELED = "canceled";
+
+    public static final int MAX_HISTORY = 100;
+
+    private static final int MSG_WRITE_STATUS = 1;
+    private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
+
+    private static final int MSG_WRITE_STATISTICS = 2;
+    private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
+
+    private static final boolean SYNC_ENABLED_DEFAULT = false;
+
+    // the version of the accounts xml file format
+    private static final int ACCOUNTS_VERSION = 2;
+
+    private static HashMap<String, String> sAuthorityRenames;
+
+    static {
+        sAuthorityRenames = new HashMap<String, String>();
+        sAuthorityRenames.put("contacts", "com.android.contacts");
+        sAuthorityRenames.put("calendar", "com.android.calendar");
+    }
+
+    public static class PendingOperation {
+        final Account account;
+        final int userId;
+        final int reason;
+        final int syncSource;
+        final String authority;
+        final Bundle extras;        // note: read-only.
+        final ComponentName serviceName;
+        final boolean expedited;
+
+        int authorityId;
+        byte[] flatExtras;
+
+        PendingOperation(Account account, int userId, int reason, int source,
+                String authority, Bundle extras, boolean expedited) {
+            this.account = account;
+            this.userId = userId;
+            this.syncSource = source;
+            this.reason = reason;
+            this.authority = authority;
+            this.extras = extras != null ? new Bundle(extras) : extras;
+            this.expedited = expedited;
+            this.authorityId = -1;
+            this.serviceName = null;
+        }
+
+        PendingOperation(PendingOperation other) {
+            this.account = other.account;
+            this.userId = other.userId;
+            this.reason = other.reason;
+            this.syncSource = other.syncSource;
+            this.authority = other.authority;
+            this.extras = other.extras;
+            this.authorityId = other.authorityId;
+            this.expedited = other.expedited;
+            this.serviceName = other.serviceName;
+        }
+    }
+
+    static class AccountInfo {
+        final AccountAndUser accountAndUser;
+        final HashMap<String, AuthorityInfo> authorities =
+                new HashMap<String, AuthorityInfo>();
+
+        AccountInfo(AccountAndUser accountAndUser) {
+            this.accountAndUser = accountAndUser;
+        }
+    }
+
+    public static class AuthorityInfo {
+        final ComponentName service;
+        final Account account;
+        final int userId;
+        final String authority;
+        final int ident;
+        boolean enabled;
+        int syncable;
+        long backoffTime;
+        long backoffDelay;
+        long delayUntil;
+        final ArrayList<PeriodicSync> periodicSyncs;
+
+        /**
+         * Copy constructor for making deep-ish copies. Only the bundles stored
+         * in periodic syncs can make unexpected changes.
+         *
+         * @param toCopy AuthorityInfo to be copied.
+         */
+        AuthorityInfo(AuthorityInfo toCopy) {
+            account = toCopy.account;
+            userId = toCopy.userId;
+            authority = toCopy.authority;
+            service = toCopy.service;
+            ident = toCopy.ident;
+            enabled = toCopy.enabled;
+            syncable = toCopy.syncable;
+            backoffTime = toCopy.backoffTime;
+            backoffDelay = toCopy.backoffDelay;
+            delayUntil = toCopy.delayUntil;
+            periodicSyncs = new ArrayList<PeriodicSync>();
+            for (PeriodicSync sync : toCopy.periodicSyncs) {
+                // Still not a perfect copy, because we are just copying the mappings.
+                periodicSyncs.add(new PeriodicSync(sync));
+            }
+        }
+
+        /**
+         * Create an authority with one periodic sync scheduled with an empty bundle and syncing
+         * every day. An empty bundle is considered equal to any other bundle see
+         * {@link PeriodicSync.syncExtrasEquals}.
+         * @param account Account that this authority syncs.
+         * @param userId which user this sync is registered for.
+         * @param userId user for which this authority is registered.
+         * @param ident id of this authority.
+         */
+        AuthorityInfo(Account account, int userId, String authority, int ident) {
+            this.account = account;
+            this.userId = userId;
+            this.authority = authority;
+            this.service = null;
+            this.ident = ident;
+            enabled = SYNC_ENABLED_DEFAULT;
+            syncable = -1; // default to "unknown"
+            backoffTime = -1; // if < 0 then we aren't in backoff mode
+            backoffDelay = -1; // if < 0 then we aren't in backoff mode
+            periodicSyncs = new ArrayList<PeriodicSync>();
+            // Old version adds one periodic sync a day.
+            periodicSyncs.add(new PeriodicSync(account, authority,
+                                new Bundle(),
+                                DEFAULT_POLL_FREQUENCY_SECONDS,
+                                calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+        }
+
+        /**
+         * Create an authority with one periodic sync scheduled with an empty bundle and syncing
+         * every day using a sync service.
+         * @param cname sync service identifier.
+         * @param userId user for which this authority is registered.
+         * @param ident id of this authority.
+         */
+        AuthorityInfo(ComponentName cname, int userId, int ident) {
+            this.account = null;
+            this.userId = userId;
+            this.authority = null;
+            this.service = cname;
+            this.ident = ident;
+            // Sync service is always enabled.
+            enabled = true;
+            syncable = -1; // default to "unknown"
+            backoffTime = -1; // if < 0 then we aren't in backoff mode
+            backoffDelay = -1; // if < 0 then we aren't in backoff mode
+            periodicSyncs = new ArrayList<PeriodicSync>();
+            periodicSyncs.add(new PeriodicSync(account, authority,
+                                new Bundle(),
+                                DEFAULT_POLL_FREQUENCY_SECONDS,
+                                calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+        }
+    }
+
+    public static class SyncHistoryItem {
+        int authorityId;
+        int historyId;
+        long eventTime;
+        long elapsedTime;
+        int source;
+        int event;
+        long upstreamActivity;
+        long downstreamActivity;
+        String mesg;
+        boolean initialization;
+        Bundle extras;
+        int reason;
+    }
+
+    public static class DayStats {
+        public final int day;
+        public int successCount;
+        public long successTime;
+        public int failureCount;
+        public long failureTime;
+
+        public DayStats(int day) {
+            this.day = day;
+        }
+    }
+
+    interface OnSyncRequestListener {
+        /**
+         * Called when a sync is needed on an account(s) due to some change in state.
+         * @param account
+         * @param userId
+         * @param reason
+         * @param authority
+         * @param extras
+         */
+        public void onSyncRequest(Account account, int userId, int reason, String authority,
+                Bundle extras);
+    }
+
+    // Primary list of all syncable authorities.  Also our global lock.
+    private final SparseArray<AuthorityInfo> mAuthorities =
+            new SparseArray<AuthorityInfo>();
+
+    private final HashMap<AccountAndUser, AccountInfo> mAccounts
+            = new HashMap<AccountAndUser, AccountInfo>();
+
+    private final ArrayList<PendingOperation> mPendingOperations =
+            new ArrayList<PendingOperation>();
+
+    private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
+            = new SparseArray<ArrayList<SyncInfo>>();
+
+    private final SparseArray<SyncStatusInfo> mSyncStatus =
+            new SparseArray<SyncStatusInfo>();
+
+    private final ArrayList<SyncHistoryItem> mSyncHistory =
+            new ArrayList<SyncHistoryItem>();
+
+    private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
+            = new RemoteCallbackList<ISyncStatusObserver>();
+
+    /** Reverse mapping for component name -> <userid -> authority id>. */
+    private final HashMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
+            new HashMap<ComponentName, SparseArray<AuthorityInfo>>();
+
+    private int mNextAuthorityId = 0;
+
+    // We keep 4 weeks of stats.
+    private final DayStats[] mDayStats = new DayStats[7*4];
+    private final Calendar mCal;
+    private int mYear;
+    private int mYearInDays;
+
+    private final Context mContext;
+
+    private static volatile SyncStorageEngine sSyncStorageEngine = null;
+
+    private int mSyncRandomOffset;
+
+    /**
+     * This file contains the core engine state: all accounts and the
+     * settings for them.  It must never be lost, and should be changed
+     * infrequently, so it is stored as an XML file.
+     */
+    private final AtomicFile mAccountInfoFile;
+
+    /**
+     * This file contains the current sync status.  We would like to retain
+     * it across boots, but its loss is not the end of the world, so we store
+     * this information as binary data.
+     */
+    private final AtomicFile mStatusFile;
+
+    /**
+     * This file contains sync statistics.  This is purely debugging information
+     * so is written infrequently and can be thrown away at any time.
+     */
+    private final AtomicFile mStatisticsFile;
+
+    /**
+     * This file contains the pending sync operations.  It is a binary file,
+     * which must be updated every time an operation is added or removed,
+     * so we have special handling of it.
+     */
+    private final AtomicFile mPendingFile;
+    private static final int PENDING_FINISH_TO_WRITE = 4;
+    private int mNumPendingFinished = 0;
+
+    private int mNextHistoryId = 0;
+    private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
+    private boolean mDefaultMasterSyncAutomatically;
+
+    private OnSyncRequestListener mSyncRequestListener;
+
+    private SyncStorageEngine(Context context, File dataDir) {
+        mContext = context;
+        sSyncStorageEngine = this;
+
+        mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
+
+        mDefaultMasterSyncAutomatically = mContext.getResources().getBoolean(
+               com.android.internal.R.bool.config_syncstorageengine_masterSyncAutomatically);
+
+        File systemDir = new File(dataDir, "system");
+        File syncDir = new File(systemDir, "sync");
+        syncDir.mkdirs();
+
+        maybeDeleteLegacyPendingInfoLocked(syncDir);
+
+        mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
+        mPendingFile = new AtomicFile(new File(syncDir, "pending.xml"));
+        mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
+
+        readAccountInfoLocked();
+        readStatusLocked();
+        readPendingOperationsLocked();
+        readStatisticsLocked();
+        readAndDeleteLegacyAccountInfoLocked();
+        writeAccountInfoLocked();
+        writeStatusLocked();
+        writePendingOperationsLocked();
+        writeStatisticsLocked();
+    }
+
+    public static SyncStorageEngine newTestInstance(Context context) {
+        return new SyncStorageEngine(context, context.getFilesDir());
+    }
+
+    public static void init(Context context) {
+        if (sSyncStorageEngine != null) {
+            return;
+        }
+        // This call will return the correct directory whether Encrypted File Systems is
+        // enabled or not.
+        File dataDir = Environment.getSecureDataDirectory();
+        sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
+    }
+
+    public static SyncStorageEngine getSingleton() {
+        if (sSyncStorageEngine == null) {
+            throw new IllegalStateException("not initialized");
+        }
+        return sSyncStorageEngine;
+    }
+
+    protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
+        if (mSyncRequestListener == null) {
+            mSyncRequestListener = listener;
+        }
+    }
+
+    @Override public void handleMessage(Message msg) {
+        if (msg.what == MSG_WRITE_STATUS) {
+            synchronized (mAuthorities) {
+                writeStatusLocked();
+            }
+        } else if (msg.what == MSG_WRITE_STATISTICS) {
+            synchronized (mAuthorities) {
+                writeStatisticsLocked();
+            }
+        }
+    }
+
+    public int getSyncRandomOffset() {
+        return mSyncRandomOffset;
+    }
+
+    public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
+        synchronized (mAuthorities) {
+            mChangeListeners.register(callback, mask);
+        }
+    }
+
+    public void removeStatusChangeListener(ISyncStatusObserver callback) {
+        synchronized (mAuthorities) {
+            mChangeListeners.unregister(callback);
+        }
+    }
+
+    /**
+     * Figure out a reasonable flex time for cases where none is provided (old api calls).
+     * @param syncTimeSeconds requested sync time from now.
+     * @return amount of seconds before syncTimeSeconds that the sync can occur.
+     *      I.e.
+     *      earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
+     * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}.
+     */
+    public static long calculateDefaultFlexTime(long syncTimeSeconds) {
+        if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
+            // Small enough sync request time that we don't add flex time - developer probably
+            // wants to wait for an operation to occur before syncing so we honour the
+            // request time.
+            return 0L;
+        } else if (syncTimeSeconds < DEFAULT_POLL_FREQUENCY_SECONDS) {
+            return (long) (syncTimeSeconds * DEFAULT_FLEX_PERCENT_SYNC);
+        } else {
+            // Large enough sync request time that we cap the flex time.
+            return (long) (DEFAULT_POLL_FREQUENCY_SECONDS * DEFAULT_FLEX_PERCENT_SYNC);
+        }
+    }
+
+    private void reportChange(int which) {
+        ArrayList<ISyncStatusObserver> reports = null;
+        synchronized (mAuthorities) {
+            int i = mChangeListeners.beginBroadcast();
+            while (i > 0) {
+                i--;
+                Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
+                if ((which & mask.intValue()) == 0) {
+                    continue;
+                }
+                if (reports == null) {
+                    reports = new ArrayList<ISyncStatusObserver>(i);
+                }
+                reports.add(mChangeListeners.getBroadcastItem(i));
+            }
+            mChangeListeners.finishBroadcast();
+        }
+
+        if (DEBUG) {
+            Log.v(TAG, "reportChange " + which + " to: " + reports);
+        }
+
+        if (reports != null) {
+            int i = reports.size();
+            while (i > 0) {
+                i--;
+                try {
+                    reports.get(i).onStatusChanged(which);
+                } catch (RemoteException e) {
+                    // The remote callback list will take care of this for us.
+                }
+            }
+        }
+    }
+
+    public boolean getSyncAutomatically(Account account, int userId, String providerName) {
+        synchronized (mAuthorities) {
+            if (account != null) {
+                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                        "getSyncAutomatically");
+                return authority != null && authority.enabled;
+            }
+
+            int i = mAuthorities.size();
+            while (i > 0) {
+                i--;
+                AuthorityInfo authority = mAuthorities.valueAt(i);
+                if (authority.authority.equals(providerName)
+                        && authority.userId == userId
+                        && authority.enabled) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    public void setSyncAutomatically(Account account, int userId, String providerName,
+            boolean sync) {
+        if (DEBUG) {
+            Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
+                    + ", user " + userId + " -> " + sync);
+        }
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
+                    false);
+            if (authority.enabled == sync) {
+                if (DEBUG) {
+                    Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
+                }
+                return;
+            }
+            authority.enabled = sync;
+            writeAccountInfoLocked();
+        }
+
+        if (sync) {
+            requestSync(account, userId, SyncOperation.REASON_SYNC_AUTO, providerName,
+                    new Bundle());
+        }
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public int getIsSyncable(Account account, int userId, String providerName) {
+        synchronized (mAuthorities) {
+            if (account != null) {
+                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                        "getIsSyncable");
+                if (authority == null) {
+                    return -1;
+                }
+                return authority.syncable;
+            }
+
+            int i = mAuthorities.size();
+            while (i > 0) {
+                i--;
+                AuthorityInfo authority = mAuthorities.valueAt(i);
+                if (authority.authority.equals(providerName)) {
+                    return authority.syncable;
+                }
+            }
+            return -1;
+        }
+    }
+
+    public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
+        if (syncable > 1) {
+            syncable = 1;
+        } else if (syncable < -1) {
+            syncable = -1;
+        }
+        if (DEBUG) {
+            Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
+                    + ", user " + userId + " -> " + syncable);
+        }
+        synchronized (mAuthorities) {
+            AuthorityInfo authority =
+                    getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
+            if (authority.syncable == syncable) {
+                if (DEBUG) {
+                    Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
+                }
+                return;
+            }
+            authority.syncable = syncable;
+            writeAccountInfoLocked();
+        }
+
+        if (syncable > 0) {
+            requestSync(account, userId, SyncOperation.REASON_IS_SYNCABLE,  providerName,
+                    new Bundle());
+        }
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getBackoff");
+            if (authority == null || authority.backoffTime < 0) {
+                return null;
+            }
+            return Pair.create(authority.backoffTime, authority.backoffDelay);
+        }
+    }
+
+    public void setBackoff(Account account, int userId, String providerName,
+            long nextSyncTime, long nextDelay) {
+        if (DEBUG) {
+            Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+                    + ", user " + userId
+                    + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
+        }
+        boolean changed = false;
+        synchronized (mAuthorities) {
+            if (account == null || providerName == null) {
+                for (AccountInfo accountInfo : mAccounts.values()) {
+                    if (account != null && !account.equals(accountInfo.accountAndUser.account)
+                            && userId != accountInfo.accountAndUser.userId) {
+                        continue;
+                    }
+                    for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+                        if (providerName != null
+                                && !providerName.equals(authorityInfo.authority)) {
+                            continue;
+                        }
+                        if (authorityInfo.backoffTime != nextSyncTime
+                                || authorityInfo.backoffDelay != nextDelay) {
+                            authorityInfo.backoffTime = nextSyncTime;
+                            authorityInfo.backoffDelay = nextDelay;
+                            changed = true;
+                        }
+                    }
+                }
+            } else {
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
+                                true);
+                if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
+                    return;
+                }
+                authority.backoffTime = nextSyncTime;
+                authority.backoffDelay = nextDelay;
+                changed = true;
+            }
+        }
+
+        if (changed) {
+            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+        }
+    }
+
+    /**
+     * Callers of this function need to hold a lock for syncQueue object passed in. Bear in mind
+     * this function grabs the lock for {@link #mAuthorities}
+     * @param syncQueue queue containing pending sync operations.
+     */
+    public void clearAllBackoffsLocked(SyncQueue syncQueue) {
+        boolean changed = false;
+        synchronized (mAuthorities) {
+            for (AccountInfo accountInfo : mAccounts.values()) {
+                for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
+                    if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE
+                            || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) {
+                        if (DEBUG) {
+                            Log.v(TAG, "clearAllBackoffs:"
+                                    + " authority:" + authorityInfo.authority
+                                    + " account:" + accountInfo.accountAndUser.account.name
+                                    + " user:" + accountInfo.accountAndUser.userId
+                                    + " backoffTime was: " + authorityInfo.backoffTime
+                                    + " backoffDelay was: " + authorityInfo.backoffDelay);
+                        }
+                        authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
+                        authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
+                        syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
+                                accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
+                        changed = true;
+                    }
+                }
+            }
+        }
+
+        if (changed) {
+            reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+        }
+    }
+
+    public void setDelayUntilTime(Account account, int userId, String providerName,
+            long delayUntil) {
+        if (DEBUG) {
+            Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
+                    + ", user " + userId + " -> delayUntil " + delayUntil);
+        }
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getOrCreateAuthorityLocked(
+                    account, userId, providerName, -1 /* ident */, true);
+            if (authority.delayUntil == delayUntil) {
+                return;
+            }
+            authority.delayUntil = delayUntil;
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public long getDelayUntilTime(Account account, int userId, String providerName) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getDelayUntil");
+            if (authority == null) {
+                return 0;
+            }
+            return authority.delayUntil;
+        }
+    }
+
+    private void updateOrRemovePeriodicSync(PeriodicSync toUpdate, int userId, boolean add) {
+        if (DEBUG) {
+            Log.v(TAG, "addOrRemovePeriodicSync: " + toUpdate.account + ", user " + userId
+                    + ", provider " + toUpdate.authority
+                    + " -> period " + toUpdate.period + ", extras " + toUpdate.extras);
+        }
+        synchronized (mAuthorities) {
+            if (toUpdate.period <= 0 && add) {
+                Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-"
+                        + add);
+            }
+            if (toUpdate.extras == null) {
+                Log.e(TAG, "null extras, should never happen in updateOrRemovePeriodicSync: add-"
+                        + add);
+            }
+            try {
+                AuthorityInfo authority =
+                        getOrCreateAuthorityLocked(toUpdate.account, userId, toUpdate.authority,
+                                -1, false);
+                if (add) {
+                    // add this periodic sync if an equivalent periodic doesn't already exist.
+                    boolean alreadyPresent = false;
+                    for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+                        PeriodicSync syncInfo = authority.periodicSyncs.get(i);
+                        if (PeriodicSync.syncExtrasEquals(
+                                toUpdate.extras,
+                                syncInfo.extras)) {
+                            if (toUpdate.period == syncInfo.period &&
+                                    toUpdate.flexTime == syncInfo.flexTime) {
+                                // Absolutely the same.
+                                return;
+                            }
+                            authority.periodicSyncs.set(i, new PeriodicSync(toUpdate));
+                            alreadyPresent = true;
+                            break;
+                        }
+                    }
+                    // If we added an entry to the periodicSyncs array also add an entry to
+                    // the periodic syncs status to correspond to it.
+                    if (!alreadyPresent) {
+                        authority.periodicSyncs.add(new PeriodicSync(toUpdate));
+                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                        status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0L);
+                    }
+                } else {
+                    // Remove any periodic syncs that match the authority and extras.
+                    SyncStatusInfo status = mSyncStatus.get(authority.ident);
+                    boolean changed = false;
+                    Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
+                    int i = 0;
+                    while (iterator.hasNext()) {
+                        PeriodicSync syncInfo = iterator.next();
+                        if (PeriodicSync.syncExtrasEquals(syncInfo.extras, toUpdate.extras)) {
+                            iterator.remove();
+                            changed = true;
+                            // If we removed an entry from the periodicSyncs array also
+                            // remove the corresponding entry from the status
+                            if (status != null) {
+                                status.removePeriodicSyncTime(i);
+                            } else {
+                                Log.e(TAG, "Tried removing sync status on remove periodic sync but"
+                                        + "did not find it.");
+                            }
+                        } else {
+                            i++;
+                        }
+                    }
+                    if (!changed) {
+                        return;
+                    }
+                }
+            } finally {
+                writeAccountInfoLocked();
+                writeStatusLocked();
+            }
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+    }
+
+    public void addPeriodicSync(PeriodicSync toAdd, int userId) {
+        updateOrRemovePeriodicSync(toAdd, userId, true /* add */);
+    }
+
+    public void removePeriodicSync(PeriodicSync toRemove, int userId) {
+        updateOrRemovePeriodicSync(toRemove, userId, false /* remove */);
+    }
+
+    public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
+        ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+        synchronized (mAuthorities) {
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getPeriodicSyncs");
+            if (authority != null) {
+                for (PeriodicSync item : authority.periodicSyncs) {
+                    // Copy and send out. Necessary for thread-safety although it's parceled.
+                    syncs.add(new PeriodicSync(item));
+                }
+            }
+        }
+        return syncs;
+    }
+
+    public void setMasterSyncAutomatically(boolean flag, int userId) {
+        synchronized (mAuthorities) {
+            Boolean auto = mMasterSyncAutomatically.get(userId);
+            if (auto != null && (boolean) auto == flag) {
+                return;
+            }
+            mMasterSyncAutomatically.put(userId, flag);
+            writeAccountInfoLocked();
+        }
+        if (flag) {
+            requestSync(null, userId, SyncOperation.REASON_MASTER_SYNC_AUTO, null,
+                    new Bundle());
+        }
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+        mContext.sendBroadcast(ContentResolver.ACTION_SYNC_CONN_STATUS_CHANGED);
+    }
+
+    public boolean getMasterSyncAutomatically(int userId) {
+        synchronized (mAuthorities) {
+            Boolean auto = mMasterSyncAutomatically.get(userId);
+            return auto == null ? mDefaultMasterSyncAutomatically : auto;
+        }
+    }
+
+    public void removeAuthority(Account account, int userId, String authority) {
+        synchronized (mAuthorities) {
+            removeAuthorityLocked(account, userId, authority, true /* doWrite */);
+        }
+    }
+
+    public AuthorityInfo getAuthority(int authorityId) {
+        synchronized (mAuthorities) {
+            return mAuthorities.get(authorityId);
+        }
+    }
+
+    /**
+     * Returns true if there is currently a sync operation for the given
+     * account or authority actively being processed.
+     */
+    public boolean isSyncActive(Account account, int userId, String authority) {
+        synchronized (mAuthorities) {
+            for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
+                AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
+                if (ainfo != null && ainfo.account.equals(account)
+                        && ainfo.authority.equals(authority)
+                        && ainfo.userId == userId) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    public PendingOperation insertIntoPending(PendingOperation op) {
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "insertIntoPending: account=" + op.account
+                        + " user=" + op.userId
+                        + " auth=" + op.authority
+                        + " src=" + op.syncSource
+                        + " extras=" + op.extras);
+            }
+
+            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
+                    op.authority,
+                    -1 /* desired identifier */,
+                    true /* write accounts to storage */);
+            if (authority == null) {
+                return null;
+            }
+
+            op = new PendingOperation(op);
+            op.authorityId = authority.ident;
+            mPendingOperations.add(op);
+            appendPendingOperationLocked(op);
+
+            SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+            status.pending = true;
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
+        return op;
+    }
+
+    /**
+     * Remove from list of pending operations. If successful, search through list for matching
+     * authorities. If there are no more pending syncs for the same authority/account/userid,
+     * update the SyncStatusInfo for that authority(authority here is the internal representation
+     * of a 'sync operation'.
+     * @param op
+     * @return
+     */
+    public boolean deleteFromPending(PendingOperation op) {
+        boolean res = false;
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "deleteFromPending: account=" + op.account
+                    + " user=" + op.userId
+                    + " auth=" + op.authority
+                    + " src=" + op.syncSource
+                    + " extras=" + op.extras);
+            }
+            if (mPendingOperations.remove(op)) {
+                if (mPendingOperations.size() == 0
+                        || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
+                    writePendingOperationsLocked();
+                    mNumPendingFinished = 0;
+                } else {
+                    mNumPendingFinished++;
+                }
+
+                AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
+                        "deleteFromPending");
+                if (authority != null) {
+                    if (DEBUG) Log.v(TAG, "removing - " + authority.toString());
+                    final int N = mPendingOperations.size();
+                    boolean morePending = false;
+                    for (int i=0; i<N; i++) {
+                        PendingOperation cur = mPendingOperations.get(i);
+                        if (cur.account.equals(op.account)
+                                && cur.authority.equals(op.authority)
+                                && cur.userId == op.userId) {
+                            morePending = true;
+                            break;
+                        }
+                    }
+
+                    if (!morePending) {
+                        if (DEBUG) Log.v(TAG, "no more pending!");
+                        SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+                        status.pending = false;
+                    }
+                }
+
+                res = true;
+            }
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
+        return res;
+    }
+
+    /**
+     * Return a copy of the current array of pending operations.  The
+     * PendingOperation objects are the real objects stored inside, so that
+     * they can be used with deleteFromPending().
+     */
+    public ArrayList<PendingOperation> getPendingOperations() {
+        synchronized (mAuthorities) {
+            return new ArrayList<PendingOperation>(mPendingOperations);
+        }
+    }
+
+    /**
+     * Return the number of currently pending operations.
+     */
+    public int getPendingOperationCount() {
+        synchronized (mAuthorities) {
+            return mPendingOperations.size();
+        }
+    }
+
+    /**
+     * Called when the set of account has changed, given the new array of
+     * active accounts.
+     */
+    public void doDatabaseCleanup(Account[] accounts, int userId) {
+        synchronized (mAuthorities) {
+            if (DEBUG) Log.v(TAG, "Updating for new accounts...");
+            SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
+            Iterator<AccountInfo> accIt = mAccounts.values().iterator();
+            while (accIt.hasNext()) {
+                AccountInfo acc = accIt.next();
+                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
+                        && acc.accountAndUser.userId == userId) {
+                    // This account no longer exists...
+                    if (DEBUG) {
+                        Log.v(TAG, "Account removed: " + acc.accountAndUser);
+                    }
+                    for (AuthorityInfo auth : acc.authorities.values()) {
+                        removing.put(auth.ident, auth);
+                    }
+                    accIt.remove();
+                }
+            }
+
+            // Clean out all data structures.
+            int i = removing.size();
+            if (i > 0) {
+                while (i > 0) {
+                    i--;
+                    int ident = removing.keyAt(i);
+                    mAuthorities.remove(ident);
+                    int j = mSyncStatus.size();
+                    while (j > 0) {
+                        j--;
+                        if (mSyncStatus.keyAt(j) == ident) {
+                            mSyncStatus.remove(mSyncStatus.keyAt(j));
+                        }
+                    }
+                    j = mSyncHistory.size();
+                    while (j > 0) {
+                        j--;
+                        if (mSyncHistory.get(j).authorityId == ident) {
+                            mSyncHistory.remove(j);
+                        }
+                    }
+                }
+                writeAccountInfoLocked();
+                writeStatusLocked();
+                writePendingOperationsLocked();
+                writeStatisticsLocked();
+            }
+        }
+    }
+
+    /**
+     * Called when a sync is starting. Supply a valid ActiveSyncContext with information
+     * about the sync.
+     */
+    public SyncInfo addActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
+        final SyncInfo syncInfo;
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "setActiveSync: account="
+                    + activeSyncContext.mSyncOperation.account
+                    + " auth=" + activeSyncContext.mSyncOperation.authority
+                    + " src=" + activeSyncContext.mSyncOperation.syncSource
+                    + " extras=" + activeSyncContext.mSyncOperation.extras);
+            }
+            AuthorityInfo authority = getOrCreateAuthorityLocked(
+                    activeSyncContext.mSyncOperation.account,
+                    activeSyncContext.mSyncOperation.userId,
+                    activeSyncContext.mSyncOperation.authority,
+                    -1 /* assign a new identifier if creating a new authority */,
+                    true /* write to storage if this results in a change */);
+            syncInfo = new SyncInfo(authority.ident,
+                    authority.account, authority.authority,
+                    activeSyncContext.mStartTime);
+            getCurrentSyncs(authority.userId).add(syncInfo);
+        }
+
+        reportActiveChange();
+        return syncInfo;
+    }
+
+    /**
+     * Called to indicate that a previously active sync is no longer active.
+     */
+    public void removeActiveSync(SyncInfo syncInfo, int userId) {
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
+                        + " user=" + userId
+                        + " auth=" + syncInfo.authority);
+            }
+            getCurrentSyncs(userId).remove(syncInfo);
+        }
+
+        reportActiveChange();
+    }
+
+    /**
+     * To allow others to send active change reports, to poke clients.
+     */
+    public void reportActiveChange() {
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
+    }
+
+    /**
+     * Note that sync has started for the given account and authority.
+     */
+    public long insertStartSyncEvent(Account accountName, int userId, int reason,
+            String authorityName, long now, int source, boolean initialization, Bundle extras) {
+        long id;
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
+                    + " auth=" + authorityName + " source=" + source);
+            }
+            AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
+                    "insertStartSyncEvent");
+            if (authority == null) {
+                return -1;
+            }
+            SyncHistoryItem item = new SyncHistoryItem();
+            item.initialization = initialization;
+            item.authorityId = authority.ident;
+            item.historyId = mNextHistoryId++;
+            if (mNextHistoryId < 0) mNextHistoryId = 0;
+            item.eventTime = now;
+            item.source = source;
+            item.reason = reason;
+            item.extras = extras;
+            item.event = EVENT_START;
+            mSyncHistory.add(0, item);
+            while (mSyncHistory.size() > MAX_HISTORY) {
+                mSyncHistory.remove(mSyncHistory.size()-1);
+            }
+            id = item.historyId;
+            if (DEBUG) Log.v(TAG, "returning historyId " + id);
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
+        return id;
+    }
+
+    public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+            long downstreamActivity, long upstreamActivity) {
+        synchronized (mAuthorities) {
+            if (DEBUG) {
+                Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
+            }
+            SyncHistoryItem item = null;
+            int i = mSyncHistory.size();
+            while (i > 0) {
+                i--;
+                item = mSyncHistory.get(i);
+                if (item.historyId == historyId) {
+                    break;
+                }
+                item = null;
+            }
+
+            if (item == null) {
+                Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
+                return;
+            }
+
+            item.elapsedTime = elapsedTime;
+            item.event = EVENT_STOP;
+            item.mesg = resultMessage;
+            item.downstreamActivity = downstreamActivity;
+            item.upstreamActivity = upstreamActivity;
+
+            SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
+
+            status.numSyncs++;
+            status.totalElapsedTime += elapsedTime;
+            switch (item.source) {
+                case SOURCE_LOCAL:
+                    status.numSourceLocal++;
+                    break;
+                case SOURCE_POLL:
+                    status.numSourcePoll++;
+                    break;
+                case SOURCE_USER:
+                    status.numSourceUser++;
+                    break;
+                case SOURCE_SERVER:
+                    status.numSourceServer++;
+                    break;
+                case SOURCE_PERIODIC:
+                    status.numSourcePeriodic++;
+                    break;
+            }
+
+            boolean writeStatisticsNow = false;
+            int day = getCurrentDayLocked();
+            if (mDayStats[0] == null) {
+                mDayStats[0] = new DayStats(day);
+            } else if (day != mDayStats[0].day) {
+                System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
+                mDayStats[0] = new DayStats(day);
+                writeStatisticsNow = true;
+            } else if (mDayStats[0] == null) {
+            }
+            final DayStats ds = mDayStats[0];
+
+            final long lastSyncTime = (item.eventTime + elapsedTime);
+            boolean writeStatusNow = false;
+            if (MESG_SUCCESS.equals(resultMessage)) {
+                // - if successful, update the successful columns
+                if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
+                    writeStatusNow = true;
+                }
+                status.lastSuccessTime = lastSyncTime;
+                status.lastSuccessSource = item.source;
+                status.lastFailureTime = 0;
+                status.lastFailureSource = -1;
+                status.lastFailureMesg = null;
+                status.initialFailureTime = 0;
+                ds.successCount++;
+                ds.successTime += elapsedTime;
+            } else if (!MESG_CANCELED.equals(resultMessage)) {
+                if (status.lastFailureTime == 0) {
+                    writeStatusNow = true;
+                }
+                status.lastFailureTime = lastSyncTime;
+                status.lastFailureSource = item.source;
+                status.lastFailureMesg = resultMessage;
+                if (status.initialFailureTime == 0) {
+                    status.initialFailureTime = lastSyncTime;
+                }
+                ds.failureCount++;
+                ds.failureTime += elapsedTime;
+            }
+
+            if (writeStatusNow) {
+                writeStatusLocked();
+            } else if (!hasMessages(MSG_WRITE_STATUS)) {
+                sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
+                        WRITE_STATUS_DELAY);
+            }
+            if (writeStatisticsNow) {
+                writeStatisticsLocked();
+            } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
+                sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
+                        WRITE_STATISTICS_DELAY);
+            }
+        }
+
+        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
+    }
+
+    /**
+     * Return a list of the currently active syncs. Note that the returned
+     * items are the real, live active sync objects, so be careful what you do
+     * with it.
+     */
+    private List<SyncInfo> getCurrentSyncs(int userId) {
+        synchronized (mAuthorities) {
+            return getCurrentSyncsLocked(userId);
+        }
+    }
+
+    /**
+     * @return a copy of the current syncs data structure. Will not return
+     * null.
+     */
+    public List<SyncInfo> getCurrentSyncsCopy(int userId) {
+        synchronized (mAuthorities) {
+            final List<SyncInfo> syncs = getCurrentSyncsLocked(userId);
+            final List<SyncInfo> syncsCopy = new ArrayList<SyncInfo>();
+            for (SyncInfo sync : syncs) {
+                syncsCopy.add(new SyncInfo(sync));
+            }
+            return syncsCopy;
+        }
+    }
+
+    private List<SyncInfo> getCurrentSyncsLocked(int userId) {
+        ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
+        if (syncs == null) {
+            syncs = new ArrayList<SyncInfo>();
+            mCurrentSyncs.put(userId, syncs);
+        }
+        return syncs;
+    }
+
+    /**
+     * Return an array of the current sync status for all authorities.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public ArrayList<SyncStatusInfo> getSyncStatus() {
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
+            for (int i=0; i<N; i++) {
+                ops.add(mSyncStatus.valueAt(i));
+            }
+            return ops;
+        }
+    }
+
+    /**
+     * Return a copy of the specified authority with the corresponding sync status
+     */
+    public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus(
+            Account account, int userId, String authority) {
+        synchronized (mAuthorities) {
+            AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(account, userId, authority,
+                    -1 /* assign a new identifier if creating a new authority */,
+                    true /* write to storage if this results in a change */);
+            return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo);
+        }
+    }
+
+    /**
+     * Return a copy of all authorities with their corresponding sync status
+     */
+    public ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> getCopyOfAllAuthoritiesWithSyncStatus() {
+        synchronized (mAuthorities) {
+            ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos =
+                    new ArrayList<Pair<AuthorityInfo, SyncStatusInfo>>(mAuthorities.size());
+            for (int i = 0; i < mAuthorities.size(); i++) {
+                infos.add(createCopyPairOfAuthorityWithSyncStatusLocked(mAuthorities.valueAt(i)));
+            }
+            return infos;
+        }
+    }
+
+    /**
+     * Returns the status that matches the authority and account.
+     *
+     * @param account the account we want to check
+     * @param authority the authority whose row should be selected
+     * @return the SyncStatusInfo for the authority or null if none found.
+     */
+    public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
+            String authority) {
+        if (account == null || authority == null) {
+          return null;
+        }
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo cur = mSyncStatus.valueAt(i);
+                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+
+                if (ainfo != null && ainfo.authority.equals(authority)
+                        && ainfo.userId == userId
+                        && account.equals(ainfo.account)) {
+                  return cur;
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Return true if the pending status is true of any matching authorities.
+     */
+    public boolean isSyncPending(Account account, int userId, String authority) {
+        synchronized (mAuthorities) {
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo cur = mSyncStatus.valueAt(i);
+                AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
+                if (ainfo == null) {
+                    continue;
+                }
+                if (userId != ainfo.userId) {
+                    continue;
+                }
+                if (account != null && !ainfo.account.equals(account)) {
+                    continue;
+                }
+                if (ainfo.authority.equals(authority) && cur.pending) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Return an array of the current sync status for all authorities.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public ArrayList<SyncHistoryItem> getSyncHistory() {
+        synchronized (mAuthorities) {
+            final int N = mSyncHistory.size();
+            ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
+            for (int i=0; i<N; i++) {
+                items.add(mSyncHistory.get(i));
+            }
+            return items;
+        }
+    }
+
+    /**
+     * Return an array of the current per-day statistics.  Note
+     * that the objects inside the array are the real, live status objects,
+     * so be careful what you do with them.
+     */
+    public DayStats[] getDayStatistics() {
+        synchronized (mAuthorities) {
+            DayStats[] ds = new DayStats[mDayStats.length];
+            System.arraycopy(mDayStats, 0, ds, 0, ds.length);
+            return ds;
+        }
+    }
+
+    private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked(
+            AuthorityInfo authorityInfo) {
+        SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident);
+        return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo));
+    }
+
+    private int getCurrentDayLocked() {
+        mCal.setTimeInMillis(System.currentTimeMillis());
+        final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
+        if (mYear != mCal.get(Calendar.YEAR)) {
+            mYear = mCal.get(Calendar.YEAR);
+            mCal.clear();
+            mCal.set(Calendar.YEAR, mYear);
+            mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
+        }
+        return dayOfYear + mYearInDays;
+    }
+
+    /**
+     * Retrieve an authority, returning null if one does not exist.
+     *
+     * @param accountName The name of the account for the authority.
+     * @param authorityName The name of the authority itself.
+     * @param tag If non-null, this will be used in a log message if the
+     * requested authority does not exist.
+     */
+    private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
+            String tag) {
+        AccountAndUser au = new AccountAndUser(accountName, userId);
+        AccountInfo accountInfo = mAccounts.get(au);
+        if (accountInfo == null) {
+            if (tag != null) {
+                if (DEBUG) {
+                    Log.v(TAG, tag + ": unknown account " + au);
+                }
+            }
+            return null;
+        }
+        AuthorityInfo authority = accountInfo.authorities.get(authorityName);
+        if (authority == null) {
+            if (tag != null) {
+                if (DEBUG) {
+                    Log.v(TAG, tag + ": unknown authority " + authorityName);
+                }
+            }
+            return null;
+        }
+
+        return authority;
+    }
+
+    /**
+     * Retrieve an authority, returning null if one does not exist.
+     *
+     * @param service The service name used for this sync.
+     * @param userId The user for whom this sync is scheduled.
+     * @param tag If non-null, this will be used in a log message if the
+     * requested authority does not exist.
+     */
+    private AuthorityInfo getAuthorityLocked(ComponentName service, int userId, String tag) {
+        AuthorityInfo authority = mServices.get(service).get(userId);
+        if (authority == null) {
+            if (tag != null) {
+                if (DEBUG) {
+                    Log.v(TAG, tag + " No authority info found for " + service + " for user "
+                            + userId);
+                }
+            }
+            return null;
+        }
+        return authority;
+    }
+
+    /**
+     * @param cname identifier for the service.
+     * @param userId for the syncs corresponding to this authority.
+     * @param ident unique identifier for authority. -1 for none.
+     * @param doWrite if true, update the accounts.xml file on the disk.
+     * @return the authority that corresponds to the provided sync service, creating it if none
+     * exists.
+     */
+    private AuthorityInfo getOrCreateAuthorityLocked(ComponentName cname, int userId, int ident,
+            boolean doWrite) {
+        SparseArray<AuthorityInfo> aInfo = mServices.get(cname);
+        if (aInfo == null) {
+            aInfo = new SparseArray<AuthorityInfo>();
+            mServices.put(cname, aInfo);
+        }
+        AuthorityInfo authority = aInfo.get(userId);
+        if (authority == null) {
+            if (ident < 0) {
+                ident = mNextAuthorityId;
+                mNextAuthorityId++;
+                doWrite = true;
+            }
+            if (DEBUG) {
+                Log.v(TAG, "created a new AuthorityInfo for " + cname.getPackageName()
+                        + ", " + cname.getClassName()
+                        + ", user: " + userId);
+            }
+            authority = new AuthorityInfo(cname, userId, ident);
+            aInfo.put(userId, authority);
+            mAuthorities.put(ident, authority);
+            if (doWrite) {
+                writeAccountInfoLocked();
+            }
+        }
+        return authority;
+    }
+
+    private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
+            String authorityName, int ident, boolean doWrite) {
+        AccountAndUser au = new AccountAndUser(accountName, userId);
+        AccountInfo account = mAccounts.get(au);
+        if (account == null) {
+            account = new AccountInfo(au);
+            mAccounts.put(au, account);
+        }
+        AuthorityInfo authority = account.authorities.get(authorityName);
+        if (authority == null) {
+            if (ident < 0) {
+                ident = mNextAuthorityId;
+                mNextAuthorityId++;
+                doWrite = true;
+            }
+            if (DEBUG) {
+                Log.v(TAG, "created a new AuthorityInfo for " + accountName
+                        + ", user " + userId
+                        + ", provider " + authorityName);
+            }
+            authority = new AuthorityInfo(accountName, userId, authorityName, ident);
+            account.authorities.put(authorityName, authority);
+            mAuthorities.put(ident, authority);
+            if (doWrite) {
+                writeAccountInfoLocked();
+            }
+        }
+
+        return authority;
+    }
+
+    private void removeAuthorityLocked(Account account, int userId, String authorityName,
+            boolean doWrite) {
+        AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
+        if (accountInfo != null) {
+            final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
+            if (authorityInfo != null) {
+                mAuthorities.remove(authorityInfo.ident);
+                if (doWrite) {
+                    writeAccountInfoLocked();
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates (in a synchronized way) the periodic sync time of the specified
+     * authority id and target periodic sync
+     */
+    public void setPeriodicSyncTime(
+            int authorityId, PeriodicSync targetPeriodicSync, long when) {
+        boolean found = false;
+        final AuthorityInfo authorityInfo;
+        synchronized (mAuthorities) {
+            authorityInfo = mAuthorities.get(authorityId);
+            for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) {
+                PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i);
+                if (targetPeriodicSync.equals(periodicSync)) {
+                    mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when);
+                    found = true;
+                    break;
+                }
+            }
+        }
+        if (!found) {
+            Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
+                    "Authority: " + authorityInfo.authority);
+        }
+    }
+
+    private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
+        SyncStatusInfo status = mSyncStatus.get(authorityId);
+        if (status == null) {
+            status = new SyncStatusInfo(authorityId);
+            mSyncStatus.put(authorityId, status);
+        }
+        return status;
+    }
+
+    public void writeAllState() {
+        synchronized (mAuthorities) {
+            // Account info is always written so no need to do it here.
+
+            if (mNumPendingFinished > 0) {
+                // Only write these if they are out of date.
+                writePendingOperationsLocked();
+            }
+
+            // Just always write these...  they are likely out of date.
+            writeStatusLocked();
+            writeStatisticsLocked();
+        }
+    }
+
+    /**
+     * public for testing
+     */
+    public void clearAndReadState() {
+        synchronized (mAuthorities) {
+            mAuthorities.clear();
+            mAccounts.clear();
+            mServices.clear();
+            mPendingOperations.clear();
+            mSyncStatus.clear();
+            mSyncHistory.clear();
+
+            readAccountInfoLocked();
+            readStatusLocked();
+            readPendingOperationsLocked();
+            readStatisticsLocked();
+            readAndDeleteLegacyAccountInfoLocked();
+            writeAccountInfoLocked();
+            writeStatusLocked();
+            writePendingOperationsLocked();
+            writeStatisticsLocked();
+        }
+    }
+
+    /**
+     * Read all account information back in to the initial engine state.
+     */
+    private void readAccountInfoLocked() {
+        int highestAuthorityId = -1;
+        FileInputStream fis = null;
+        try {
+            fis = mAccountInfoFile.openRead();
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
+            }
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG) {
+                eventType = parser.next();
+            }
+            String tagName = parser.getName();
+            if ("accounts".equals(tagName)) {
+                String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
+                String versionString = parser.getAttributeValue(null, "version");
+                int version;
+                try {
+                    version = (versionString == null) ? 0 : Integer.parseInt(versionString);
+                } catch (NumberFormatException e) {
+                    version = 0;
+                }
+                String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
+                try {
+                    int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
+                    mNextAuthorityId = Math.max(mNextAuthorityId, id);
+                } catch (NumberFormatException e) {
+                    // don't care
+                }
+                String offsetString = parser.getAttributeValue(null, XML_ATTR_SYNC_RANDOM_OFFSET);
+                try {
+                    mSyncRandomOffset = (offsetString == null) ? 0 : Integer.parseInt(offsetString);
+                } catch (NumberFormatException e) {
+                    mSyncRandomOffset = 0;
+                }
+                if (mSyncRandomOffset == 0) {
+                    Random random = new Random(System.currentTimeMillis());
+                    mSyncRandomOffset = random.nextInt(86400);
+                }
+                mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
+                eventType = parser.next();
+                AuthorityInfo authority = null;
+                PeriodicSync periodicSync = null;
+                do {
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = parser.getName();
+                        if (parser.getDepth() == 2) {
+                            if ("authority".equals(tagName)) {
+                                authority = parseAuthority(parser, version);
+                                periodicSync = null;
+                                if (authority.ident > highestAuthorityId) {
+                                    highestAuthorityId = authority.ident;
+                                }
+                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
+                                parseListenForTickles(parser);
+                            }
+                        } else if (parser.getDepth() == 3) {
+                            if ("periodicSync".equals(tagName) && authority != null) {
+                                periodicSync = parsePeriodicSync(parser, authority);
+                            }
+                        } else if (parser.getDepth() == 4 && periodicSync != null) {
+                            if ("extra".equals(tagName)) {
+                                parseExtra(parser, periodicSync.extras);
+                            }
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (XmlPullParserException e) {
+            Log.w(TAG, "Error reading accounts", e);
+            return;
+        } catch (java.io.IOException e) {
+            if (fis == null) Log.i(TAG, "No initial accounts");
+            else Log.w(TAG, "Error reading accounts", e);
+            return;
+        } finally {
+            mNextAuthorityId = Math.max(highestAuthorityId + 1, mNextAuthorityId);
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {
+                }
+            }
+        }
+
+        maybeMigrateSettingsForRenamedAuthorities();
+    }
+
+    /**
+     * Ensure the old pending.bin is deleted, as it has been changed to pending.xml.
+     * pending.xml was used starting in KLP.
+     * @param syncDir directory where the sync files are located.
+     */
+    private void maybeDeleteLegacyPendingInfoLocked(File syncDir) {
+        File file = new File(syncDir, "pending.bin");
+        if (!file.exists()) {
+            return;
+        } else {
+            file.delete();
+        }
+    }
+
+    /**
+     * some authority names have changed. copy over their settings and delete the old ones
+     * @return true if a change was made
+     */
+    private boolean maybeMigrateSettingsForRenamedAuthorities() {
+        boolean writeNeeded = false;
+
+        ArrayList<AuthorityInfo> authoritiesToRemove = new ArrayList<AuthorityInfo>();
+        final int N = mAuthorities.size();
+        for (int i=0; i<N; i++) {
+            AuthorityInfo authority = mAuthorities.valueAt(i);
+            // skip this authority if it isn't one of the renamed ones
+            final String newAuthorityName = sAuthorityRenames.get(authority.authority);
+            if (newAuthorityName == null) {
+                continue;
+            }
+
+            // remember this authority so we can remove it later. we can't remove it
+            // now without messing up this loop iteration
+            authoritiesToRemove.add(authority);
+
+            // this authority isn't enabled, no need to copy it to the new authority name since
+            // the default is "disabled"
+            if (!authority.enabled) {
+                continue;
+            }
+
+            // if we already have a record of this new authority then don't copy over the settings
+            if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
+                    != null) {
+                continue;
+            }
+
+            AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
+                    authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
+            newAuthority.enabled = true;
+            writeNeeded = true;
+        }
+
+        for (AuthorityInfo authorityInfo : authoritiesToRemove) {
+            removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
+                    authorityInfo.authority, false /* doWrite */);
+            writeNeeded = true;
+        }
+
+        return writeNeeded;
+    }
+
+    private void parseListenForTickles(XmlPullParser parser) {
+        String user = parser.getAttributeValue(null, XML_ATTR_USER);
+        int userId = 0;
+        try {
+            userId = Integer.parseInt(user);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the user for listen-for-tickles", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the user in listen-for-tickles is null", e);
+        }
+        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
+        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
+        mMasterSyncAutomatically.put(userId, listen);
+    }
+
+    private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
+        AuthorityInfo authority = null;
+        int id = -1;
+        try {
+            id = Integer.parseInt(parser.getAttributeValue(null, "id"));
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the id of the authority", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the id of the authority is null", e);
+        }
+        if (id >= 0) {
+            String authorityName = parser.getAttributeValue(null, "authority");
+            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
+            String syncable = parser.getAttributeValue(null, "syncable");
+            String accountName = parser.getAttributeValue(null, "account");
+            String accountType = parser.getAttributeValue(null, "type");
+            String user = parser.getAttributeValue(null, XML_ATTR_USER);
+            String packageName = parser.getAttributeValue(null, "package");
+            String className = parser.getAttributeValue(null, "class");
+            int userId = user == null ? 0 : Integer.parseInt(user);
+            if (accountType == null) {
+                accountType = "com.google";
+                syncable = "unknown";
+            }
+            authority = mAuthorities.get(id);
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG, "Adding authority: account="
+                        + accountName + " auth=" + authorityName
+                        + " user=" + userId
+                        + " enabled=" + enabled
+                        + " syncable=" + syncable);
+            }
+            if (authority == null) {
+                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                    Log.v(TAG, "Creating entry");
+                }
+                if (accountName != null && accountType != null) {
+                    authority = getOrCreateAuthorityLocked(
+                            new Account(accountName, accountType), userId, authorityName, id,
+                                false);
+                } else {
+                    authority = getOrCreateAuthorityLocked(
+                            new ComponentName(packageName, className), userId, id, false);
+                }
+                // If the version is 0 then we are upgrading from a file format that did not
+                // know about periodic syncs. In that case don't clear the list since we
+                // want the default, which is a daily periodic sync.
+                // Otherwise clear out this default list since we will populate it later with
+                // the periodic sync descriptions that are read from the configuration file.
+                if (version > 0) {
+                    authority.periodicSyncs.clear();
+                }
+            }
+            if (authority != null) {
+                authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+                if ("unknown".equals(syncable)) {
+                    authority.syncable = -1;
+                } else {
+                    authority.syncable =
+                            (syncable == null || Boolean.parseBoolean(syncable)) ? 1 : 0;
+                }
+            } else {
+                Log.w(TAG, "Failure adding authority: account="
+                        + accountName + " auth=" + authorityName
+                        + " enabled=" + enabled
+                        + " syncable=" + syncable);
+            }
+        }
+        return authority;
+    }
+
+    /**
+     * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
+     */
+    private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+        Bundle extras = new Bundle(); // Gets filled in later.
+        String periodValue = parser.getAttributeValue(null, "period");
+        String flexValue = parser.getAttributeValue(null, "flex");
+        final long period;
+        long flextime;
+        try {
+            period = Long.parseLong(periodValue);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the period of a periodic sync", e);
+            return null;
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the period of a periodic sync is null", e);
+            return null;
+        }
+        try {
+            flextime = Long.parseLong(flexValue);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue);
+            flextime = calculateDefaultFlexTime(period);
+        } catch (NullPointerException expected) {
+            flextime = calculateDefaultFlexTime(period);
+            Log.d(TAG, "No flex time specified for this sync, using a default. period: "
+            + period + " flex: " + flextime);
+        }
+        final PeriodicSync periodicSync =
+                new PeriodicSync(authority.account, authority.authority, extras,
+                        period, flextime);
+        authority.periodicSyncs.add(periodicSync);
+        return periodicSync;
+    }
+
+    private void parseExtra(XmlPullParser parser, Bundle extras) {
+        String name = parser.getAttributeValue(null, "name");
+        String type = parser.getAttributeValue(null, "type");
+        String value1 = parser.getAttributeValue(null, "value1");
+        String value2 = parser.getAttributeValue(null, "value2");
+
+        try {
+            if ("long".equals(type)) {
+                extras.putLong(name, Long.parseLong(value1));
+            } else if ("integer".equals(type)) {
+                extras.putInt(name, Integer.parseInt(value1));
+            } else if ("double".equals(type)) {
+                extras.putDouble(name, Double.parseDouble(value1));
+            } else if ("float".equals(type)) {
+                extras.putFloat(name, Float.parseFloat(value1));
+            } else if ("boolean".equals(type)) {
+                extras.putBoolean(name, Boolean.parseBoolean(value1));
+            } else if ("string".equals(type)) {
+                extras.putString(name, value1);
+            } else if ("account".equals(type)) {
+                extras.putParcelable(name, new Account(value1, value2));
+            }
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing bundle value", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "error parsing bundle value", e);
+        }
+    }
+
+    /**
+     * Write all account information to the account file.
+     */
+    private void writeAccountInfoLocked() {
+        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+            Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
+        }
+        FileOutputStream fos = null;
+
+        try {
+            fos = mAccountInfoFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            out.startTag(null, "accounts");
+            out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
+            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+            out.attribute(null, XML_ATTR_SYNC_RANDOM_OFFSET, Integer.toString(mSyncRandomOffset));
+
+            // Write the Sync Automatically flags for each user
+            final int M = mMasterSyncAutomatically.size();
+            for (int m = 0; m < M; m++) {
+                int userId = mMasterSyncAutomatically.keyAt(m);
+                Boolean listen = mMasterSyncAutomatically.valueAt(m);
+                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
+                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
+                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
+                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
+            }
+
+            final int N = mAuthorities.size();
+            for (int i = 0; i < N; i++) {
+                AuthorityInfo authority = mAuthorities.valueAt(i);
+                out.startTag(null, "authority");
+                out.attribute(null, "id", Integer.toString(authority.ident));
+                out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
+                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
+                if (authority.service == null) {
+                    out.attribute(null, "account", authority.account.name);
+                    out.attribute(null, "type", authority.account.type);
+                    out.attribute(null, "authority", authority.authority);
+                } else {
+                    out.attribute(null, "package", authority.service.getPackageName());
+                    out.attribute(null, "class", authority.service.getClassName());
+                }
+                if (authority.syncable < 0) {
+                    out.attribute(null, "syncable", "unknown");
+                } else {
+                    out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
+                }
+                for (PeriodicSync periodicSync : authority.periodicSyncs) {
+                    out.startTag(null, "periodicSync");
+                    out.attribute(null, "period", Long.toString(periodicSync.period));
+                    out.attribute(null, "flex", Long.toString(periodicSync.flexTime));
+                    final Bundle extras = periodicSync.extras;
+                    extrasToXml(out, extras);
+                    out.endTag(null, "periodicSync");
+                }
+                out.endTag(null, "authority");
+            }
+            out.endTag(null, "accounts");
+            out.endDocument();
+            mAccountInfoFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing accounts", e1);
+            if (fos != null) {
+                mAccountInfoFile.failWrite(fos);
+            }
+        }
+    }
+
+    static int getIntColumn(Cursor c, String name) {
+        return c.getInt(c.getColumnIndex(name));
+    }
+
+    static long getLongColumn(Cursor c, String name) {
+        return c.getLong(c.getColumnIndex(name));
+    }
+
+    /**
+     * Load sync engine state from the old syncmanager database, and then
+     * erase it.  Note that we don't deal with pending operations, active
+     * sync, or history.
+     */
+    private void readAndDeleteLegacyAccountInfoLocked() {
+        // Look for old database to initialize from.
+        File file = mContext.getDatabasePath("syncmanager.db");
+        if (!file.exists()) {
+            return;
+        }
+        String path = file.getPath();
+        SQLiteDatabase db = null;
+        try {
+            db = SQLiteDatabase.openDatabase(path, null,
+                    SQLiteDatabase.OPEN_READONLY);
+        } catch (SQLiteException e) {
+        }
+
+        if (db != null) {
+            final boolean hasType = db.getVersion() >= 11;
+
+            // Copy in all of the status information, as well as accounts.
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG, "Reading legacy sync accounts db");
+            }
+            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("stats, status");
+            HashMap<String,String> map = new HashMap<String,String>();
+            map.put("_id", "status._id as _id");
+            map.put("account", "stats.account as account");
+            if (hasType) {
+                map.put("account_type", "stats.account_type as account_type");
+            }
+            map.put("authority", "stats.authority as authority");
+            map.put("totalElapsedTime", "totalElapsedTime");
+            map.put("numSyncs", "numSyncs");
+            map.put("numSourceLocal", "numSourceLocal");
+            map.put("numSourcePoll", "numSourcePoll");
+            map.put("numSourceServer", "numSourceServer");
+            map.put("numSourceUser", "numSourceUser");
+            map.put("lastSuccessSource", "lastSuccessSource");
+            map.put("lastSuccessTime", "lastSuccessTime");
+            map.put("lastFailureSource", "lastFailureSource");
+            map.put("lastFailureTime", "lastFailureTime");
+            map.put("lastFailureMesg", "lastFailureMesg");
+            map.put("pending", "pending");
+            qb.setProjectionMap(map);
+            qb.appendWhere("stats._id = status.stats_id");
+            Cursor c = qb.query(db, null, null, null, null, null, null);
+            while (c.moveToNext()) {
+                String accountName = c.getString(c.getColumnIndex("account"));
+                String accountType = hasType
+                        ? c.getString(c.getColumnIndex("account_type")) : null;
+                if (accountType == null) {
+                    accountType = "com.google";
+                }
+                String authorityName = c.getString(c.getColumnIndex("authority"));
+                AuthorityInfo authority = this.getOrCreateAuthorityLocked(
+                        new Account(accountName, accountType), 0 /* legacy is single-user */,
+                        authorityName, -1, false);
+                if (authority != null) {
+                    int i = mSyncStatus.size();
+                    boolean found = false;
+                    SyncStatusInfo st = null;
+                    while (i > 0) {
+                        i--;
+                        st = mSyncStatus.valueAt(i);
+                        if (st.authorityId == authority.ident) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if (!found) {
+                        st = new SyncStatusInfo(authority.ident);
+                        mSyncStatus.put(authority.ident, st);
+                    }
+                    st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
+                    st.numSyncs = getIntColumn(c, "numSyncs");
+                    st.numSourceLocal = getIntColumn(c, "numSourceLocal");
+                    st.numSourcePoll = getIntColumn(c, "numSourcePoll");
+                    st.numSourceServer = getIntColumn(c, "numSourceServer");
+                    st.numSourceUser = getIntColumn(c, "numSourceUser");
+                    st.numSourcePeriodic = 0;
+                    st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
+                    st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
+                    st.lastFailureSource = getIntColumn(c, "lastFailureSource");
+                    st.lastFailureTime = getLongColumn(c, "lastFailureTime");
+                    st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
+                    st.pending = getIntColumn(c, "pending") != 0;
+                }
+            }
+
+            c.close();
+
+            // Retrieve the settings.
+            qb = new SQLiteQueryBuilder();
+            qb.setTables("settings");
+            c = qb.query(db, null, null, null, null, null, null);
+            while (c.moveToNext()) {
+                String name = c.getString(c.getColumnIndex("name"));
+                String value = c.getString(c.getColumnIndex("value"));
+                if (name == null) continue;
+                if (name.equals("listen_for_tickles")) {
+                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
+                } else if (name.startsWith("sync_provider_")) {
+                    String provider = name.substring("sync_provider_".length(),
+                            name.length());
+                    int i = mAuthorities.size();
+                    while (i > 0) {
+                        i--;
+                        AuthorityInfo authority = mAuthorities.valueAt(i);
+                        if (authority.authority.equals(provider)) {
+                            authority.enabled = value == null || Boolean.parseBoolean(value);
+                            authority.syncable = 1;
+                        }
+                    }
+                }
+            }
+
+            c.close();
+
+            db.close();
+
+            (new File(path)).delete();
+        }
+    }
+
+    public static final int STATUS_FILE_END = 0;
+    public static final int STATUS_FILE_ITEM = 100;
+
+    /**
+     * Read all sync status back in to the initial engine state.
+     */
+    private void readStatusLocked() {
+        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+            Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
+        }
+        try {
+            byte[] data = mStatusFile.readFully();
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            int token;
+            while ((token=in.readInt()) != STATUS_FILE_END) {
+                if (token == STATUS_FILE_ITEM) {
+                    SyncStatusInfo status = new SyncStatusInfo(in);
+                    if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
+                        status.pending = false;
+                        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                            Log.v(TAG, "Adding status for id "
+                                    + status.authorityId);
+                        }
+                        mSyncStatus.put(status.authorityId, status);
+                    }
+                } else {
+                    // Ooops.
+                    Log.w(TAG, "Unknown status token: " + token);
+                    break;
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.i(TAG, "No initial status");
+        }
+    }
+
+    /**
+     * Write all sync status to the sync status file.
+     */
+    private void writeStatusLocked() {
+        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+            Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
+        }
+
+        // The file is being written, so we don't need to have a scheduled
+        // write until the next change.
+        removeMessages(MSG_WRITE_STATUS);
+
+        FileOutputStream fos = null;
+        try {
+            fos = mStatusFile.startWrite();
+            Parcel out = Parcel.obtain();
+            final int N = mSyncStatus.size();
+            for (int i=0; i<N; i++) {
+                SyncStatusInfo status = mSyncStatus.valueAt(i);
+                out.writeInt(STATUS_FILE_ITEM);
+                status.writeToParcel(out, 0);
+            }
+            out.writeInt(STATUS_FILE_END);
+            fos.write(out.marshall());
+            out.recycle();
+
+            mStatusFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing status", e1);
+            if (fos != null) {
+                mStatusFile.failWrite(fos);
+            }
+        }
+    }
+
+    public static final int PENDING_OPERATION_VERSION = 3;
+
+    /** Read all pending operations back in to the initial engine state. */
+    private void readPendingOperationsLocked() {
+        FileInputStream fis = null;
+        if (!mPendingFile.getBaseFile().exists()) {
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG_FILE, "No pending operation file.");
+                return;
+            }
+        }
+        try {
+            fis = mPendingFile.openRead();
+            XmlPullParser parser;
+            parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG &&
+                    eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+            }
+            if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read.
+
+            String tagName = parser.getName();
+            do {
+                PendingOperation pop = null;
+                if (eventType == XmlPullParser.START_TAG) {
+                    try {
+                        tagName = parser.getName();
+                        if (parser.getDepth() == 1 && "op".equals(tagName)) {
+                            // Verify version.
+                            String versionString =
+                                    parser.getAttributeValue(null, XML_ATTR_VERSION);
+                            if (versionString == null ||
+                                    Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
+                                Log.w(TAG, "Unknown pending operation version " + versionString);
+                                throw new java.io.IOException("Unknown version.");
+                            }
+                            int authorityId = Integer.valueOf(parser.getAttributeValue(
+                                    null, XML_ATTR_AUTHORITYID));
+                            boolean expedited = Boolean.valueOf(parser.getAttributeValue(
+                                    null, XML_ATTR_EXPEDITED));
+                            int syncSource = Integer.valueOf(parser.getAttributeValue(
+                                    null, XML_ATTR_SOURCE));
+                            int reason = Integer.valueOf(parser.getAttributeValue(
+                                    null, XML_ATTR_REASON));
+                            AuthorityInfo authority = mAuthorities.get(authorityId);
+                            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                                Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " "
+                                        + reason);
+                            }
+                            if (authority != null) {
+                                pop = new PendingOperation(
+                                        authority.account, authority.userId, reason,
+                                        syncSource, authority.authority, new Bundle(),
+                                        expedited);
+                                pop.flatExtras = null; // No longer used.
+                                mPendingOperations.add(pop);
+                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                                    Log.v(TAG_FILE, "Adding pending op: "
+                                            + pop.authority
+                                            + " src=" + pop.syncSource
+                                            + " reason=" + pop.reason
+                                            + " expedited=" + pop.expedited);
+                                }
+                            } else {
+                                // Skip non-existent authority.
+                                pop = null;
+                                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                                    Log.v(TAG_FILE, "No authority found for " + authorityId
+                                            + ", skipping");
+                                }
+                            }
+                        } else if (parser.getDepth() == 2 &&
+                                pop != null &&
+                                "extra".equals(tagName)) {
+                            parseExtra(parser, pop.extras);
+                        }
+                    } catch (NumberFormatException e) {
+                        Log.d(TAG, "Invalid data in xml file.", e);
+                    }
+                }
+                eventType = parser.next();
+            } while(eventType != XmlPullParser.END_DOCUMENT);
+        } catch (java.io.IOException e) {
+            Log.w(TAG_FILE, "Error reading pending data.", e);
+        } catch (XmlPullParserException e) {
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.w(TAG_FILE, "Error parsing pending ops xml.", e);
+            }
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {}
+            }
+        }
+    }
+
+    private static final String XML_ATTR_AUTHORITYID = "authority_id";
+    private static final String XML_ATTR_SOURCE = "source";
+    private static final String XML_ATTR_EXPEDITED = "expedited";
+    private static final String XML_ATTR_REASON = "reason";
+    private static final String XML_ATTR_VERSION = "version";
+
+    /**
+     * Write all currently pending ops to the pending ops file.
+     */
+    private void writePendingOperationsLocked() {
+        final int N = mPendingOperations.size();
+        FileOutputStream fos = null;
+        try {
+            if (N == 0) {
+                if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                    Log.v(TAG_FILE, "Truncating " + mPendingFile.getBaseFile());
+                }
+                mPendingFile.truncate();
+                return;
+            }
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG_FILE, "Writing new " + mPendingFile.getBaseFile());
+            }
+            fos = mPendingFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+
+            for (int i = 0; i < N; i++) {
+                PendingOperation pop = mPendingOperations.get(i);
+                writePendingOperationLocked(pop, out);
+            }
+            out.endDocument();
+            mPendingFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing pending operations", e1);
+            if (fos != null) {
+                mPendingFile.failWrite(fos);
+            }
+        }
+    }
+
+    /** Write all currently pending ops to the pending ops file. */
+     private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out)
+             throws IOException {
+         // Pending operation.
+         out.startTag(null, "op");
+
+         out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION));
+         out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
+         out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
+         out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
+         out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
+         extrasToXml(out, pop.extras);
+
+         out.endTag(null, "op");
+     }
+
+    /**
+     * Append the given operation to the pending ops file; if unable to,
+     * write all pending ops.
+     */
+    private void appendPendingOperationLocked(PendingOperation op) {
+        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+            Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
+        }
+        FileOutputStream fos = null;
+        try {
+            fos = mPendingFile.openAppend();
+        } catch (java.io.IOException e) {
+            if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+                Log.v(TAG, "Failed append; writing full file");
+            }
+            writePendingOperationsLocked();
+            return;
+        }
+
+        try {
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            writePendingOperationLocked(op, out);
+            out.endDocument();
+            mPendingFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing appending operation", e1);
+            mPendingFile.failWrite(fos);
+        } finally {
+            try {
+                fos.close();
+            } catch (IOException e) {}
+        }
+    }
+
+    static private byte[] flattenBundle(Bundle bundle) {
+        byte[] flatData = null;
+        Parcel parcel = Parcel.obtain();
+        try {
+            bundle.writeToParcel(parcel, 0);
+            flatData = parcel.marshall();
+        } finally {
+            parcel.recycle();
+        }
+        return flatData;
+    }
+
+    static private Bundle unflattenBundle(byte[] flatData) {
+        Bundle bundle;
+        Parcel parcel = Parcel.obtain();
+        try {
+            parcel.unmarshall(flatData, 0, flatData.length);
+            parcel.setDataPosition(0);
+            bundle = parcel.readBundle();
+        } catch (RuntimeException e) {
+            // A RuntimeException is thrown if we were unable to parse the parcel.
+            // Create an empty parcel in this case.
+            bundle = new Bundle();
+        } finally {
+            parcel.recycle();
+        }
+        return bundle;
+    }
+
+    private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
+        for (String key : extras.keySet()) {
+            out.startTag(null, "extra");
+            out.attribute(null, "name", key);
+            final Object value = extras.get(key);
+            if (value instanceof Long) {
+                out.attribute(null, "type", "long");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof Integer) {
+                out.attribute(null, "type", "integer");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof Boolean) {
+                out.attribute(null, "type", "boolean");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof Float) {
+                out.attribute(null, "type", "float");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof Double) {
+                out.attribute(null, "type", "double");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof String) {
+                out.attribute(null, "type", "string");
+                out.attribute(null, "value1", value.toString());
+            } else if (value instanceof Account) {
+                out.attribute(null, "type", "account");
+                out.attribute(null, "value1", ((Account)value).name);
+                out.attribute(null, "value2", ((Account)value).type);
+            }
+            out.endTag(null, "extra");
+        }
+    }
+
+    private void requestSync(Account account, int userId, int reason, String authority,
+            Bundle extras) {
+        // If this is happening in the system process, then call the syncrequest listener
+        // to make a request back to the SyncManager directly.
+        // If this is probably a test instance, then call back through the ContentResolver
+        // which will know which userId to apply based on the Binder id.
+        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
+                && mSyncRequestListener != null) {
+            mSyncRequestListener.onSyncRequest(account, userId, reason, authority, extras);
+        } else {
+            ContentResolver.requestSync(account, authority, extras);
+        }
+    }
+
+    public static final int STATISTICS_FILE_END = 0;
+    public static final int STATISTICS_FILE_ITEM_OLD = 100;
+    public static final int STATISTICS_FILE_ITEM = 101;
+
+    /**
+     * Read all sync statistics back in to the initial engine state.
+     */
+    private void readStatisticsLocked() {
+        try {
+            byte[] data = mStatisticsFile.readFully();
+            Parcel in = Parcel.obtain();
+            in.unmarshall(data, 0, data.length);
+            in.setDataPosition(0);
+            int token;
+            int index = 0;
+            while ((token=in.readInt()) != STATISTICS_FILE_END) {
+                if (token == STATISTICS_FILE_ITEM
+                        || token == STATISTICS_FILE_ITEM_OLD) {
+                    int day = in.readInt();
+                    if (token == STATISTICS_FILE_ITEM_OLD) {
+                        day = day - 2009 + 14245;  // Magic!
+                    }
+                    DayStats ds = new DayStats(day);
+                    ds.successCount = in.readInt();
+                    ds.successTime = in.readLong();
+                    ds.failureCount = in.readInt();
+                    ds.failureTime = in.readLong();
+                    if (index < mDayStats.length) {
+                        mDayStats[index] = ds;
+                        index++;
+                    }
+                } else {
+                    // Ooops.
+                    Log.w(TAG, "Unknown stats token: " + token);
+                    break;
+                }
+            }
+        } catch (java.io.IOException e) {
+            Log.i(TAG, "No initial statistics");
+        }
+    }
+
+    /**
+     * Write all sync statistics to the sync status file.
+     */
+    private void writeStatisticsLocked() {
+        if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) {
+            Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
+        }
+
+        // The file is being written, so we don't need to have a scheduled
+        // write until the next change.
+        removeMessages(MSG_WRITE_STATISTICS);
+
+        FileOutputStream fos = null;
+        try {
+            fos = mStatisticsFile.startWrite();
+            Parcel out = Parcel.obtain();
+            final int N = mDayStats.length;
+            for (int i=0; i<N; i++) {
+                DayStats ds = mDayStats[i];
+                if (ds == null) {
+                    break;
+                }
+                out.writeInt(STATISTICS_FILE_ITEM);
+                out.writeInt(ds.day);
+                out.writeInt(ds.successCount);
+                out.writeLong(ds.successTime);
+                out.writeInt(ds.failureCount);
+                out.writeLong(ds.failureTime);
+            }
+            out.writeInt(STATISTICS_FILE_END);
+            fos.write(out.marshall());
+            out.recycle();
+
+            mStatisticsFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing stats", e1);
+            if (fos != null) {
+                mStatisticsFile.failWrite(fos);
+            }
+        }
+    }
+
+    /**
+     * Dump state of PendingOperations.
+     */
+    public void dumpPendingOperations(StringBuilder sb) {
+        sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
+        for (PendingOperation pop : mPendingOperations) {
+            sb.append("(" + pop.account)
+                .append(", u" + pop.userId)
+                .append(", " + pop.authority)
+                .append(", " + pop.extras)
+                .append(")\n");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
new file mode 100644
index 0000000..b411a0d
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.os.Handler;
+
+import java.io.PrintWriter;
+
+/**
+ * A display adapter makes zero or more display devices available to the system
+ * and provides facilities for discovering when displays are connected or disconnected.
+ * <p>
+ * For now, all display adapters are registered in the system server but
+ * in principle it could be done from other processes.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+abstract class DisplayAdapter {
+    private final DisplayManagerService.SyncRoot mSyncRoot;
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Listener mListener;
+    private final String mName;
+
+    public static final int DISPLAY_DEVICE_EVENT_ADDED = 1;
+    public static final int DISPLAY_DEVICE_EVENT_CHANGED = 2;
+    public static final int DISPLAY_DEVICE_EVENT_REMOVED = 3;
+
+    // Called with SyncRoot lock held.
+    public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener, String name) {
+        mSyncRoot = syncRoot;
+        mContext = context;
+        mHandler = handler;
+        mListener = listener;
+        mName = name;
+    }
+
+    /**
+     * Gets the object that the display adapter should synchronize on when handling
+     * calls that come in from outside of the display manager service.
+     */
+    public final DisplayManagerService.SyncRoot getSyncRoot() {
+        return mSyncRoot;
+    }
+
+    /**
+     * Gets the display adapter's context.
+     */
+    public final Context getContext() {
+        return mContext;
+    }
+
+    /**
+     * Gets a handler that the display adapter may use to post asynchronous messages.
+     */
+    public final Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Gets the display adapter name for debugging purposes.
+     */
+    public final String getName() {
+        return mName;
+    }
+
+    /**
+     * Registers the display adapter with the display manager.
+     *
+     * The display adapter should register any built-in display devices as soon as possible.
+     * The boot process will wait for the default display to be registered.
+     * Other display devices can be registered dynamically later.
+     */
+    public void registerLocked() {
+    }
+
+    /**
+     * Dumps the local state of the display adapter.
+     */
+    public void dumpLocked(PrintWriter pw) {
+    }
+
+    /**
+     * Sends a display device event to the display adapter listener asynchronously.
+     */
+    protected final void sendDisplayDeviceEventLocked(
+            final DisplayDevice device, final int event) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayDeviceEvent(device, event);
+            }
+        });
+    }
+
+    /**
+     * Sends a request to perform traversals.
+     */
+    protected final void sendTraversalRequestLocked() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onTraversalRequested();
+            }
+        });
+    }
+
+    public interface Listener {
+        public void onDisplayDeviceEvent(DisplayDevice device, int event);
+        public void onTraversalRequested();
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDevice.java b/services/core/java/com/android/server/display/DisplayDevice.java
new file mode 100644
index 0000000..4161147
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayDevice.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+
+/**
+ * Represents a physical display device such as the built-in display
+ * an external monitor, or a WiFi display.
+ * <p>
+ * Display devices are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+abstract class DisplayDevice {
+    private final DisplayAdapter mDisplayAdapter;
+    private final IBinder mDisplayToken;
+
+    // The display device does not manage these properties itself, they are set by
+    // the display manager service.  The display device shouldn't really be looking at these.
+    private int mCurrentLayerStack = -1;
+    private int mCurrentOrientation = -1;
+    private Rect mCurrentLayerStackRect;
+    private Rect mCurrentDisplayRect;
+
+    // The display device owns its surface, but it should only set it
+    // within a transaction from performTraversalInTransactionLocked.
+    private Surface mCurrentSurface;
+
+    public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
+        mDisplayAdapter = displayAdapter;
+        mDisplayToken = displayToken;
+    }
+
+    /**
+     * Gets the display adapter that owns the display device.
+     *
+     * @return The display adapter.
+     */
+    public final DisplayAdapter getAdapterLocked() {
+        return mDisplayAdapter;
+    }
+
+    /**
+     * Gets the Surface Flinger display token for this display.
+     *
+     * @return The display token, or null if the display is not being managed
+     * by Surface Flinger.
+     */
+    public final IBinder getDisplayTokenLocked() {
+        return mDisplayToken;
+    }
+
+    /**
+     * Gets the name of the display device.
+     *
+     * @return The display device name.
+     */
+    public final String getNameLocked() {
+        return getDisplayDeviceInfoLocked().name;
+    }
+
+    /**
+     * Gets information about the display device.
+     *
+     * The information returned should not change between calls unless the display
+     * adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event and
+     * {@link #applyPendingDisplayDeviceInfoChangesLocked()} has been called to apply
+     * the pending changes.
+     *
+     * @return The display device info, which should be treated as immutable by the caller.
+     * The display device should allocate a new display device info object whenever
+     * the data changes.
+     */
+    public abstract DisplayDeviceInfo getDisplayDeviceInfoLocked();
+
+    /**
+     * Applies any pending changes to the observable state of the display device
+     * if the display adapter sent a {@link DisplayAdapter#DISPLAY_DEVICE_EVENT_CHANGED} event.
+     */
+    public void applyPendingDisplayDeviceInfoChangesLocked() {
+    }
+
+    /**
+     * Gives the display device a chance to update its properties while in a transaction.
+     */
+    public void performTraversalInTransactionLocked() {
+    }
+
+    /**
+     * Blanks the display, if supported.
+     */
+    public void blankLocked() {
+    }
+
+    /**
+     * Unblanks the display, if supported.
+     */
+    public void unblankLocked() {
+    }
+
+    /**
+     * Sets the display layer stack while in a transaction.
+     */
+    public final void setLayerStackInTransactionLocked(int layerStack) {
+        if (mCurrentLayerStack != layerStack) {
+            mCurrentLayerStack = layerStack;
+            SurfaceControl.setDisplayLayerStack(mDisplayToken, layerStack);
+        }
+    }
+
+    /**
+     * Sets the display projection while in a transaction.
+     *
+     * @param orientation defines the display's orientation
+     * @param layerStackRect defines which area of the window manager coordinate
+     *            space will be used
+     * @param displayRect defines where on the display will layerStackRect be
+     *            mapped to. displayRect is specified post-orientation, that is
+     *            it uses the orientation seen by the end-user
+     */
+    public final void setProjectionInTransactionLocked(int orientation,
+            Rect layerStackRect, Rect displayRect) {
+        if (mCurrentOrientation != orientation
+                || mCurrentLayerStackRect == null
+                || !mCurrentLayerStackRect.equals(layerStackRect)
+                || mCurrentDisplayRect == null
+                || !mCurrentDisplayRect.equals(displayRect)) {
+            mCurrentOrientation = orientation;
+
+            if (mCurrentLayerStackRect == null) {
+                mCurrentLayerStackRect = new Rect();
+            }
+            mCurrentLayerStackRect.set(layerStackRect);
+
+            if (mCurrentDisplayRect == null) {
+                mCurrentDisplayRect = new Rect();
+            }
+            mCurrentDisplayRect.set(displayRect);
+
+            SurfaceControl.setDisplayProjection(mDisplayToken,
+                    orientation, layerStackRect, displayRect);
+        }
+    }
+
+    /**
+     * Sets the display surface while in a transaction.
+     */
+    public final void setSurfaceInTransactionLocked(Surface surface) {
+        if (mCurrentSurface != surface) {
+            mCurrentSurface = surface;
+            SurfaceControl.setDisplaySurface(mDisplayToken, surface);
+        }
+    }
+
+    /**
+     * Populates the specified viewport object with orientation,
+     * physical and logical rects based on the display's current projection.
+     */
+    public final void populateViewportLocked(DisplayViewport viewport) {
+        viewport.orientation = mCurrentOrientation;
+
+        if (mCurrentLayerStackRect != null) {
+            viewport.logicalFrame.set(mCurrentLayerStackRect);
+        } else {
+            viewport.logicalFrame.setEmpty();
+        }
+
+        if (mCurrentDisplayRect != null) {
+            viewport.physicalFrame.set(mCurrentDisplayRect);
+        } else {
+            viewport.physicalFrame.setEmpty();
+        }
+
+        boolean isRotated = (mCurrentOrientation == Surface.ROTATION_90
+                || mCurrentOrientation == Surface.ROTATION_270);
+        DisplayDeviceInfo info = getDisplayDeviceInfoLocked();
+        viewport.deviceWidth = isRotated ? info.height : info.width;
+        viewport.deviceHeight = isRotated ? info.width : info.height;
+    }
+
+    /**
+     * Dumps the local state of the display device.
+     * Does not need to dump the display device info because that is already dumped elsewhere.
+     */
+    public void dumpLocked(PrintWriter pw) {
+        pw.println("mAdapter=" + mDisplayAdapter.getName());
+        pw.println("mDisplayToken=" + mDisplayToken);
+        pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
+        pw.println("mCurrentOrientation=" + mCurrentOrientation);
+        pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect);
+        pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect);
+        pw.println("mCurrentSurface=" + mCurrentSurface);
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
new file mode 100644
index 0000000..11c5d87
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Surface;
+
+import libcore.util.Objects;
+
+/**
+ * Describes the characteristics of a physical display device.
+ */
+final class DisplayDeviceInfo {
+    /**
+     * Flag: Indicates that this display device should be considered the default display
+     * device of the system.
+     */
+    public static final int FLAG_DEFAULT_DISPLAY = 1 << 0;
+
+    /**
+     * Flag: Indicates that the orientation of this display device is coupled to the
+     * rotation of its associated logical display.
+     * <p>
+     * This flag should be applied to the default display to indicate that the user
+     * physically rotates the display when content is presented in a different orientation.
+     * The display manager will apply a coordinate transformation assuming that the
+     * physical orientation of the display matches the logical orientation of its content.
+     * </p><p>
+     * The flag should not be set when the display device is mounted in a fixed orientation
+     * such as on a desk.  The display manager will apply a coordinate transformation
+     * such as a scale and translation to letterbox or pillarbox format under the
+     * assumption that the physical orientation of the display is invariant.
+     * </p>
+     */
+    public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 1;
+
+    /**
+     * Flag: Indicates that this display device has secure video output, such as HDCP.
+     */
+    public static final int FLAG_SECURE = 1 << 2;
+
+    /**
+     * Flag: Indicates that this display device supports compositing
+     * from gralloc protected buffers.
+     */
+    public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1 << 3;
+
+    /**
+     * Flag: Indicates that the display device is owned by a particular application
+     * and that no other application should be able to interact with it.
+     */
+    public static final int FLAG_PRIVATE = 1 << 4;
+
+    /**
+     * Flag: Indicates that the display device is not blanked automatically by
+     * the power manager.
+     */
+    public static final int FLAG_NEVER_BLANK = 1 << 5;
+
+    /**
+     * Flag: Indicates that the display is suitable for presentations.
+     */
+    public static final int FLAG_PRESENTATION = 1 << 6;
+
+    /**
+     * Touch attachment: Display does not receive touch.
+     */
+    public static final int TOUCH_NONE = 0;
+
+    /**
+     * Touch attachment: Touch input is via the internal interface.
+     */
+    public static final int TOUCH_INTERNAL = 1;
+
+    /**
+     * Touch attachment: Touch input is via an external interface, such as USB.
+     */
+    public static final int TOUCH_EXTERNAL = 2;
+
+    /**
+     * Gets the name of the display device, which may be derived from
+     * EDID or other sources.  The name may be displayed to the user.
+     */
+    public String name;
+
+    /**
+     * The width of the display in its natural orientation, in pixels.
+     * This value is not affected by display rotation.
+     */
+    public int width;
+
+    /**
+     * The height of the display in its natural orientation, in pixels.
+     * This value is not affected by display rotation.
+     */
+    public int height;
+
+    /**
+     * The refresh rate of the display.
+     */
+    public float refreshRate;
+
+    /**
+     * The nominal apparent density of the display in DPI used for layout calculations.
+     * This density is sensitive to the viewing distance.  A big TV and a tablet may have
+     * the same apparent density even though the pixels on the TV are much bigger than
+     * those on the tablet.
+     */
+    public int densityDpi;
+
+    /**
+     * The physical density of the display in DPI in the X direction.
+     * This density should specify the physical size of each pixel.
+     */
+    public float xDpi;
+
+    /**
+     * The physical density of the display in DPI in the X direction.
+     * This density should specify the physical size of each pixel.
+     */
+    public float yDpi;
+
+    /**
+     * Display flags.
+     */
+    public int flags;
+
+    /**
+     * The touch attachment, per {@link DisplayViewport#touch}.
+     */
+    public int touch;
+
+    /**
+     * The additional rotation to apply to all content presented on the display device
+     * relative to its physical coordinate system.  Default is {@link Surface#ROTATION_0}.
+     * <p>
+     * This field can be used to compensate for the fact that the display has been
+     * physically rotated relative to its natural orientation such as an HDMI monitor
+     * that has been mounted sideways to appear to be portrait rather than landscape.
+     * </p>
+     */
+    public int rotation = Surface.ROTATION_0;
+
+    /**
+     * Display type.
+     */
+    public int type;
+
+    /**
+     * Display address, or null if none.
+     * Interpretation varies by display type.
+     */
+    public String address;
+
+    /**
+     * The UID of the application that owns this display, or zero if it is owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     */
+    public int ownerUid;
+
+    /**
+     * The package name of the application that owns this display, or null if it is
+     * owned by the system.
+     * <p>
+     * If the display is private, then only the owner can use it.
+     * </p>
+     */
+    public String ownerPackageName;
+
+    public void setAssumedDensityForExternalDisplay(int width, int height) {
+        densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
+        // Technically, these values should be smaller than the apparent density
+        // but we don't know the physical size of the display.
+        xDpi = densityDpi;
+        yDpi = densityDpi;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof DisplayDeviceInfo && equals((DisplayDeviceInfo)o);
+    }
+
+    public boolean equals(DisplayDeviceInfo other) {
+        return other != null
+                && Objects.equal(name, other.name)
+                && width == other.width
+                && height == other.height
+                && refreshRate == other.refreshRate
+                && densityDpi == other.densityDpi
+                && xDpi == other.xDpi
+                && yDpi == other.yDpi
+                && flags == other.flags
+                && touch == other.touch
+                && rotation == other.rotation
+                && type == other.type
+                && Objects.equal(address, other.address)
+                && ownerUid == other.ownerUid
+                && Objects.equal(ownerPackageName, other.ownerPackageName);
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
+    public void copyFrom(DisplayDeviceInfo other) {
+        name = other.name;
+        width = other.width;
+        height = other.height;
+        refreshRate = other.refreshRate;
+        densityDpi = other.densityDpi;
+        xDpi = other.xDpi;
+        yDpi = other.yDpi;
+        flags = other.flags;
+        touch = other.touch;
+        rotation = other.rotation;
+        type = other.type;
+        address = other.address;
+        ownerUid = other.ownerUid;
+        ownerPackageName = other.ownerPackageName;
+    }
+
+    // For debugging purposes
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("DisplayDeviceInfo{\"");
+        sb.append(name).append("\": ").append(width).append(" x ").append(height);
+        sb.append(", ").append(refreshRate).append(" fps, ");
+        sb.append("density ").append(densityDpi);
+        sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi");
+        sb.append(", touch ").append(touchToString(touch));
+        sb.append(", rotation ").append(rotation);
+        sb.append(", type ").append(Display.typeToString(type));
+        if (address != null) {
+            sb.append(", address ").append(address);
+        }
+        if (ownerUid != 0 || ownerPackageName != null) {
+            sb.append(", owner ").append(ownerPackageName);
+            sb.append(" (uid ").append(ownerUid).append(")");
+        }
+        sb.append(flagsToString(flags));
+        sb.append("}");
+        return sb.toString();
+    }
+
+    private static String touchToString(int touch) {
+        switch (touch) {
+            case TOUCH_NONE:
+                return "NONE";
+            case TOUCH_INTERNAL:
+                return "INTERNAL";
+            case TOUCH_EXTERNAL:
+                return "EXTERNAL";
+            default:
+                return Integer.toString(touch);
+        }
+    }
+
+    private static String flagsToString(int flags) {
+        StringBuilder msg = new StringBuilder();
+        if ((flags & FLAG_DEFAULT_DISPLAY) != 0) {
+            msg.append(", FLAG_DEFAULT_DISPLAY");
+        }
+        if ((flags & FLAG_ROTATES_WITH_CONTENT) != 0) {
+            msg.append(", FLAG_ROTATES_WITH_CONTENT");
+        }
+        if ((flags & FLAG_SECURE) != 0) {
+            msg.append(", FLAG_SECURE");
+        }
+        if ((flags & FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
+            msg.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS");
+        }
+        if ((flags & FLAG_PRIVATE) != 0) {
+            msg.append(", FLAG_PRIVATE");
+        }
+        if ((flags & FLAG_NEVER_BLANK) != 0) {
+            msg.append(", FLAG_NEVER_BLANK");
+        }
+        if ((flags & FLAG_PRESENTATION) != 0) {
+            msg.append(", FLAG_PRESENTATION");
+        }
+        return msg.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
new file mode 100644
index 0000000..73040d5
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -0,0 +1,1312 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.display.IDisplayManager;
+import android.hardware.display.IDisplayManagerCallback;
+import android.hardware.display.WifiDisplayStatus;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import com.android.server.UiThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Manages attached displays.
+ * <p>
+ * The {@link DisplayManagerService} manages the global lifecycle of displays,
+ * decides how to configure logical displays based on the physical display devices currently
+ * attached, sends notifications to the system and to applications when the state
+ * changes, and so on.
+ * </p><p>
+ * The display manager service relies on a collection of {@link DisplayAdapter} components,
+ * for discovering and configuring physical display devices attached to the system.
+ * There are separate display adapters for each manner that devices are attached:
+ * one display adapter for built-in local displays, one for simulated non-functional
+ * displays when the system is headless, one for simulated overlay displays used for
+ * development, one for wifi displays, etc.
+ * </p><p>
+ * Display adapters are only weakly coupled to the display manager service.
+ * Display adapters communicate changes in display device state to the display manager
+ * service asynchronously via a {@link DisplayAdapter.Listener} registered
+ * by the display manager service.  This separation of concerns is important for
+ * two main reasons.  First, it neatly encapsulates the responsibilities of these
+ * two classes: display adapters handle individual display devices whereas
+ * the display manager service handles the global state.  Second, it eliminates
+ * the potential for deadlocks resulting from asynchronous display device discovery.
+ * </p>
+ *
+ * <h3>Synchronization</h3>
+ * <p>
+ * Because the display manager may be accessed by multiple threads, the synchronization
+ * story gets a little complicated.  In particular, the window manager may call into
+ * the display manager while holding a surface transaction with the expectation that
+ * it can apply changes immediately.  Unfortunately, that means we can't just do
+ * everything asynchronously (*grump*).
+ * </p><p>
+ * To make this work, all of the objects that belong to the display manager must
+ * use the same lock.  We call this lock the synchronization root and it has a unique
+ * type {@link DisplayManagerService.SyncRoot}.  Methods that require this lock are
+ * named with the "Locked" suffix.
+ * </p><p>
+ * Where things get tricky is that the display manager is not allowed to make
+ * any potentially reentrant calls, especially into the window manager.  We generally
+ * avoid this by making all potentially reentrant out-calls asynchronous.
+ * </p>
+ */
+public final class DisplayManagerService extends IDisplayManager.Stub {
+    private static final String TAG = "DisplayManagerService";
+    private static final boolean DEBUG = false;
+
+    // When this system property is set to 0, WFD is forcibly disabled on boot.
+    // When this system property is set to 1, WFD is forcibly enabled on boot.
+    // Otherwise WFD is enabled according to the value of config_enableWifiDisplay.
+    private static final String FORCE_WIFI_DISPLAY_ENABLE = "persist.debug.wfd.enable";
+
+    private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
+
+    private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER = 1;
+    private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
+    private static final int MSG_DELIVER_DISPLAY_EVENT = 3;
+    private static final int MSG_REQUEST_TRAVERSAL = 4;
+    private static final int MSG_UPDATE_VIEWPORT = 5;
+
+    private static final int DISPLAY_BLANK_STATE_UNKNOWN = 0;
+    private static final int DISPLAY_BLANK_STATE_BLANKED = 1;
+    private static final int DISPLAY_BLANK_STATE_UNBLANKED = 2;
+
+    private final Context mContext;
+    private final DisplayManagerHandler mHandler;
+    private final Handler mUiHandler;
+    private final DisplayAdapterListener mDisplayAdapterListener;
+    private WindowManagerFuncs mWindowManagerFuncs;
+    private InputManagerFuncs mInputManagerFuncs;
+
+    // The synchronization root for the display manager.
+    // This lock guards most of the display manager's state.
+    // NOTE: This is synchronized on while holding WindowManagerService.mWindowMap so never call
+    // into WindowManagerService methods that require mWindowMap while holding this unless you are
+    // very very sure that no deadlock can occur.
+    private final SyncRoot mSyncRoot = new SyncRoot();
+
+    // True if in safe mode.
+    // This option may disable certain display adapters.
+    public boolean mSafeMode;
+
+    // True if we are in a special boot mode where only core applications and
+    // services should be started.  This option may disable certain display adapters.
+    public boolean mOnlyCore;
+
+    // True if the display manager service should pretend there is only one display
+    // and only tell applications about the existence of the default logical display.
+    // The display manager can still mirror content to secondary displays but applications
+    // cannot present unique content on those displays.
+    // Used for demonstration purposes only.
+    private final boolean mSingleDisplayDemoMode;
+
+    // All callback records indexed by calling process id.
+    public final SparseArray<CallbackRecord> mCallbacks =
+            new SparseArray<CallbackRecord>();
+
+    // List of all currently registered display adapters.
+    private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();
+
+    // List of all currently connected display devices.
+    private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>();
+
+    // List of all logical displays indexed by logical display id.
+    private final SparseArray<LogicalDisplay> mLogicalDisplays =
+            new SparseArray<LogicalDisplay>();
+    private int mNextNonDefaultDisplayId = Display.DEFAULT_DISPLAY + 1;
+
+    // List of all display transaction listeners.
+    private final CopyOnWriteArrayList<DisplayTransactionListener> mDisplayTransactionListeners =
+            new CopyOnWriteArrayList<DisplayTransactionListener>();
+
+    // Set to true if all displays have been blanked by the power manager.
+    private int mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_UNKNOWN;
+
+    // Set to true when there are pending display changes that have yet to be applied
+    // to the surface flinger state.
+    private boolean mPendingTraversal;
+
+    // The Wifi display adapter, or null if not registered.
+    private WifiDisplayAdapter mWifiDisplayAdapter;
+
+    // The number of active wifi display scan requests.
+    private int mWifiDisplayScanRequestCount;
+
+    // The virtual display adapter, or null if not registered.
+    private VirtualDisplayAdapter mVirtualDisplayAdapter;
+
+    // Viewports of the default display and the display that should receive touch
+    // input from an external source.  Used by the input system.
+    private final DisplayViewport mDefaultViewport = new DisplayViewport();
+    private final DisplayViewport mExternalTouchViewport = new DisplayViewport();
+
+    // Persistent data store for all internal settings maintained by the display manager service.
+    private final PersistentDataStore mPersistentDataStore = new PersistentDataStore();
+
+    // Temporary callback list, used when sending display events to applications.
+    // May be used outside of the lock but only on the handler thread.
+    private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
+
+    // Temporary display info, used for comparing display configurations.
+    private final DisplayInfo mTempDisplayInfo = new DisplayInfo();
+
+    // Temporary viewports, used when sending new viewport information to the
+    // input system.  May be used outside of the lock but only on the handler thread.
+    private final DisplayViewport mTempDefaultViewport = new DisplayViewport();
+    private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport();
+
+    public DisplayManagerService(Context context, Handler mainHandler) {
+        mContext = context;
+        mHandler = new DisplayManagerHandler(mainHandler.getLooper());
+        mUiHandler = UiThread.getHandler();
+        mDisplayAdapterListener = new DisplayAdapterListener();
+        mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);
+
+        mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER);
+    }
+
+    /**
+     * Pauses the boot process to wait for the first display to be initialized.
+     */
+    public boolean waitForDefaultDisplay() {
+        synchronized (mSyncRoot) {
+            long timeout = SystemClock.uptimeMillis() + WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT;
+            while (mLogicalDisplays.get(Display.DEFAULT_DISPLAY) == null) {
+                long delay = timeout - SystemClock.uptimeMillis();
+                if (delay <= 0) {
+                    return false;
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "waitForDefaultDisplay: waiting, timeout=" + delay);
+                }
+                try {
+                    mSyncRoot.wait(delay);
+                } catch (InterruptedException ex) {
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Called during initialization to associate the display manager with the
+     * window manager.
+     */
+    public void setWindowManager(WindowManagerFuncs windowManagerFuncs) {
+        synchronized (mSyncRoot) {
+            mWindowManagerFuncs = windowManagerFuncs;
+            scheduleTraversalLocked(false);
+        }
+    }
+
+    /**
+     * Called during initialization to associate the display manager with the
+     * input manager.
+     */
+    public void setInputManager(InputManagerFuncs inputManagerFuncs) {
+        synchronized (mSyncRoot) {
+            mInputManagerFuncs = inputManagerFuncs;
+            scheduleTraversalLocked(false);
+        }
+    }
+
+    /**
+     * Called when the system is ready to go.
+     */
+    public void systemReady(boolean safeMode, boolean onlyCore) {
+        synchronized (mSyncRoot) {
+            mSafeMode = safeMode;
+            mOnlyCore = onlyCore;
+        }
+
+        mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);
+    }
+
+    /**
+     * Registers a display transaction listener to provide the client a chance to
+     * update its surfaces within the same transaction as any display layout updates.
+     *
+     * @param listener The listener to register.
+     */
+    public void registerDisplayTransactionListener(DisplayTransactionListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        // List is self-synchronized copy-on-write.
+        mDisplayTransactionListeners.add(listener);
+    }
+
+    /**
+     * Unregisters a display transaction listener to provide the client a chance to
+     * update its surfaces within the same transaction as any display layout updates.
+     *
+     * @param listener The listener to unregister.
+     */
+    public void unregisterDisplayTransactionListener(DisplayTransactionListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        // List is self-synchronized copy-on-write.
+        mDisplayTransactionListeners.remove(listener);
+    }
+
+    /**
+     * Overrides the display information of a particular logical display.
+     * This is used by the window manager to control the size and characteristics
+     * of the default display.  It is expected to apply the requested change
+     * to the display information synchronously so that applications will immediately
+     * observe the new state.
+     *
+     * NOTE: This method must be the only entry point by which the window manager
+     * influences the logical configuration of displays.
+     *
+     * @param displayId The logical display id.
+     * @param info The new data to be stored.
+     */
+    public void setDisplayInfoOverrideFromWindowManager(
+            int displayId, DisplayInfo info) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null) {
+                if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
+                    sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                    scheduleTraversalLocked(false);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by the window manager to perform traversals while holding a
+     * surface flinger transaction.
+     */
+    public void performTraversalInTransactionFromWindowManager() {
+        synchronized (mSyncRoot) {
+            if (!mPendingTraversal) {
+                return;
+            }
+            mPendingTraversal = false;
+
+            performTraversalInTransactionLocked();
+        }
+
+        // List is self-synchronized copy-on-write.
+        for (DisplayTransactionListener listener : mDisplayTransactionListeners) {
+            listener.onDisplayTransaction();
+        }
+    }
+
+    /**
+     * Called by the power manager to blank all displays.
+     */
+    public void blankAllDisplaysFromPowerManager() {
+        synchronized (mSyncRoot) {
+            if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_BLANKED) {
+                mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_BLANKED;
+                updateAllDisplayBlankingLocked();
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
+    /**
+     * Called by the power manager to unblank all displays.
+     */
+    public void unblankAllDisplaysFromPowerManager() {
+        synchronized (mSyncRoot) {
+            if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_UNBLANKED) {
+                mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_UNBLANKED;
+                updateAllDisplayBlankingLocked();
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
+    /**
+     * Returns information about the specified logical display.
+     *
+     * @param displayId The logical display id.
+     * @return The logical display info, or null if the display does not exist.  The
+     * returned object must be treated as immutable.
+     */
+    @Override // Binder call
+    public DisplayInfo getDisplayInfo(int displayId) {
+        final int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                LogicalDisplay display = mLogicalDisplays.get(displayId);
+                if (display != null) {
+                    DisplayInfo info = display.getDisplayInfoLocked();
+                    if (info.hasAccess(callingUid)) {
+                        return info;
+                    }
+                }
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Returns the list of all display ids.
+     */
+    @Override // Binder call
+    public int[] getDisplayIds() {
+        final int callingUid = Binder.getCallingUid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                final int count = mLogicalDisplays.size();
+                int[] displayIds = new int[count];
+                int n = 0;
+                for (int i = 0; i < count; i++) {
+                    LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                    DisplayInfo info = display.getDisplayInfoLocked();
+                    if (info.hasAccess(callingUid)) {
+                        displayIds[n++] = mLogicalDisplays.keyAt(i);
+                    }
+                }
+                if (n != count) {
+                    displayIds = Arrays.copyOfRange(displayIds, 0, n);
+                }
+                return displayIds;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void registerCallback(IDisplayManagerCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mSyncRoot) {
+            int callingPid = Binder.getCallingPid();
+            if (mCallbacks.get(callingPid) != null) {
+                throw new SecurityException("The calling process has already "
+                        + "registered an IDisplayManagerCallback.");
+            }
+
+            CallbackRecord record = new CallbackRecord(callingPid, callback);
+            try {
+                IBinder binder = callback.asBinder();
+                binder.linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                // give up
+                throw new RuntimeException(ex);
+            }
+
+            mCallbacks.put(callingPid, record);
+        }
+    }
+
+    private void onCallbackDied(CallbackRecord record) {
+        synchronized (mSyncRoot) {
+            mCallbacks.remove(record.mPid);
+            stopWifiDisplayScanLocked(record);
+        }
+    }
+
+    @Override // Binder call
+    public void startWifiDisplayScan() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to start wifi display scans");
+
+        final int callingPid = Binder.getCallingPid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                CallbackRecord record = mCallbacks.get(callingPid);
+                if (record == null) {
+                    throw new IllegalStateException("The calling process has not "
+                            + "registered an IDisplayManagerCallback.");
+                }
+                startWifiDisplayScanLocked(record);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void startWifiDisplayScanLocked(CallbackRecord record) {
+        if (!record.mWifiDisplayScanRequested) {
+            record.mWifiDisplayScanRequested = true;
+            if (mWifiDisplayScanRequestCount++ == 0) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestStartScanLocked();
+                }
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void stopWifiDisplayScan() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to stop wifi display scans");
+
+        final int callingPid = Binder.getCallingPid();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                CallbackRecord record = mCallbacks.get(callingPid);
+                if (record == null) {
+                    throw new IllegalStateException("The calling process has not "
+                            + "registered an IDisplayManagerCallback.");
+                }
+                stopWifiDisplayScanLocked(record);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void stopWifiDisplayScanLocked(CallbackRecord record) {
+        if (record.mWifiDisplayScanRequested) {
+            record.mWifiDisplayScanRequested = false;
+            if (--mWifiDisplayScanRequestCount == 0) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestStopScanLocked();
+                }
+            } else if (mWifiDisplayScanRequestCount < 0) {
+                Log.wtf(TAG, "mWifiDisplayScanRequestCount became negative: "
+                        + mWifiDisplayScanRequestCount);
+                mWifiDisplayScanRequestCount = 0;
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void connectWifiDisplay(String address) {
+        if (address == null) {
+            throw new IllegalArgumentException("address must not be null");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to connect to a wifi display");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestConnectLocked(address);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void pauseWifiDisplay() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to pause a wifi display session");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestPauseLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void resumeWifiDisplay() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to resume a wifi display session");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestResumeLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void disconnectWifiDisplay() {
+        // This request does not require special permissions.
+        // Any app can request disconnection from the currently active wifi display.
+        // This exception should no longer be needed once wifi display control moves
+        // to the media router service.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestDisconnectLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void renameWifiDisplay(String address, String alias) {
+        if (address == null) {
+            throw new IllegalArgumentException("address must not be null");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to rename to a wifi display");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestRenameLocked(address, alias);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public void forgetWifiDisplay(String address) {
+        if (address == null) {
+            throw new IllegalArgumentException("address must not be null");
+        }
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.CONFIGURE_WIFI_DISPLAY,
+                "Permission required to forget to a wifi display");
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    mWifiDisplayAdapter.requestForgetLocked(address);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public WifiDisplayStatus getWifiDisplayStatus() {
+        // This request does not require special permissions.
+        // Any app can get information about available wifi displays.
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mWifiDisplayAdapter != null) {
+                    return mWifiDisplayAdapter.getWifiDisplayStatusLocked();
+                }
+                return new WifiDisplayStatus();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override // Binder call
+    public int createVirtualDisplay(IBinder appToken, String packageName,
+            String name, int width, int height, int densityDpi, Surface surface, int flags) {
+        final int callingUid = Binder.getCallingUid();
+        if (!validatePackageName(callingUid, packageName)) {
+            throw new SecurityException("packageName must match the calling uid");
+        }
+        if (appToken == null) {
+            throw new IllegalArgumentException("appToken must not be null");
+        }
+        if (TextUtils.isEmpty(name)) {
+            throw new IllegalArgumentException("name must be non-null and non-empty");
+        }
+        if (width <= 0 || height <= 0 || densityDpi <= 0) {
+            throw new IllegalArgumentException("width, height, and densityDpi must be "
+                    + "greater than 0");
+        }
+        if (surface == null) {
+            throw new IllegalArgumentException("surface must not be null");
+        }
+        if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
+            if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+                    != PackageManager.PERMISSION_GRANTED
+                    && mContext.checkCallingPermission(
+                            android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
+                            != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+                        + "CAPTURE_SECURE_VIDEO_OUTPUT permission to create a "
+                        + "public virtual display.");
+            }
+        }
+        if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
+            if (mContext.checkCallingPermission(
+                    android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+                        + "to create a secure virtual display.");
+            }
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mVirtualDisplayAdapter == null) {
+                    Slog.w(TAG, "Rejecting request to create private virtual display "
+                            + "because the virtual display adapter is not available.");
+                    return -1;
+                }
+
+                DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
+                        appToken, callingUid, packageName, name, width, height, densityDpi,
+                        surface, flags);
+                if (device == null) {
+                    return -1;
+                }
+
+                handleDisplayDeviceAddedLocked(device);
+                LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+                if (display != null) {
+                    return display.getDisplayIdLocked();
+                }
+
+                // Something weird happened and the logical display was not created.
+                Slog.w(TAG, "Rejecting request to create virtual display "
+                        + "because the logical display was not created.");
+                mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+                handleDisplayDeviceRemovedLocked(device);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return -1;
+    }
+
+    @Override // Binder call
+    public void releaseVirtualDisplay(IBinder appToken) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mSyncRoot) {
+                if (mVirtualDisplayAdapter == null) {
+                    return;
+                }
+
+                DisplayDevice device =
+                        mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+                if (device != null) {
+                    handleDisplayDeviceRemovedLocked(device);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private boolean validatePackageName(int uid, String packageName) {
+        if (packageName != null) {
+            String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            if (packageNames != null) {
+                for (String n : packageNames) {
+                    if (n.equals(packageName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    private void registerDefaultDisplayAdapter() {
+        // Register default display adapter.
+        synchronized (mSyncRoot) {
+            registerDisplayAdapterLocked(new LocalDisplayAdapter(
+                    mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
+        }
+    }
+
+    private void registerAdditionalDisplayAdapters() {
+        synchronized (mSyncRoot) {
+            if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
+                registerOverlayDisplayAdapterLocked();
+                registerWifiDisplayAdapterLocked();
+                registerVirtualDisplayAdapterLocked();
+            }
+        }
+    }
+
+    private void registerOverlayDisplayAdapterLocked() {
+        registerDisplayAdapterLocked(new OverlayDisplayAdapter(
+                mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+    }
+
+    private void registerWifiDisplayAdapterLocked() {
+        if (mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableWifiDisplay)
+                || SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {
+            mWifiDisplayAdapter = new WifiDisplayAdapter(
+                    mSyncRoot, mContext, mHandler, mDisplayAdapterListener,
+                    mPersistentDataStore);
+            registerDisplayAdapterLocked(mWifiDisplayAdapter);
+        }
+    }
+
+    private void registerVirtualDisplayAdapterLocked() {
+        mVirtualDisplayAdapter = new VirtualDisplayAdapter(
+                mSyncRoot, mContext, mHandler, mDisplayAdapterListener);
+        registerDisplayAdapterLocked(mVirtualDisplayAdapter);
+    }
+
+    private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() {
+        // In safe mode, we disable non-essential display adapters to give the user
+        // an opportunity to fix broken settings or other problems that might affect
+        // system stability.
+        // In only-core mode, we disable non-essential display adapters to minimize
+        // the number of dependencies that are started while in this mode and to
+        // prevent problems that might occur due to the device being encrypted.
+        return !mSafeMode && !mOnlyCore;
+    }
+
+    private void registerDisplayAdapterLocked(DisplayAdapter adapter) {
+        mDisplayAdapters.add(adapter);
+        adapter.registerLocked();
+    }
+
+    private void handleDisplayDeviceAdded(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            handleDisplayDeviceAddedLocked(device);
+        }
+    }
+
+    private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
+        if (mDisplayDevices.contains(device)) {
+            Slog.w(TAG, "Attempted to add already added display device: "
+                    + device.getDisplayDeviceInfoLocked());
+            return;
+        }
+
+        Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());
+
+        mDisplayDevices.add(device);
+        addLogicalDisplayLocked(device);
+        updateDisplayBlankingLocked(device);
+        scheduleTraversalLocked(false);
+    }
+
+    private void handleDisplayDeviceChanged(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            if (!mDisplayDevices.contains(device)) {
+                Slog.w(TAG, "Attempted to change non-existent display device: "
+                        + device.getDisplayDeviceInfoLocked());
+                return;
+            }
+
+            Slog.i(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());
+
+            device.applyPendingDisplayDeviceInfoChangesLocked();
+            if (updateLogicalDisplaysLocked()) {
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
+    private void handleDisplayDeviceRemoved(DisplayDevice device) {
+        synchronized (mSyncRoot) {
+            handleDisplayDeviceRemovedLocked(device);
+        }
+    }
+    private void handleDisplayDeviceRemovedLocked(DisplayDevice device) {
+        if (!mDisplayDevices.remove(device)) {
+            Slog.w(TAG, "Attempted to remove non-existent display device: "
+                    + device.getDisplayDeviceInfoLocked());
+            return;
+        }
+
+        Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());
+
+        updateLogicalDisplaysLocked();
+        scheduleTraversalLocked(false);
+    }
+
+    private void updateAllDisplayBlankingLocked() {
+        final int count = mDisplayDevices.size();
+        for (int i = 0; i < count; i++) {
+            DisplayDevice device = mDisplayDevices.get(i);
+            updateDisplayBlankingLocked(device);
+        }
+    }
+
+    private void updateDisplayBlankingLocked(DisplayDevice device) {
+        // Blank or unblank the display immediately to match the state requested
+        // by the power manager (if known).
+        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) {
+            switch (mAllDisplayBlankStateFromPowerManager) {
+                case DISPLAY_BLANK_STATE_BLANKED:
+                    device.blankLocked();
+                    break;
+                case DISPLAY_BLANK_STATE_UNBLANKED:
+                    device.unblankLocked();
+                    break;
+            }
+        }
+    }
+
+    // Adds a new logical display based on the given display device.
+    // Sends notifications if needed.
+    private void addLogicalDisplayLocked(DisplayDevice device) {
+        DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();
+        boolean isDefault = (deviceInfo.flags
+                & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0;
+        if (isDefault && mLogicalDisplays.get(Display.DEFAULT_DISPLAY) != null) {
+            Slog.w(TAG, "Ignoring attempt to add a second default display: " + deviceInfo);
+            isDefault = false;
+        }
+
+        if (!isDefault && mSingleDisplayDemoMode) {
+            Slog.i(TAG, "Not creating a logical display for a secondary display "
+                    + " because single display demo mode is enabled: " + deviceInfo);
+            return;
+        }
+
+        final int displayId = assignDisplayIdLocked(isDefault);
+        final int layerStack = assignLayerStackLocked(displayId);
+
+        LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
+        display.updateLocked(mDisplayDevices);
+        if (!display.isValidLocked()) {
+            // This should never happen currently.
+            Slog.w(TAG, "Ignoring display device because the logical display "
+                    + "created from it was not considered valid: " + deviceInfo);
+            return;
+        }
+
+        mLogicalDisplays.put(displayId, display);
+
+        // Wake up waitForDefaultDisplay.
+        if (isDefault) {
+            mSyncRoot.notifyAll();
+        }
+
+        sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_ADDED);
+    }
+
+    private int assignDisplayIdLocked(boolean isDefault) {
+        return isDefault ? Display.DEFAULT_DISPLAY : mNextNonDefaultDisplayId++;
+    }
+
+    private int assignLayerStackLocked(int displayId) {
+        // Currently layer stacks and display ids are the same.
+        // This need not be the case.
+        return displayId;
+    }
+
+    // Updates all existing logical displays given the current set of display devices.
+    // Removes invalid logical displays.
+    // Sends notifications if needed.
+    private boolean updateLogicalDisplaysLocked() {
+        boolean changed = false;
+        for (int i = mLogicalDisplays.size(); i-- > 0; ) {
+            final int displayId = mLogicalDisplays.keyAt(i);
+            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+
+            mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());
+            display.updateLocked(mDisplayDevices);
+            if (!display.isValidLocked()) {
+                mLogicalDisplays.removeAt(i);
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED);
+                changed = true;
+            } else if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) {
+                sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    private void performTraversalInTransactionLocked() {
+        // Clear all viewports before configuring displays so that we can keep
+        // track of which ones we have configured.
+        clearViewportsLocked();
+
+        // Configure each display device.
+        final int count = mDisplayDevices.size();
+        for (int i = 0; i < count; i++) {
+            DisplayDevice device = mDisplayDevices.get(i);
+            configureDisplayInTransactionLocked(device);
+            device.performTraversalInTransactionLocked();
+        }
+
+        // Tell the input system about these new viewports.
+        if (mInputManagerFuncs != null) {
+            mHandler.sendEmptyMessage(MSG_UPDATE_VIEWPORT);
+        }
+    }
+
+    /**
+     * Tells the display manager whether there is interesting unique content on the
+     * specified logical display.  This is used to control automatic mirroring.
+     * <p>
+     * If the display has unique content, then the display manager arranges for it
+     * to be presented on a physical display if appropriate.  Otherwise, the display manager
+     * may choose to make the physical display mirror some other logical display.
+     * </p>
+     *
+     * @param displayId The logical display id to update.
+     * @param hasContent True if the logical display has content.
+     * @param inTraversal True if called from WindowManagerService during a window traversal prior
+     * to call to performTraversalInTransactionFromWindowManager.
+     */
+    public void setDisplayHasContent(int displayId, boolean hasContent, boolean inTraversal) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null && display.hasContentLocked() != hasContent) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Display " + displayId + " hasContent flag changed: "
+                            + "hasContent=" + hasContent + ", inTraversal=" + inTraversal);
+                }
+
+                display.setHasContentLocked(hasContent);
+                scheduleTraversalLocked(inTraversal);
+            }
+        }
+    }
+
+    private void clearViewportsLocked() {
+        mDefaultViewport.valid = false;
+        mExternalTouchViewport.valid = false;
+    }
+
+    private void configureDisplayInTransactionLocked(DisplayDevice device) {
+        DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();
+        boolean isPrivate = (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0;
+
+        // Find the logical display that the display device is showing.
+        // Private displays never mirror other displays.
+        LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+        if (!isPrivate) {
+            if (display != null && !display.hasContentLocked()) {
+                // If the display does not have any content of its own, then
+                // automatically mirror the default logical display contents.
+                display = null;
+            }
+            if (display == null) {
+                display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
+            }
+        }
+
+        // Apply the logical display configuration to the display device.
+        if (display == null) {
+            // TODO: no logical display for the device, blank it
+            Slog.w(TAG, "Missing logical display to use for physical display device: "
+                    + device.getDisplayDeviceInfoLocked());
+            return;
+        }
+        boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED)
+                && (info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0;
+        display.configureDisplayInTransactionLocked(device, isBlanked);
+
+        // Update the viewports if needed.
+        if (!mDefaultViewport.valid
+                && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) {
+            setViewportLocked(mDefaultViewport, display, device);
+        }
+        if (!mExternalTouchViewport.valid
+                && info.touch == DisplayDeviceInfo.TOUCH_EXTERNAL) {
+            setViewportLocked(mExternalTouchViewport, display, device);
+        }
+    }
+
+    private static void setViewportLocked(DisplayViewport viewport,
+            LogicalDisplay display, DisplayDevice device) {
+        viewport.valid = true;
+        viewport.displayId = display.getDisplayIdLocked();
+        device.populateViewportLocked(viewport);
+    }
+
+    private LogicalDisplay findLogicalDisplayForDeviceLocked(DisplayDevice device) {
+        final int count = mLogicalDisplays.size();
+        for (int i = 0; i < count; i++) {
+            LogicalDisplay display = mLogicalDisplays.valueAt(i);
+            if (display.getPrimaryDisplayDeviceLocked() == device) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    private void sendDisplayEventLocked(int displayId, int event) {
+        Message msg = mHandler.obtainMessage(MSG_DELIVER_DISPLAY_EVENT, displayId, event);
+        mHandler.sendMessage(msg);
+    }
+
+    // Requests that performTraversalsInTransactionFromWindowManager be called at a
+    // later time to apply changes to surfaces and displays.
+    private void scheduleTraversalLocked(boolean inTraversal) {
+        if (!mPendingTraversal && mWindowManagerFuncs != null) {
+            mPendingTraversal = true;
+            if (!inTraversal) {
+                mHandler.sendEmptyMessage(MSG_REQUEST_TRAVERSAL);
+            }
+        }
+    }
+
+    // Runs on Handler thread.
+    // Delivers display event notifications to callbacks.
+    private void deliverDisplayEvent(int displayId, int event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Delivering display event: displayId="
+                    + displayId + ", event=" + event);
+        }
+
+        // Grab the lock and copy the callbacks.
+        final int count;
+        synchronized (mSyncRoot) {
+            count = mCallbacks.size();
+            mTempCallbacks.clear();
+            for (int i = 0; i < count; i++) {
+                mTempCallbacks.add(mCallbacks.valueAt(i));
+            }
+        }
+
+        // After releasing the lock, send the notifications out.
+        for (int i = 0; i < count; i++) {
+            mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
+        }
+        mTempCallbacks.clear();
+    }
+
+    @Override // Binder call
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (mContext == null
+                || mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                        != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump DisplayManager from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("DISPLAY MANAGER (dumpsys display)");
+
+        synchronized (mSyncRoot) {
+            pw.println("  mOnlyCode=" + mOnlyCore);
+            pw.println("  mSafeMode=" + mSafeMode);
+            pw.println("  mPendingTraversal=" + mPendingTraversal);
+            pw.println("  mAllDisplayBlankStateFromPowerManager="
+                    + mAllDisplayBlankStateFromPowerManager);
+            pw.println("  mNextNonDefaultDisplayId=" + mNextNonDefaultDisplayId);
+            pw.println("  mDefaultViewport=" + mDefaultViewport);
+            pw.println("  mExternalTouchViewport=" + mExternalTouchViewport);
+            pw.println("  mSingleDisplayDemoMode=" + mSingleDisplayDemoMode);
+            pw.println("  mWifiDisplayScanRequestCount=" + mWifiDisplayScanRequestCount);
+
+            IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+            ipw.increaseIndent();
+
+            pw.println();
+            pw.println("Display Adapters: size=" + mDisplayAdapters.size());
+            for (DisplayAdapter adapter : mDisplayAdapters) {
+                pw.println("  " + adapter.getName());
+                adapter.dumpLocked(ipw);
+            }
+
+            pw.println();
+            pw.println("Display Devices: size=" + mDisplayDevices.size());
+            for (DisplayDevice device : mDisplayDevices) {
+                pw.println("  " + device.getDisplayDeviceInfoLocked());
+                device.dumpLocked(ipw);
+            }
+
+            final int logicalDisplayCount = mLogicalDisplays.size();
+            pw.println();
+            pw.println("Logical Displays: size=" + logicalDisplayCount);
+            for (int i = 0; i < logicalDisplayCount; i++) {
+                int displayId = mLogicalDisplays.keyAt(i);
+                LogicalDisplay display = mLogicalDisplays.valueAt(i);
+                pw.println("  Display " + displayId + ":");
+                display.dumpLocked(ipw);
+            }
+
+            final int callbackCount = mCallbacks.size();
+            pw.println();
+            pw.println("Callbacks: size=" + callbackCount);
+            for (int i = 0; i < callbackCount; i++) {
+                CallbackRecord callback = mCallbacks.valueAt(i);
+                pw.println("  " + i + ": mPid=" + callback.mPid
+                        + ", mWifiDisplayScanRequested=" + callback.mWifiDisplayScanRequested);
+            }
+        }
+    }
+
+    /**
+     * This is the object that everything in the display manager locks on.
+     * We make it an inner class within the {@link DisplayManagerService} to so that it is
+     * clear that the object belongs to the display manager service and that it is
+     * a unique object with a special purpose.
+     */
+    public static final class SyncRoot {
+    }
+
+    /**
+     * Private interface to the window manager.
+     */
+    public interface WindowManagerFuncs {
+        /**
+         * Request that the window manager call
+         * {@link #performTraversalInTransactionFromWindowManager} within a surface
+         * transaction at a later time.
+         */
+        void requestTraversal();
+    }
+
+    /**
+     * Private interface to the input manager.
+     */
+    public interface InputManagerFuncs {
+        /**
+         * Sets information about the displays as needed by the input system.
+         * The input system should copy this information if required.
+         */
+        void setDisplayViewports(DisplayViewport defaultViewport,
+                DisplayViewport externalTouchViewport);
+    }
+
+    private final class DisplayManagerHandler extends Handler {
+        public DisplayManagerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTER:
+                    registerDefaultDisplayAdapter();
+                    break;
+
+                case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS:
+                    registerAdditionalDisplayAdapters();
+                    break;
+
+                case MSG_DELIVER_DISPLAY_EVENT:
+                    deliverDisplayEvent(msg.arg1, msg.arg2);
+                    break;
+
+                case MSG_REQUEST_TRAVERSAL:
+                    mWindowManagerFuncs.requestTraversal();
+                    break;
+
+                case MSG_UPDATE_VIEWPORT: {
+                    synchronized (mSyncRoot) {
+                        mTempDefaultViewport.copyFrom(mDefaultViewport);
+                        mTempExternalTouchViewport.copyFrom(mExternalTouchViewport);
+                    }
+                    mInputManagerFuncs.setDisplayViewports(
+                            mTempDefaultViewport, mTempExternalTouchViewport);
+                    break;
+                }
+            }
+        }
+    }
+
+    private final class DisplayAdapterListener implements DisplayAdapter.Listener {
+        @Override
+        public void onDisplayDeviceEvent(DisplayDevice device, int event) {
+            switch (event) {
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED:
+                    handleDisplayDeviceAdded(device);
+                    break;
+
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED:
+                    handleDisplayDeviceChanged(device);
+                    break;
+
+                case DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED:
+                    handleDisplayDeviceRemoved(device);
+                    break;
+            }
+        }
+
+        @Override
+        public void onTraversalRequested() {
+            synchronized (mSyncRoot) {
+                scheduleTraversalLocked(false);
+            }
+        }
+    }
+
+    private final class CallbackRecord implements DeathRecipient {
+        public final int mPid;
+        private final IDisplayManagerCallback mCallback;
+
+        public boolean mWifiDisplayScanRequested;
+
+        public CallbackRecord(int pid, IDisplayManagerCallback callback) {
+            mPid = pid;
+            mCallback = callback;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Display listener for pid " + mPid + " died.");
+            }
+            onCallbackDied(this);
+        }
+
+        public void notifyDisplayEventAsync(int displayId, int event) {
+            try {
+                mCallback.onDisplayEvent(displayId, event);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that displays changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/DisplayTransactionListener.java b/services/core/java/com/android/server/display/DisplayTransactionListener.java
new file mode 100644
index 0000000..34eb8f9
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTransactionListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+/**
+ * Called within a Surface transaction whenever the size or orientation of a
+ * display may have changed.  Provides an opportunity for the client to
+ * update the position of its surfaces as part of the same transaction.
+ */
+public interface DisplayTransactionListener {
+    void onDisplayTransaction();
+}
diff --git a/services/core/java/com/android/server/display/DisplayViewport.java b/services/core/java/com/android/server/display/DisplayViewport.java
new file mode 100644
index 0000000..5080556
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayViewport.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.graphics.Rect;
+
+/**
+ * Describes how the pixels of physical display device reflects the content of
+ * a logical display.
+ * <p>
+ * This information is used by the input system to translate touch input from
+ * physical display coordinates into logical display coordinates.
+ * </p>
+ */
+public final class DisplayViewport {
+    // True if this viewport is valid.
+    public boolean valid;
+
+    // The logical display id.
+    public int displayId;
+
+    // The rotation applied to the physical coordinate system.
+    public int orientation;
+
+    // The portion of the logical display that are presented on this physical display.
+    public final Rect logicalFrame = new Rect();
+
+    // The portion of the (rotated) physical display that shows the logical display contents.
+    // The relation between logical and physical frame defines how the coordinate system
+    // should be scaled or translated after rotation.
+    public final Rect physicalFrame = new Rect();
+
+    // The full width and height of the display device, rotated in the same
+    // manner as physicalFrame.  This expresses the full native size of the display device.
+    // The physical frame should usually fit within this area.
+    public int deviceWidth;
+    public int deviceHeight;
+
+    public void copyFrom(DisplayViewport viewport) {
+        valid = viewport.valid;
+        displayId = viewport.displayId;
+        orientation = viewport.orientation;
+        logicalFrame.set(viewport.logicalFrame);
+        physicalFrame.set(viewport.physicalFrame);
+        deviceWidth = viewport.deviceWidth;
+        deviceHeight = viewport.deviceHeight;
+    }
+
+    // For debugging purposes.
+    @Override
+    public String toString() {
+        return "DisplayViewport{valid=" + valid
+                + ", displayId=" + displayId
+                + ", orientation=" + orientation
+                + ", logicalFrame=" + logicalFrame
+                + ", physicalFrame=" + physicalFrame
+                + ", deviceWidth=" + deviceWidth
+                + ", deviceHeight=" + deviceHeight
+                + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
new file mode 100644
index 0000000..cb8f3e2
--- /dev/null
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.SystemProperties;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.DisplayEventReceiver;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.PhysicalDisplayInfo;
+
+import java.io.PrintWriter;
+
+/**
+ * A display adapter for the local displays managed by Surface Flinger.
+ * <p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class LocalDisplayAdapter extends DisplayAdapter {
+    private static final String TAG = "LocalDisplayAdapter";
+
+    private static final int[] BUILT_IN_DISPLAY_IDS_TO_SCAN = new int[] {
+            SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN,
+            SurfaceControl.BUILT_IN_DISPLAY_ID_HDMI,
+    };
+
+    private final SparseArray<LocalDisplayDevice> mDevices =
+            new SparseArray<LocalDisplayDevice>();
+    private HotplugDisplayEventReceiver mHotplugReceiver;
+
+    private final SurfaceControl.PhysicalDisplayInfo mTempPhys = new SurfaceControl.PhysicalDisplayInfo();
+
+    // Called with SyncRoot lock held.
+    public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener) {
+        super(syncRoot, context, handler, listener, TAG);
+    }
+
+    @Override
+    public void registerLocked() {
+        super.registerLocked();
+
+        mHotplugReceiver = new HotplugDisplayEventReceiver(getHandler().getLooper());
+
+        for (int builtInDisplayId : BUILT_IN_DISPLAY_IDS_TO_SCAN) {
+            tryConnectDisplayLocked(builtInDisplayId);
+        }
+    }
+
+    private void tryConnectDisplayLocked(int builtInDisplayId) {
+        IBinder displayToken = SurfaceControl.getBuiltInDisplay(builtInDisplayId);
+        if (displayToken != null && SurfaceControl.getDisplayInfo(displayToken, mTempPhys)) {
+            LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+            if (device == null) {
+                // Display was added.
+                device = new LocalDisplayDevice(displayToken, builtInDisplayId, mTempPhys);
+                mDevices.put(builtInDisplayId, device);
+                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
+            } else if (device.updatePhysicalDisplayInfoLocked(mTempPhys)) {
+                // Display properties changed.
+                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
+            }
+        } else {
+            // The display is no longer available. Ignore the attempt to add it.
+            // If it was connected but has already been disconnected, we'll get a
+            // disconnect event that will remove it from mDevices.
+        }
+    }
+
+    private void tryDisconnectDisplayLocked(int builtInDisplayId) {
+        LocalDisplayDevice device = mDevices.get(builtInDisplayId);
+        if (device != null) {
+            // Display was removed.
+            mDevices.remove(builtInDisplayId);
+            sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        }
+    }
+
+    private final class LocalDisplayDevice extends DisplayDevice {
+        private final int mBuiltInDisplayId;
+        private final SurfaceControl.PhysicalDisplayInfo mPhys;
+
+        private DisplayDeviceInfo mInfo;
+        private boolean mHavePendingChanges;
+        private boolean mBlanked;
+
+        public LocalDisplayDevice(IBinder displayToken, int builtInDisplayId,
+                SurfaceControl.PhysicalDisplayInfo phys) {
+            super(LocalDisplayAdapter.this, displayToken);
+            mBuiltInDisplayId = builtInDisplayId;
+            mPhys = new SurfaceControl.PhysicalDisplayInfo(phys);
+        }
+
+        public boolean updatePhysicalDisplayInfoLocked(SurfaceControl.PhysicalDisplayInfo phys) {
+            if (!mPhys.equals(phys)) {
+                mPhys.copyFrom(phys);
+                mHavePendingChanges = true;
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void applyPendingDisplayDeviceInfoChangesLocked() {
+            if (mHavePendingChanges) {
+                mInfo = null;
+                mHavePendingChanges = false;
+            }
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.width = mPhys.width;
+                mInfo.height = mPhys.height;
+                mInfo.refreshRate = mPhys.refreshRate;
+
+                // Assume that all built-in displays that have secure output (eg. HDCP) also
+                // support compositing from gralloc protected buffers.
+                if (mPhys.secure) {
+                    mInfo.flags = DisplayDeviceInfo.FLAG_SECURE
+                            | DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
+                }
+
+                if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
+                    mInfo.name = getContext().getResources().getString(
+                            com.android.internal.R.string.display_manager_built_in_display_name);
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
+                            | DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+                    mInfo.type = Display.TYPE_BUILT_IN;
+                    mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
+                    mInfo.xDpi = mPhys.xDpi;
+                    mInfo.yDpi = mPhys.yDpi;
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+                } else {
+                    mInfo.type = Display.TYPE_HDMI;
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
+                    mInfo.name = getContext().getResources().getString(
+                            com.android.internal.R.string.display_manager_hdmi_display_name);
+                    mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+                    mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height);
+
+                    // For demonstration purposes, allow rotation of the external display.
+                    // In the future we might allow the user to configure this directly.
+                    if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
+                        mInfo.rotation = Surface.ROTATION_270;
+                    }
+                }
+            }
+            return mInfo;
+        }
+
+        @Override
+        public void blankLocked() {
+            mBlanked = true;
+            SurfaceControl.blankDisplay(getDisplayTokenLocked());
+        }
+
+        @Override
+        public void unblankLocked() {
+            mBlanked = false;
+            SurfaceControl.unblankDisplay(getDisplayTokenLocked());
+        }
+
+        @Override
+        public void dumpLocked(PrintWriter pw) {
+            super.dumpLocked(pw);
+            pw.println("mBuiltInDisplayId=" + mBuiltInDisplayId);
+            pw.println("mPhys=" + mPhys);
+            pw.println("mBlanked=" + mBlanked);
+        }
+    }
+
+    private final class HotplugDisplayEventReceiver extends DisplayEventReceiver {
+        public HotplugDisplayEventReceiver(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void onHotplug(long timestampNanos, int builtInDisplayId, boolean connected) {
+            synchronized (getSyncRoot()) {
+                if (connected) {
+                    tryConnectDisplayLocked(builtInDisplayId);
+                } else {
+                    tryDisconnectDisplayLocked(builtInDisplayId);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
new file mode 100644
index 0000000..7e357c0
--- /dev/null
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.graphics.Rect;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+
+import java.io.PrintWriter;
+import java.util.List;
+
+import libcore.util.Objects;
+
+/**
+ * Describes how a logical display is configured.
+ * <p>
+ * At this time, we only support logical displays that are coupled to a particular
+ * primary display device from which the logical display derives its basic properties
+ * such as its size, density and refresh rate.
+ * </p><p>
+ * A logical display may be mirrored onto multiple display devices in addition to its
+ * primary display device.  Note that the contents of a logical display may not
+ * always be visible, even on its primary display device, such as in the case where
+ * the primary display device is currently mirroring content from a different
+ * logical display.
+ * </p><p>
+ * This object is designed to encapsulate as much of the policy of logical
+ * displays as possible.  The idea is to make it easy to implement new kinds of
+ * logical displays mostly by making local changes to this class.
+ * </p><p>
+ * Note: The display manager architecture does not actually require logical displays
+ * to be associated with any individual display device.  Logical displays and
+ * display devices are orthogonal concepts.  Some mapping will exist between
+ * logical displays and display devices but it can be many-to-many and
+ * and some might have no relation at all.
+ * </p><p>
+ * Logical displays are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class LogicalDisplay {
+    private final DisplayInfo mBaseDisplayInfo = new DisplayInfo();
+
+    // The layer stack we use when the display has been blanked to prevent any
+    // of its content from appearing.
+    private static final int BLANK_LAYER_STACK = -1;
+
+    private final int mDisplayId;
+    private final int mLayerStack;
+    private DisplayInfo mOverrideDisplayInfo; // set by the window manager
+    private DisplayInfo mInfo;
+
+    // The display device that this logical display is based on and which
+    // determines the base metrics that it uses.
+    private DisplayDevice mPrimaryDisplayDevice;
+    private DisplayDeviceInfo mPrimaryDisplayDeviceInfo;
+
+    // True if the logical display has unique content.
+    private boolean mHasContent;
+
+    // Temporary rectangle used when needed.
+    private final Rect mTempLayerStackRect = new Rect();
+    private final Rect mTempDisplayRect = new Rect();
+
+    public LogicalDisplay(int displayId, int layerStack, DisplayDevice primaryDisplayDevice) {
+        mDisplayId = displayId;
+        mLayerStack = layerStack;
+        mPrimaryDisplayDevice = primaryDisplayDevice;
+    }
+
+    /**
+     * Gets the logical display id of this logical display.
+     *
+     * @return The logical display id.
+     */
+    public int getDisplayIdLocked() {
+        return mDisplayId;
+    }
+
+    /**
+     * Gets the primary display device associated with this logical display.
+     *
+     * @return The primary display device.
+     */
+    public DisplayDevice getPrimaryDisplayDeviceLocked() {
+        return mPrimaryDisplayDevice;
+    }
+
+    /**
+     * Gets information about the logical display.
+     *
+     * @return The device info, which should be treated as immutable by the caller.
+     * The logical display should allocate a new display info object whenever
+     * the data changes.
+     */
+    public DisplayInfo getDisplayInfoLocked() {
+        if (mInfo == null) {
+            mInfo = new DisplayInfo();
+            if (mOverrideDisplayInfo != null) {
+                mInfo.copyFrom(mOverrideDisplayInfo);
+                mInfo.layerStack = mBaseDisplayInfo.layerStack;
+                mInfo.name = mBaseDisplayInfo.name;
+            } else {
+                mInfo.copyFrom(mBaseDisplayInfo);
+            }
+        }
+        return mInfo;
+    }
+
+    /**
+     * Sets overridden logical display information from the window manager.
+     * This method can be used to adjust application insets, rotation, and other
+     * properties that the window manager takes care of.
+     *
+     * @param info The logical display information, may be null.
+     */
+    public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
+        if (info != null) {
+            if (mOverrideDisplayInfo == null) {
+                mOverrideDisplayInfo = new DisplayInfo(info);
+                mInfo = null;
+                return true;
+            }
+            if (!mOverrideDisplayInfo.equals(info)) {
+                mOverrideDisplayInfo.copyFrom(info);
+                mInfo = null;
+                return true;
+            }
+        } else if (mOverrideDisplayInfo != null) {
+            mOverrideDisplayInfo = null;
+            mInfo = null;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the logical display is in a valid state.
+     * This method should be checked after calling {@link #updateLocked} to handle the
+     * case where a logical display should be removed because all of its associated
+     * display devices are gone or if it is otherwise no longer needed.
+     *
+     * @return True if the logical display is still valid.
+     */
+    public boolean isValidLocked() {
+        return mPrimaryDisplayDevice != null;
+    }
+
+    /**
+     * Updates the state of the logical display based on the available display devices.
+     * The logical display might become invalid if it is attached to a display device
+     * that no longer exists.
+     *
+     * @param devices The list of all connected display devices.
+     */
+    public void updateLocked(List<DisplayDevice> devices) {
+        // Nothing to update if already invalid.
+        if (mPrimaryDisplayDevice == null) {
+            return;
+        }
+
+        // Check whether logical display has become invalid.
+        if (!devices.contains(mPrimaryDisplayDevice)) {
+            mPrimaryDisplayDevice = null;
+            return;
+        }
+
+        // Bootstrap the logical display using its associated primary physical display.
+        // We might use more elaborate configurations later.  It's possible that the
+        // configuration of several physical displays might be used to determine the
+        // logical display that they are sharing.  (eg. Adjust size for pixel-perfect
+        // mirroring over HDMI.)
+        DisplayDeviceInfo deviceInfo = mPrimaryDisplayDevice.getDisplayDeviceInfoLocked();
+        if (!Objects.equal(mPrimaryDisplayDeviceInfo, deviceInfo)) {
+            mBaseDisplayInfo.layerStack = mLayerStack;
+            mBaseDisplayInfo.flags = 0;
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_SUPPORTS_PROTECTED_BUFFERS;
+            }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SECURE) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_SECURE;
+            }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_PRIVATE;
+            }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
+            }
+            mBaseDisplayInfo.type = deviceInfo.type;
+            mBaseDisplayInfo.address = deviceInfo.address;
+            mBaseDisplayInfo.name = deviceInfo.name;
+            mBaseDisplayInfo.appWidth = deviceInfo.width;
+            mBaseDisplayInfo.appHeight = deviceInfo.height;
+            mBaseDisplayInfo.logicalWidth = deviceInfo.width;
+            mBaseDisplayInfo.logicalHeight = deviceInfo.height;
+            mBaseDisplayInfo.rotation = Surface.ROTATION_0;
+            mBaseDisplayInfo.refreshRate = deviceInfo.refreshRate;
+            mBaseDisplayInfo.logicalDensityDpi = deviceInfo.densityDpi;
+            mBaseDisplayInfo.physicalXDpi = deviceInfo.xDpi;
+            mBaseDisplayInfo.physicalYDpi = deviceInfo.yDpi;
+            mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width;
+            mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height;
+            mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width;
+            mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height;
+            mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid;
+            mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName;
+
+            mPrimaryDisplayDeviceInfo = deviceInfo;
+            mInfo = null;
+        }
+    }
+
+    /**
+     * Applies the layer stack and transformation to the given display device
+     * so that it shows the contents of this logical display.
+     *
+     * We know that the given display device is only ever showing the contents of
+     * a single logical display, so this method is expected to blow away all of its
+     * transformation properties to make it happen regardless of what the
+     * display device was previously showing.
+     *
+     * The caller must have an open Surface transaction.
+     *
+     * The display device may not be the primary display device, in the case
+     * where the display is being mirrored.
+     *
+     * @param device The display device to modify.
+     * @param isBlanked True if the device is being blanked.
+     */
+    public void configureDisplayInTransactionLocked(DisplayDevice device,
+            boolean isBlanked) {
+        final DisplayInfo displayInfo = getDisplayInfoLocked();
+        final DisplayDeviceInfo displayDeviceInfo = device.getDisplayDeviceInfoLocked();
+
+        // Set the layer stack.
+        device.setLayerStackInTransactionLocked(isBlanked ? BLANK_LAYER_STACK : mLayerStack);
+
+        // Set the viewport.
+        // This is the area of the logical display that we intend to show on the
+        // display device.  For now, it is always the full size of the logical display.
+        mTempLayerStackRect.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
+
+        // Set the orientation.
+        // The orientation specifies how the physical coordinate system of the display
+        // is rotated when the contents of the logical display are rendered.
+        int orientation = Surface.ROTATION_0;
+        if (device == mPrimaryDisplayDevice
+                && (displayDeviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
+            orientation = displayInfo.rotation;
+        }
+
+        // Apply the physical rotation of the display device itself.
+        orientation = (orientation + displayDeviceInfo.rotation) % 4;
+
+        // Set the frame.
+        // The frame specifies the rotated physical coordinates into which the viewport
+        // is mapped.  We need to take care to preserve the aspect ratio of the viewport.
+        // Currently we maximize the area to fill the display, but we could try to be
+        // more clever and match resolutions.
+        boolean rotated = (orientation == Surface.ROTATION_90
+                || orientation == Surface.ROTATION_270);
+        int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
+        int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
+
+        // Determine whether the width or height is more constrained to be scaled.
+        //    physWidth / displayInfo.logicalWidth    => letter box
+        // or physHeight / displayInfo.logicalHeight  => pillar box
+        //
+        // We avoid a division (and possible floating point imprecision) here by
+        // multiplying the fractions by the product of their denominators before
+        // comparing them.
+        int displayRectWidth, displayRectHeight;
+        if (physWidth * displayInfo.logicalHeight
+                < physHeight * displayInfo.logicalWidth) {
+            // Letter box.
+            displayRectWidth = physWidth;
+            displayRectHeight = displayInfo.logicalHeight * physWidth / displayInfo.logicalWidth;
+        } else {
+            // Pillar box.
+            displayRectWidth = displayInfo.logicalWidth * physHeight / displayInfo.logicalHeight;
+            displayRectHeight = physHeight;
+        }
+        int displayRectTop = (physHeight - displayRectHeight) / 2;
+        int displayRectLeft = (physWidth - displayRectWidth) / 2;
+        mTempDisplayRect.set(displayRectLeft, displayRectTop,
+                displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);
+
+        device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);
+    }
+
+    /**
+     * Returns true if the logical display has unique content.
+     * <p>
+     * If the display has unique content then we will try to ensure that it is
+     * visible on at least its primary display device.  Otherwise we will ignore the
+     * logical display and perhaps show mirrored content on the primary display device.
+     * </p>
+     *
+     * @return True if the display has unique content.
+     */
+    public boolean hasContentLocked() {
+        return mHasContent;
+    }
+
+    /**
+     * Sets whether the logical display has unique content.
+     *
+     * @param hasContent True if the display has unique content.
+     */
+    public void setHasContentLocked(boolean hasContent) {
+        mHasContent = hasContent;
+    }
+
+    public void dumpLocked(PrintWriter pw) {
+        pw.println("mDisplayId=" + mDisplayId);
+        pw.println("mLayerStack=" + mLayerStack);
+        pw.println("mHasContent=" + mHasContent);
+        pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
+                mPrimaryDisplayDevice.getNameLocked() : "null"));
+        pw.println("mBaseDisplayInfo=" + mBaseDisplayInfo);
+        pw.println("mOverrideDisplayInfo=" + mOverrideDisplayInfo);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
new file mode 100644
index 0000000..007acf7
--- /dev/null
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -0,0 +1,359 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.SurfaceTexture;
+import android.os.Handler;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A display adapter that uses overlay windows to simulate secondary displays
+ * for development purposes.  Use Development Settings to enable one or more
+ * overlay displays.
+ * <p>
+ * This object has two different handlers (which may be the same) which must not
+ * get confused.  The main handler is used to posting messages to the display manager
+ * service as usual.  The UI handler is only used by the {@link OverlayDisplayWindow}.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class OverlayDisplayAdapter extends DisplayAdapter {
+    static final String TAG = "OverlayDisplayAdapter";
+    static final boolean DEBUG = false;
+
+    private static final int MIN_WIDTH = 100;
+    private static final int MIN_HEIGHT = 100;
+    private static final int MAX_WIDTH = 4096;
+    private static final int MAX_HEIGHT = 4096;
+
+    private static final Pattern SETTING_PATTERN =
+            Pattern.compile("(\\d+)x(\\d+)/(\\d+)(,[a-z]+)*");
+
+    private final Handler mUiHandler;
+    private final ArrayList<OverlayDisplayHandle> mOverlays =
+            new ArrayList<OverlayDisplayHandle>();
+    private String mCurrentOverlaySetting = "";
+
+    // Called with SyncRoot lock held.
+    public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener, Handler uiHandler) {
+        super(syncRoot, context, handler, listener, TAG);
+        mUiHandler = uiHandler;
+    }
+
+    @Override
+    public void dumpLocked(PrintWriter pw) {
+        super.dumpLocked(pw);
+
+        pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
+        pw.println("mOverlays: size=" + mOverlays.size());
+        for (OverlayDisplayHandle overlay : mOverlays) {
+            overlay.dumpLocked(pw);
+        }
+    }
+
+    @Override
+    public void registerLocked() {
+        super.registerLocked();
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                getContext().getContentResolver().registerContentObserver(
+                        Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
+                        true, new ContentObserver(getHandler()) {
+                            @Override
+                            public void onChange(boolean selfChange) {
+                                updateOverlayDisplayDevices();
+                            }
+                        });
+
+                updateOverlayDisplayDevices();
+            }
+        });
+    }
+
+    private void updateOverlayDisplayDevices() {
+        synchronized (getSyncRoot()) {
+            updateOverlayDisplayDevicesLocked();
+        }
+    }
+
+    private void updateOverlayDisplayDevicesLocked() {
+        String value = Settings.Global.getString(getContext().getContentResolver(),
+                Settings.Global.OVERLAY_DISPLAY_DEVICES);
+        if (value == null) {
+            value = "";
+        }
+
+        if (value.equals(mCurrentOverlaySetting)) {
+            return;
+        }
+        mCurrentOverlaySetting = value;
+
+        if (!mOverlays.isEmpty()) {
+            Slog.i(TAG, "Dismissing all overlay display devices.");
+            for (OverlayDisplayHandle overlay : mOverlays) {
+                overlay.dismissLocked();
+            }
+            mOverlays.clear();
+        }
+
+        int count = 0;
+        for (String part : value.split(";")) {
+            Matcher matcher = SETTING_PATTERN.matcher(part);
+            if (matcher.matches()) {
+                if (count >= 4) {
+                    Slog.w(TAG, "Too many overlay display devices specified: " + value);
+                    break;
+                }
+                try {
+                    int width = Integer.parseInt(matcher.group(1), 10);
+                    int height = Integer.parseInt(matcher.group(2), 10);
+                    int densityDpi = Integer.parseInt(matcher.group(3), 10);
+                    String flagString = matcher.group(4);
+                    if (width >= MIN_WIDTH && width <= MAX_WIDTH
+                            && height >= MIN_HEIGHT && height <= MAX_HEIGHT
+                            && densityDpi >= DisplayMetrics.DENSITY_LOW
+                            && densityDpi <= DisplayMetrics.DENSITY_XXHIGH) {
+                        int number = ++count;
+                        String name = getContext().getResources().getString(
+                                com.android.internal.R.string.display_manager_overlay_display_name,
+                                number);
+                        int gravity = chooseOverlayGravity(number);
+                        boolean secure = flagString != null && flagString.contains(",secure");
+
+                        Slog.i(TAG, "Showing overlay display device #" + number
+                                + ": name=" + name + ", width=" + width + ", height=" + height
+                                + ", densityDpi=" + densityDpi + ", secure=" + secure);
+
+                        mOverlays.add(new OverlayDisplayHandle(name,
+                                width, height, densityDpi, gravity, secure));
+                        continue;
+                    }
+                } catch (NumberFormatException ex) {
+                }
+            } else if (part.isEmpty()) {
+                continue;
+            }
+            Slog.w(TAG, "Malformed overlay display devices setting: " + value);
+        }
+    }
+
+    private static int chooseOverlayGravity(int overlayNumber) {
+        switch (overlayNumber) {
+            case 1:
+                return Gravity.TOP | Gravity.LEFT;
+            case 2:
+                return Gravity.BOTTOM | Gravity.RIGHT;
+            case 3:
+                return Gravity.TOP | Gravity.RIGHT;
+            case 4:
+            default:
+                return Gravity.BOTTOM | Gravity.LEFT;
+        }
+    }
+
+    private final class OverlayDisplayDevice extends DisplayDevice {
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final float mRefreshRate;
+        private final int mDensityDpi;
+        private final boolean mSecure;
+
+        private Surface mSurface;
+        private SurfaceTexture mSurfaceTexture;
+        private DisplayDeviceInfo mInfo;
+
+        public OverlayDisplayDevice(IBinder displayToken, String name,
+                int width, int height, float refreshRate, int densityDpi, boolean secure,
+                SurfaceTexture surfaceTexture) {
+            super(OverlayDisplayAdapter.this, displayToken);
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mRefreshRate = refreshRate;
+            mDensityDpi = densityDpi;
+            mSecure = secure;
+            mSurfaceTexture = surfaceTexture;
+        }
+
+        public void destroyLocked() {
+            mSurfaceTexture = null;
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+        }
+
+        @Override
+        public void performTraversalInTransactionLocked() {
+            if (mSurfaceTexture != null) {
+                if (mSurface == null) {
+                    mSurface = new Surface(mSurfaceTexture);
+                }
+                setSurfaceInTransactionLocked(mSurface);
+            }
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = mName;
+                mInfo.width = mWidth;
+                mInfo.height = mHeight;
+                mInfo.refreshRate = mRefreshRate;
+                mInfo.densityDpi = mDensityDpi;
+                mInfo.xDpi = mDensityDpi;
+                mInfo.yDpi = mDensityDpi;
+                mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION;
+                if (mSecure) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
+                }
+                mInfo.type = Display.TYPE_OVERLAY;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+            }
+            return mInfo;
+        }
+    }
+
+    /**
+     * Functions as a handle for overlay display devices which are created and
+     * destroyed asynchronously.
+     *
+     * Guarded by the {@link DisplayManagerService.SyncRoot} lock.
+     */
+    private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final int mDensityDpi;
+        private final int mGravity;
+        private final boolean mSecure;
+
+        private OverlayDisplayWindow mWindow;
+        private OverlayDisplayDevice mDevice;
+
+        public OverlayDisplayHandle(String name,
+                int width, int height, int densityDpi, int gravity, boolean secure) {
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mDensityDpi = densityDpi;
+            mGravity = gravity;
+            mSecure = secure;
+
+            mUiHandler.post(mShowRunnable);
+        }
+
+        public void dismissLocked() {
+            mUiHandler.removeCallbacks(mShowRunnable);
+            mUiHandler.post(mDismissRunnable);
+        }
+
+        // Called on the UI thread.
+        @Override
+        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
+            synchronized (getSyncRoot()) {
+                IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure);
+                mDevice = new OverlayDisplayDevice(displayToken, mName,
+                        mWidth, mHeight, refreshRate, mDensityDpi, mSecure, surfaceTexture);
+
+                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
+            }
+        }
+
+        // Called on the UI thread.
+        @Override
+        public void onWindowDestroyed() {
+            synchronized (getSyncRoot()) {
+                if (mDevice != null) {
+                    mDevice.destroyLocked();
+                    sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+                }
+            }
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.println("  " + mName + ":");
+            pw.println("    mWidth=" + mWidth);
+            pw.println("    mHeight=" + mHeight);
+            pw.println("    mDensityDpi=" + mDensityDpi);
+            pw.println("    mGravity=" + mGravity);
+            pw.println("    mSecure=" + mSecure);
+
+            // Try to dump the window state.
+            if (mWindow != null) {
+                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+                ipw.increaseIndent();
+                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
+            }
+        }
+
+        // Runs on the UI thread.
+        private final Runnable mShowRunnable = new Runnable() {
+            @Override
+            public void run() {
+                OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
+                        mName, mWidth, mHeight, mDensityDpi, mGravity, mSecure,
+                        OverlayDisplayHandle.this);
+                window.show();
+
+                synchronized (getSyncRoot()) {
+                    mWindow = window;
+                }
+            }
+        };
+
+        // Runs on the UI thread.
+        private final Runnable mDismissRunnable = new Runnable() {
+            @Override
+            public void run() {
+                OverlayDisplayWindow window;
+                synchronized (getSyncRoot()) {
+                    window = mWindow;
+                    mWindow = null;
+                }
+
+                if (window != null) {
+                    window.dismiss();
+                }
+            }
+        };
+    }
+}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
new file mode 100644
index 0000000..f1dd60a
--- /dev/null
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.hardware.display.DisplayManager;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.GestureDetector;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.TextureView.SurfaceTextureListener;
+import android.widget.TextView;
+
+import java.io.PrintWriter;
+
+/**
+ * Manages an overlay window on behalf of {@link OverlayDisplayAdapter}.
+ * <p>
+ * This object must only be accessed on the UI thread.
+ * No locks are held by this object and locks must not be held while making called into it.
+ * </p>
+ */
+final class OverlayDisplayWindow implements DumpUtils.Dump {
+    private static final String TAG = "OverlayDisplayWindow";
+    private static final boolean DEBUG = false;
+
+    private final float INITIAL_SCALE = 0.5f;
+    private final float MIN_SCALE = 0.3f;
+    private final float MAX_SCALE = 1.0f;
+    private final float WINDOW_ALPHA = 0.8f;
+
+    // When true, disables support for moving and resizing the overlay.
+    // The window is made non-touchable, which makes it possible to
+    // directly interact with the content underneath.
+    private final boolean DISABLE_MOVE_AND_RESIZE = false;
+
+    private final Context mContext;
+    private final String mName;
+    private final int mWidth;
+    private final int mHeight;
+    private final int mDensityDpi;
+    private final int mGravity;
+    private final boolean mSecure;
+    private final Listener mListener;
+    private String mTitle;
+
+    private final DisplayManager mDisplayManager;
+    private final WindowManager mWindowManager;
+
+
+    private final Display mDefaultDisplay;
+    private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+
+    private View mWindowContent;
+    private WindowManager.LayoutParams mWindowParams;
+    private TextureView mTextureView;
+    private TextView mTitleTextView;
+
+    private GestureDetector mGestureDetector;
+    private ScaleGestureDetector mScaleGestureDetector;
+
+    private boolean mWindowVisible;
+    private int mWindowX;
+    private int mWindowY;
+    private float mWindowScale;
+
+    private float mLiveTranslationX;
+    private float mLiveTranslationY;
+    private float mLiveScale = 1.0f;
+
+    public OverlayDisplayWindow(Context context, String name,
+            int width, int height, int densityDpi, int gravity, boolean secure,
+            Listener listener) {
+        mContext = context;
+        mName = name;
+        mWidth = width;
+        mHeight = height;
+        mDensityDpi = densityDpi;
+        mGravity = gravity;
+        mSecure = secure;
+        mListener = listener;
+        mTitle = context.getResources().getString(
+                com.android.internal.R.string.display_manager_overlay_display_title,
+                mName, mWidth, mHeight, mDensityDpi);
+        if (secure) {
+            mTitle += context.getResources().getString(
+                    com.android.internal.R.string.display_manager_overlay_display_secure_suffix);
+        }
+
+        mDisplayManager = (DisplayManager)context.getSystemService(
+                Context.DISPLAY_SERVICE);
+        mWindowManager = (WindowManager)context.getSystemService(
+                Context.WINDOW_SERVICE);
+
+        mDefaultDisplay = mWindowManager.getDefaultDisplay();
+        updateDefaultDisplayInfo();
+
+        createWindow();
+    }
+
+    public void show() {
+        if (!mWindowVisible) {
+            mDisplayManager.registerDisplayListener(mDisplayListener, null);
+            if (!updateDefaultDisplayInfo()) {
+                mDisplayManager.unregisterDisplayListener(mDisplayListener);
+                return;
+            }
+
+            clearLiveState();
+            updateWindowParams();
+            mWindowManager.addView(mWindowContent, mWindowParams);
+            mWindowVisible = true;
+        }
+    }
+
+    public void dismiss() {
+        if (mWindowVisible) {
+            mDisplayManager.unregisterDisplayListener(mDisplayListener);
+            mWindowManager.removeView(mWindowContent);
+            mWindowVisible = false;
+        }
+    }
+
+    public void relayout() {
+        if (mWindowVisible) {
+            updateWindowParams();
+            mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("mWindowVisible=" + mWindowVisible);
+        pw.println("mWindowX=" + mWindowX);
+        pw.println("mWindowY=" + mWindowY);
+        pw.println("mWindowScale=" + mWindowScale);
+        pw.println("mWindowParams=" + mWindowParams);
+        if (mTextureView != null) {
+            pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
+            pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
+        }
+        pw.println("mLiveTranslationX=" + mLiveTranslationX);
+        pw.println("mLiveTranslationY=" + mLiveTranslationY);
+        pw.println("mLiveScale=" + mLiveScale);
+    }
+
+    private boolean updateDefaultDisplayInfo() {
+        if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
+            Slog.w(TAG, "Cannot show overlay display because there is no "
+                    + "default display upon which to show it.");
+            return false;
+        }
+        return true;
+    }
+
+    private void createWindow() {
+        LayoutInflater inflater = LayoutInflater.from(mContext);
+
+        mWindowContent = inflater.inflate(
+                com.android.internal.R.layout.overlay_display_window, null);
+        mWindowContent.setOnTouchListener(mOnTouchListener);
+
+        mTextureView = (TextureView)mWindowContent.findViewById(
+                com.android.internal.R.id.overlay_display_window_texture);
+        mTextureView.setPivotX(0);
+        mTextureView.setPivotY(0);
+        mTextureView.getLayoutParams().width = mWidth;
+        mTextureView.getLayoutParams().height = mHeight;
+        mTextureView.setOpaque(false);
+        mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
+
+        mTitleTextView = (TextView)mWindowContent.findViewById(
+                com.android.internal.R.id.overlay_display_window_title);
+        mTitleTextView.setText(mTitle);
+
+        mWindowParams = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY);
+        mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+        if (mSecure) {
+            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE;
+        }
+        if (DISABLE_MOVE_AND_RESIZE) {
+            mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        }
+        mWindowParams.privateFlags |=
+                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
+        mWindowParams.alpha = WINDOW_ALPHA;
+        mWindowParams.gravity = Gravity.TOP | Gravity.LEFT;
+        mWindowParams.setTitle(mTitle);
+
+        mGestureDetector = new GestureDetector(mContext, mOnGestureListener);
+        mScaleGestureDetector = new ScaleGestureDetector(mContext, mOnScaleGestureListener);
+
+        // Set the initial position and scale.
+        // The position and scale will be clamped when the display is first shown.
+        mWindowX = (mGravity & Gravity.LEFT) == Gravity.LEFT ?
+                0 : mDefaultDisplayInfo.logicalWidth;
+        mWindowY = (mGravity & Gravity.TOP) == Gravity.TOP ?
+                0 : mDefaultDisplayInfo.logicalHeight;
+        mWindowScale = INITIAL_SCALE;
+    }
+
+    private void updateWindowParams() {
+        float scale = mWindowScale * mLiveScale;
+        scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalWidth / mWidth);
+        scale = Math.min(scale, (float)mDefaultDisplayInfo.logicalHeight / mHeight);
+        scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
+
+        float offsetScale = (scale / mWindowScale - 1.0f) * 0.5f;
+        int width = (int)(mWidth * scale);
+        int height = (int)(mHeight * scale);
+        int x = (int)(mWindowX + mLiveTranslationX - width * offsetScale);
+        int y = (int)(mWindowY + mLiveTranslationY - height * offsetScale);
+        x = Math.max(0, Math.min(x, mDefaultDisplayInfo.logicalWidth - width));
+        y = Math.max(0, Math.min(y, mDefaultDisplayInfo.logicalHeight - height));
+
+        if (DEBUG) {
+            Slog.d(TAG, "updateWindowParams: scale=" + scale
+                    + ", offsetScale=" + offsetScale
+                    + ", x=" + x + ", y=" + y
+                    + ", width=" + width + ", height=" + height);
+        }
+
+        mTextureView.setScaleX(scale);
+        mTextureView.setScaleY(scale);
+
+        mWindowParams.x = x;
+        mWindowParams.y = y;
+        mWindowParams.width = width;
+        mWindowParams.height = height;
+    }
+
+    private void saveWindowParams() {
+        mWindowX = mWindowParams.x;
+        mWindowY = mWindowParams.y;
+        mWindowScale = mTextureView.getScaleX();
+        clearLiveState();
+    }
+
+    private void clearLiveState() {
+        mLiveTranslationX = 0f;
+        mLiveTranslationY = 0f;
+        mLiveScale = 1.0f;
+    }
+
+    private final DisplayManager.DisplayListener mDisplayListener =
+            new DisplayManager.DisplayListener() {
+        @Override
+        public void onDisplayAdded(int displayId) {
+        }
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId == mDefaultDisplay.getDisplayId()) {
+                if (updateDefaultDisplayInfo()) {
+                    relayout();
+                } else {
+                    dismiss();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            if (displayId == mDefaultDisplay.getDisplayId()) {
+                dismiss();
+            }
+        }
+    };
+
+    private final SurfaceTextureListener mSurfaceTextureListener =
+            new SurfaceTextureListener() {
+        @Override
+        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+                int width, int height) {
+            mListener.onWindowCreated(surfaceTexture, mDefaultDisplayInfo.refreshRate);
+        }
+
+        @Override
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+            mListener.onWindowDestroyed();
+            return true;
+        }
+
+        @Override
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+                int width, int height) {
+        }
+
+        @Override
+        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
+        }
+    };
+
+    private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View view, MotionEvent event) {
+            // Work in screen coordinates.
+            final float oldX = event.getX();
+            final float oldY = event.getY();
+            event.setLocation(event.getRawX(), event.getRawY());
+
+            mGestureDetector.onTouchEvent(event);
+            mScaleGestureDetector.onTouchEvent(event);
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    saveWindowParams();
+                    break;
+            }
+
+            // Revert to window coordinates.
+            event.setLocation(oldX, oldY);
+            return true;
+        }
+    };
+
+    private final GestureDetector.OnGestureListener mOnGestureListener =
+            new GestureDetector.SimpleOnGestureListener() {
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            mLiveTranslationX -= distanceX;
+            mLiveTranslationY -= distanceY;
+            relayout();
+            return true;
+        }
+    };
+
+    private final ScaleGestureDetector.OnScaleGestureListener mOnScaleGestureListener =
+            new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            mLiveScale *= detector.getScaleFactor();
+            relayout();
+            return true;
+        }
+    };
+
+    /**
+     * Watches for significant changes in the overlay display window lifecycle.
+     */
+    public interface Listener {
+        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
+        public void onWindowDestroyed();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
new file mode 100644
index 0000000..67b3695
--- /dev/null
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.hardware.display.WifiDisplay;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the display manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * &lt;display-manager-state>
+ *   &lt;remembered-wifi-displays>
+ *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
+ *   &gt;remembered-wifi-displays>
+ * &gt;/display-manager-state>
+ * </code>
+ *
+ * TODO: refactor this to extract common code shared with the input manager's data store
+ */
+final class PersistentDataStore {
+    static final String TAG = "DisplayManager";
+
+    // Remembered Wifi display devices.
+    private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
+
+    // The atomic file used to safely read or write the file.
+    private final AtomicFile mAtomicFile;
+
+    // True if the data has been loaded.
+    private boolean mLoaded;
+
+    // True if there are changes to be saved.
+    private boolean mDirty;
+
+    public PersistentDataStore() {
+        mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
+    }
+
+    public void saveIfNeeded() {
+        if (mDirty) {
+            save();
+            mDirty = false;
+        }
+    }
+
+    public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
+        loadIfNeeded();
+        int index = findRememberedWifiDisplay(deviceAddress);
+        if (index >= 0) {
+            return mRememberedWifiDisplays.get(index);
+        }
+        return null;
+    }
+
+    public WifiDisplay[] getRememberedWifiDisplays() {
+        loadIfNeeded();
+        return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
+    }
+
+    public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
+        if (display != null) {
+            loadIfNeeded();
+
+            String alias = null;
+            int index = findRememberedWifiDisplay(display.getDeviceAddress());
+            if (index >= 0) {
+                alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
+            }
+            if (!Objects.equal(display.getDeviceAlias(), alias)) {
+                return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
+                        alias, display.isAvailable(), display.canConnect(), display.isRemembered());
+            }
+        }
+        return display;
+    }
+
+    public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
+        WifiDisplay[] results = displays;
+        if (results != null) {
+            int count = displays.length;
+            for (int i = 0; i < count; i++) {
+                WifiDisplay result = applyWifiDisplayAlias(displays[i]);
+                if (result != displays[i]) {
+                    if (results == displays) {
+                        results = new WifiDisplay[count];
+                        System.arraycopy(displays, 0, results, 0, count);
+                    }
+                    results[i] = result;
+                }
+            }
+        }
+        return results;
+    }
+
+    public boolean rememberWifiDisplay(WifiDisplay display) {
+        loadIfNeeded();
+
+        int index = findRememberedWifiDisplay(display.getDeviceAddress());
+        if (index >= 0) {
+            WifiDisplay other = mRememberedWifiDisplays.get(index);
+            if (other.equals(display)) {
+                return false; // already remembered without change
+            }
+            mRememberedWifiDisplays.set(index, display);
+        } else {
+            mRememberedWifiDisplays.add(display);
+        }
+        setDirty();
+        return true;
+    }
+
+    public boolean forgetWifiDisplay(String deviceAddress) {
+        int index = findRememberedWifiDisplay(deviceAddress);
+        if (index >= 0) {
+            mRememberedWifiDisplays.remove(index);
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    private int findRememberedWifiDisplay(String deviceAddress) {
+        int count = mRememberedWifiDisplays.size();
+        for (int i = 0; i < count; i++) {
+            if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private void loadIfNeeded() {
+        if (!mLoaded) {
+            load();
+            mLoaded = true;
+        }
+    }
+
+    private void setDirty() {
+        mDirty = true;
+    }
+
+    private void clearState() {
+        mRememberedWifiDisplays.clear();
+    }
+
+    private void load() {
+        clearState();
+
+        final InputStream is;
+        try {
+            is = mAtomicFile.openRead();
+        } catch (FileNotFoundException ex) {
+            return;
+        }
+
+        XmlPullParser parser;
+        try {
+            parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(is), null);
+            loadFromXml(parser);
+        } catch (IOException ex) {
+            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
+            clearState();
+        } catch (XmlPullParserException ex) {
+            Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
+            clearState();
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+
+    private void save() {
+        final FileOutputStream os;
+        try {
+            os = mAtomicFile.startWrite();
+            boolean success = false;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+                saveToXml(serializer);
+                serializer.flush();
+                success = true;
+            } finally {
+                if (success) {
+                    mAtomicFile.finishWrite(os);
+                } else {
+                    mAtomicFile.failWrite(os);
+                }
+            }
+        } catch (IOException ex) {
+            Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
+        }
+    }
+
+    private void loadFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        XmlUtils.beginDocument(parser, "display-manager-state");
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("remembered-wifi-displays")) {
+                loadRememberedWifiDisplaysFromXml(parser);
+            }
+        }
+    }
+
+    private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("wifi-display")) {
+                String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
+                String deviceName = parser.getAttributeValue(null, "deviceName");
+                String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
+                if (deviceAddress == null || deviceName == null) {
+                    throw new XmlPullParserException(
+                            "Missing deviceAddress or deviceName attribute on wifi-display.");
+                }
+                if (findRememberedWifiDisplay(deviceAddress) >= 0) {
+                    throw new XmlPullParserException(
+                            "Found duplicate wifi display device address.");
+                }
+
+                mRememberedWifiDisplays.add(
+                        new WifiDisplay(deviceAddress, deviceName, deviceAlias,
+                                false, false, false));
+            }
+        }
+    }
+
+    private void saveToXml(XmlSerializer serializer) throws IOException {
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(null, "display-manager-state");
+        serializer.startTag(null, "remembered-wifi-displays");
+        for (WifiDisplay display : mRememberedWifiDisplays) {
+            serializer.startTag(null, "wifi-display");
+            serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
+            serializer.attribute(null, "deviceName", display.getDeviceName());
+            if (display.getDeviceAlias() != null) {
+                serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
+            }
+            serializer.endTag(null, "wifi-display");
+        }
+        serializer.endTag(null, "remembered-wifi-displays");
+        serializer.endTag(null, "display-manager-state");
+        serializer.endDocument();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
new file mode 100644
index 0000000..46d473c
--- /dev/null
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -0,0 +1,177 @@
+/*
+ * 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.display;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IBinder.DeathRecipient;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+/**
+ * A display adapter that provides virtual displays on behalf of applications.
+ * <p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class VirtualDisplayAdapter extends DisplayAdapter {
+    static final String TAG = "VirtualDisplayAdapter";
+    static final boolean DEBUG = false;
+
+    private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices =
+            new ArrayMap<IBinder, VirtualDisplayDevice>();
+
+    // Called with SyncRoot lock held.
+    public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener) {
+        super(syncRoot, context, handler, listener, TAG);
+    }
+
+    public DisplayDevice createVirtualDisplayLocked(IBinder appToken,
+            int ownerUid, String ownerPackageName,
+            String name, int width, int height, int densityDpi, Surface surface, int flags) {
+        boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
+        IBinder displayToken = SurfaceControl.createDisplay(name, secure);
+        VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
+                ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags);
+
+        try {
+            appToken.linkToDeath(device, 0);
+        } catch (RemoteException ex) {
+            device.destroyLocked();
+            return null;
+        }
+
+        mVirtualDisplayDevices.put(appToken, device);
+
+        // Return the display device without actually sending the event indicating
+        // that it was added.  The caller will handle it.
+        return device;
+    }
+
+    public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+        if (device != null) {
+            device.destroyLocked();
+            appToken.unlinkToDeath(device, 0);
+        }
+
+        // Return the display device that was removed without actually sending the
+        // event indicating that it was removed.  The caller will handle it.
+        return device;
+    }
+
+    private void handleBinderDiedLocked(IBinder appToken) {
+        VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken);
+        if (device != null) {
+            Slog.i(TAG, "Virtual display device released because application token died: "
+                    + device.mOwnerPackageName);
+            device.destroyLocked();
+            sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);
+        }
+    }
+
+    private final class VirtualDisplayDevice extends DisplayDevice
+            implements DeathRecipient {
+        private final IBinder mAppToken;
+        private final int mOwnerUid;
+        final String mOwnerPackageName;
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final int mDensityDpi;
+        private final int mFlags;
+
+        private Surface mSurface;
+        private DisplayDeviceInfo mInfo;
+
+        public VirtualDisplayDevice(IBinder displayToken,
+                IBinder appToken, int ownerUid, String ownerPackageName,
+                String name, int width, int height, int densityDpi, Surface surface, int flags) {
+            super(VirtualDisplayAdapter.this, displayToken);
+            mAppToken = appToken;
+            mOwnerUid = ownerUid;
+            mOwnerPackageName = ownerPackageName;
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mDensityDpi = densityDpi;
+            mSurface = surface;
+            mFlags = flags;
+        }
+
+        @Override
+        public void binderDied() {
+            synchronized (getSyncRoot()) {
+                if (mSurface != null) {
+                    handleBinderDiedLocked(mAppToken);
+                }
+            }
+        }
+
+        public void destroyLocked() {
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+        }
+
+        @Override
+        public void performTraversalInTransactionLocked() {
+            if (mSurface != null) {
+                setSurfaceInTransactionLocked(mSurface);
+            }
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = mName;
+                mInfo.width = mWidth;
+                mInfo.height = mHeight;
+                mInfo.refreshRate = 60;
+                mInfo.densityDpi = mDensityDpi;
+                mInfo.xDpi = mDensityDpi;
+                mInfo.yDpi = mDensityDpi;
+                mInfo.flags = 0;
+                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE |
+                            DisplayDeviceInfo.FLAG_NEVER_BLANK;
+                }
+                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE;
+                }
+                if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) {
+                    mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION;
+                }
+                mInfo.type = Display.TYPE_VIRTUAL;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+                mInfo.ownerUid = mOwnerUid;
+                mInfo.ownerPackageName = mOwnerPackageName;
+            }
+            return mInfo;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
new file mode 100644
index 0000000..cd57941
--- /dev/null
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.R;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplaySessionInfo;
+import android.hardware.display.WifiDisplayStatus;
+import android.media.RemoteDisplay;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.ArrayList;
+
+import libcore.util.Objects;
+
+/**
+ * Connects to Wifi displays that implement the Miracast protocol.
+ * <p>
+ * The Wifi display protocol relies on Wifi direct for discovering and pairing
+ * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
+ * a connection from the display.  After session negotiation, the Media Server
+ * streams encoded buffers to the display.
+ * </p><p>
+ * This class is responsible for connecting to Wifi displays and mediating
+ * the interactions between Media Server, Surface Flinger and the Display Manager Service.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class WifiDisplayAdapter extends DisplayAdapter {
+    private static final String TAG = "WifiDisplayAdapter";
+
+    private static final boolean DEBUG = false;
+
+    private static final int MSG_SEND_STATUS_CHANGE_BROADCAST = 1;
+    private static final int MSG_UPDATE_NOTIFICATION = 2;
+
+    private static final String ACTION_DISCONNECT = "android.server.display.wfd.DISCONNECT";
+
+    private final WifiDisplayHandler mHandler;
+    private final PersistentDataStore mPersistentDataStore;
+    private final boolean mSupportsProtectedBuffers;
+    private final NotificationManager mNotificationManager;
+
+    private PendingIntent mSettingsPendingIntent;
+    private PendingIntent mDisconnectPendingIntent;
+
+    private WifiDisplayController mDisplayController;
+    private WifiDisplayDevice mDisplayDevice;
+
+    private WifiDisplayStatus mCurrentStatus;
+    private int mFeatureState;
+    private int mScanState;
+    private int mActiveDisplayState;
+    private WifiDisplay mActiveDisplay;
+    private WifiDisplay[] mDisplays = WifiDisplay.EMPTY_ARRAY;
+    private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY;
+    private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY;
+    private WifiDisplaySessionInfo mSessionInfo;
+
+    private boolean mPendingStatusChangeBroadcast;
+    private boolean mPendingNotificationUpdate;
+
+    // Called with SyncRoot lock held.
+    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener,
+            PersistentDataStore persistentDataStore) {
+        super(syncRoot, context, handler, listener, TAG);
+        mHandler = new WifiDisplayHandler(handler.getLooper());
+        mPersistentDataStore = persistentDataStore;
+        mSupportsProtectedBuffers = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);
+        mNotificationManager = (NotificationManager)context.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+    }
+
+    @Override
+    public void dumpLocked(PrintWriter pw) {
+        super.dumpLocked(pw);
+
+        pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
+        pw.println("mFeatureState=" + mFeatureState);
+        pw.println("mScanState=" + mScanState);
+        pw.println("mActiveDisplayState=" + mActiveDisplayState);
+        pw.println("mActiveDisplay=" + mActiveDisplay);
+        pw.println("mDisplays=" + Arrays.toString(mDisplays));
+        pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays));
+        pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays));
+        pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast);
+        pw.println("mPendingNotificationUpdate=" + mPendingNotificationUpdate);
+        pw.println("mSupportsProtectedBuffers=" + mSupportsProtectedBuffers);
+ 
+        // Try to dump the controller state.
+        if (mDisplayController == null) {
+            pw.println("mDisplayController=null");
+        } else {
+            pw.println("mDisplayController:");
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+            ipw.increaseIndent();
+            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
+        }
+    }
+
+    @Override
+    public void registerLocked() {
+        super.registerLocked();
+
+        updateRememberedDisplaysLocked();
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                mDisplayController = new WifiDisplayController(
+                        getContext(), getHandler(), mWifiDisplayListener);
+
+                getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
+                        new IntentFilter(ACTION_DISCONNECT), null, mHandler);
+            }
+        });
+    }
+
+    public void requestStartScanLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "requestStartScanLocked");
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestStartScan();
+                }
+            }
+        });
+    }
+
+    public void requestStopScanLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "requestStopScanLocked");
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestStopScan();
+                }
+            }
+        });
+    }
+
+    public void requestConnectLocked(final String address) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestConnectLocked: address=" + address);
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestConnect(address);
+                }
+            }
+        });
+    }
+
+    public void requestPauseLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "requestPauseLocked");
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestPause();
+                }
+            }
+        });
+      }
+
+    public void requestResumeLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "requestResumeLocked");
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestResume();
+                }
+            }
+        });
+    }
+
+    public void requestDisconnectLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "requestDisconnectedLocked");
+        }
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                if (mDisplayController != null) {
+                    mDisplayController.requestDisconnect();
+                }
+            }
+        });
+    }
+
+    public void requestRenameLocked(String address, String alias) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestRenameLocked: address=" + address + ", alias=" + alias);
+        }
+
+        if (alias != null) {
+            alias = alias.trim();
+            if (alias.isEmpty() || alias.equals(address)) {
+                alias = null;
+            }
+        }
+
+        WifiDisplay display = mPersistentDataStore.getRememberedWifiDisplay(address);
+        if (display != null && !Objects.equal(display.getDeviceAlias(), alias)) {
+            display = new WifiDisplay(address, display.getDeviceName(), alias,
+                    false, false, false);
+            if (mPersistentDataStore.rememberWifiDisplay(display)) {
+                mPersistentDataStore.saveIfNeeded();
+                updateRememberedDisplaysLocked();
+                scheduleStatusChangedBroadcastLocked();
+            }
+        }
+
+        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
+            renameDisplayDeviceLocked(mActiveDisplay.getFriendlyDisplayName());
+        }
+    }
+
+    public void requestForgetLocked(String address) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestForgetLocked: address=" + address);
+        }
+
+        if (mPersistentDataStore.forgetWifiDisplay(address)) {
+            mPersistentDataStore.saveIfNeeded();
+            updateRememberedDisplaysLocked();
+            scheduleStatusChangedBroadcastLocked();
+        }
+
+        if (mActiveDisplay != null && mActiveDisplay.getDeviceAddress().equals(address)) {
+            requestDisconnectLocked();
+        }
+    }
+
+    public WifiDisplayStatus getWifiDisplayStatusLocked() {
+        if (mCurrentStatus == null) {
+            mCurrentStatus = new WifiDisplayStatus(
+                    mFeatureState, mScanState, mActiveDisplayState,
+                    mActiveDisplay, mDisplays, mSessionInfo);
+        }
+
+        if (DEBUG) {
+            Slog.d(TAG, "getWifiDisplayStatusLocked: result=" + mCurrentStatus);
+        }
+        return mCurrentStatus;
+    }
+
+    private void updateDisplaysLocked() {
+        List<WifiDisplay> displays = new ArrayList<WifiDisplay>(
+                mAvailableDisplays.length + mRememberedDisplays.length);
+        boolean[] remembered = new boolean[mAvailableDisplays.length];
+        for (WifiDisplay d : mRememberedDisplays) {
+            boolean available = false;
+            for (int i = 0; i < mAvailableDisplays.length; i++) {
+                if (d.equals(mAvailableDisplays[i])) {
+                    remembered[i] = available = true;
+                    break;
+                }
+            }
+            if (!available) {
+                displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
+                        d.getDeviceAlias(), false, false, true));
+            }
+        }
+        for (int i = 0; i < mAvailableDisplays.length; i++) {
+            WifiDisplay d = mAvailableDisplays[i];
+            displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(),
+                    d.getDeviceAlias(), true, d.canConnect(), remembered[i]));
+        }
+        mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY);
+    }
+
+    private void updateRememberedDisplaysLocked() {
+        mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays();
+        mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay);
+        mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays);
+        updateDisplaysLocked();
+    }
+
+    private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() {
+        // It may happen that a display name has changed since it was remembered.
+        // Consult the list of available displays and update the name if needed.
+        // We don't do anything special for the active display here.  The display
+        // controller will send a separate event when it needs to be updates.
+        boolean changed = false;
+        for (int i = 0; i < mRememberedDisplays.length; i++) {
+            WifiDisplay rememberedDisplay = mRememberedDisplays[i];
+            WifiDisplay availableDisplay = findAvailableDisplayLocked(
+                    rememberedDisplay.getDeviceAddress());
+            if (availableDisplay != null && !rememberedDisplay.equals(availableDisplay)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "fixRememberedDisplayNamesFromAvailableDisplaysLocked: "
+                            + "updating remembered display to " + availableDisplay);
+                }
+                mRememberedDisplays[i] = availableDisplay;
+                changed |= mPersistentDataStore.rememberWifiDisplay(availableDisplay);
+            }
+        }
+        if (changed) {
+            mPersistentDataStore.saveIfNeeded();
+        }
+    }
+
+    private WifiDisplay findAvailableDisplayLocked(String address) {
+        for (WifiDisplay display : mAvailableDisplays) {
+            if (display.getDeviceAddress().equals(address)) {
+                return display;
+            }
+        }
+        return null;
+    }
+
+    private void addDisplayDeviceLocked(WifiDisplay display,
+            Surface surface, int width, int height, int flags) {
+        removeDisplayDeviceLocked();
+
+        if (mPersistentDataStore.rememberWifiDisplay(display)) {
+            mPersistentDataStore.saveIfNeeded();
+            updateRememberedDisplaysLocked();
+            scheduleStatusChangedBroadcastLocked();
+        }
+
+        boolean secure = (flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0;
+        int deviceFlags = DisplayDeviceInfo.FLAG_PRESENTATION;
+        if (secure) {
+            deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
+            if (mSupportsProtectedBuffers) {
+                deviceFlags |= DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
+            }
+        }
+
+        float refreshRate = 60.0f; // TODO: get this for real
+
+        String name = display.getFriendlyDisplayName();
+        String address = display.getDeviceAddress();
+        IBinder displayToken = SurfaceControl.createDisplay(name, secure);
+        mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
+                refreshRate, deviceFlags, address, surface);
+        sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
+    }
+
+    private void removeDisplayDeviceLocked() {
+        if (mDisplayDevice != null) {
+            mDisplayDevice.destroyLocked();
+            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+            mDisplayDevice = null;
+        }
+    }
+
+    private void renameDisplayDeviceLocked(String name) {
+        if (mDisplayDevice != null && !mDisplayDevice.getNameLocked().equals(name)) {
+            mDisplayDevice.setNameLocked(name);
+            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_CHANGED);
+        }
+    }
+
+    private void scheduleStatusChangedBroadcastLocked() {
+        mCurrentStatus = null;
+        if (!mPendingStatusChangeBroadcast) {
+            mPendingStatusChangeBroadcast = true;
+            mHandler.sendEmptyMessage(MSG_SEND_STATUS_CHANGE_BROADCAST);
+        }
+    }
+
+    private void scheduleUpdateNotificationLocked() {
+        if (!mPendingNotificationUpdate) {
+            mPendingNotificationUpdate = true;
+            mHandler.sendEmptyMessage(MSG_UPDATE_NOTIFICATION);
+        }
+    }
+
+    // Runs on the handler.
+    private void handleSendStatusChangeBroadcast() {
+        final Intent intent;
+        synchronized (getSyncRoot()) {
+            if (!mPendingStatusChangeBroadcast) {
+                return;
+            }
+
+            mPendingStatusChangeBroadcast = false;
+            intent = new Intent(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            intent.putExtra(DisplayManager.EXTRA_WIFI_DISPLAY_STATUS,
+                    getWifiDisplayStatusLocked());
+        }
+
+        // Send protected broadcast about wifi display status to registered receivers.
+        getContext().sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    // Runs on the handler.
+    private void handleUpdateNotification() {
+        final int state;
+        final WifiDisplay display;
+        synchronized (getSyncRoot()) {
+            if (!mPendingNotificationUpdate) {
+                return;
+            }
+
+            mPendingNotificationUpdate = false;
+            state = mActiveDisplayState;
+            display = mActiveDisplay;
+        }
+
+        // Cancel the old notification if there is one.
+        mNotificationManager.cancelAsUser(null,
+                R.string.wifi_display_notification_disconnect, UserHandle.ALL);
+
+        if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING
+                || state == WifiDisplayStatus.DISPLAY_STATE_CONNECTED) {
+            Context context = getContext();
+
+            // Initialize pending intents for the notification outside of the lock because
+            // creating a pending intent requires a call into the activity manager.
+            if (mSettingsPendingIntent == null) {
+                Intent settingsIntent = new Intent(Settings.ACTION_WIFI_DISPLAY_SETTINGS);
+                settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                mSettingsPendingIntent = PendingIntent.getActivityAsUser(
+                        context, 0, settingsIntent, 0, null, UserHandle.CURRENT);
+            }
+
+            if (mDisconnectPendingIntent == null) {
+                Intent disconnectIntent = new Intent(ACTION_DISCONNECT);
+                mDisconnectPendingIntent = PendingIntent.getBroadcastAsUser(
+                        context, 0, disconnectIntent, 0, UserHandle.CURRENT);
+            }
+
+            // Post the notification.
+            Resources r = context.getResources();
+            Notification notification;
+            if (state == WifiDisplayStatus.DISPLAY_STATE_CONNECTING) {
+                notification = new Notification.Builder(context)
+                        .setContentTitle(r.getString(
+                                R.string.wifi_display_notification_connecting_title))
+                        .setContentText(r.getString(
+                                R.string.wifi_display_notification_connecting_message,
+                                display.getFriendlyDisplayName()))
+                        .setContentIntent(mSettingsPendingIntent)
+                        .setSmallIcon(R.drawable.ic_notification_cast_connecting)
+                        .setOngoing(true)
+                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+                                r.getString(R.string.wifi_display_notification_disconnect),
+                                mDisconnectPendingIntent)
+                        .build();
+            } else {
+                notification = new Notification.Builder(context)
+                        .setContentTitle(r.getString(
+                                R.string.wifi_display_notification_connected_title))
+                        .setContentText(r.getString(
+                                R.string.wifi_display_notification_connected_message,
+                                display.getFriendlyDisplayName()))
+                        .setContentIntent(mSettingsPendingIntent)
+                        .setSmallIcon(R.drawable.ic_notification_cast_on)
+                        .setOngoing(true)
+                        .addAction(android.R.drawable.ic_menu_close_clear_cancel,
+                                r.getString(R.string.wifi_display_notification_disconnect),
+                                mDisconnectPendingIntent)
+                        .build();
+            }
+            mNotificationManager.notifyAsUser(null,
+                    R.string.wifi_display_notification_disconnect,
+                    notification, UserHandle.ALL);
+        }
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ACTION_DISCONNECT)) {
+                synchronized (getSyncRoot()) {
+                    requestDisconnectLocked();
+                }
+            }
+        }
+    };
+
+    private final WifiDisplayController.Listener mWifiDisplayListener =
+            new WifiDisplayController.Listener() {
+        @Override
+        public void onFeatureStateChanged(int featureState) {
+            synchronized (getSyncRoot()) {
+                if (mFeatureState != featureState) {
+                    mFeatureState = featureState;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onScanStarted() {
+            synchronized (getSyncRoot()) {
+                if (mScanState != WifiDisplayStatus.SCAN_STATE_SCANNING) {
+                    mScanState = WifiDisplayStatus.SCAN_STATE_SCANNING;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onScanResults(WifiDisplay[] availableDisplays) {
+            synchronized (getSyncRoot()) {
+                availableDisplays = mPersistentDataStore.applyWifiDisplayAliases(
+                        availableDisplays);
+
+                boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays);
+
+                // Check whether any of the available displays changed canConnect status.
+                for (int i = 0; !changed && i<availableDisplays.length; i++) {
+                    changed = availableDisplays[i].canConnect()
+                            != mAvailableDisplays[i].canConnect();
+                }
+
+                if (changed) {
+                    mAvailableDisplays = availableDisplays;
+                    fixRememberedDisplayNamesFromAvailableDisplaysLocked();
+                    updateDisplaysLocked();
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onScanFinished() {
+            synchronized (getSyncRoot()) {
+                if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING) {
+                    mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING;
+                    scheduleStatusChangedBroadcastLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnecting(WifiDisplay display) {
+            synchronized (getSyncRoot()) {
+                display = mPersistentDataStore.applyWifiDisplayAlias(display);
+
+                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTING
+                        || mActiveDisplay == null
+                        || !mActiveDisplay.equals(display)) {
+                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTING;
+                    mActiveDisplay = display;
+                    scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnectionFailed() {
+            synchronized (getSyncRoot()) {
+                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
+                        || mActiveDisplay != null) {
+                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
+                    mActiveDisplay = null;
+                    scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayConnected(WifiDisplay display, Surface surface,
+                int width, int height, int flags) {
+            synchronized (getSyncRoot()) {
+                display = mPersistentDataStore.applyWifiDisplayAlias(display);
+                addDisplayDeviceLocked(display, surface, width, height, flags);
+
+                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
+                        || mActiveDisplay == null
+                        || !mActiveDisplay.equals(display)) {
+                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_CONNECTED;
+                    mActiveDisplay = display;
+                    scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) {
+            synchronized (getSyncRoot()) {
+                mSessionInfo = sessionInfo;
+                scheduleStatusChangedBroadcastLocked();
+            }
+        }
+
+        @Override
+        public void onDisplayChanged(WifiDisplay display) {
+            synchronized (getSyncRoot()) {
+                display = mPersistentDataStore.applyWifiDisplayAlias(display);
+                if (mActiveDisplay != null
+                        && mActiveDisplay.hasSameAddress(display)
+                        && !mActiveDisplay.equals(display)) {
+                    mActiveDisplay = display;
+                    renameDisplayDeviceLocked(display.getFriendlyDisplayName());
+                    scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
+                }
+            }
+        }
+
+        @Override
+        public void onDisplayDisconnected() {
+            // Stop listening.
+            synchronized (getSyncRoot()) {
+                removeDisplayDeviceLocked();
+
+                if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED
+                        || mActiveDisplay != null) {
+                    mActiveDisplayState = WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED;
+                    mActiveDisplay = null;
+                    scheduleStatusChangedBroadcastLocked();
+                    scheduleUpdateNotificationLocked();
+                }
+            }
+        }
+    };
+
+    private final class WifiDisplayDevice extends DisplayDevice {
+        private String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final float mRefreshRate;
+        private final int mFlags;
+        private final String mAddress;
+
+        private Surface mSurface;
+        private DisplayDeviceInfo mInfo;
+
+        public WifiDisplayDevice(IBinder displayToken, String name,
+                int width, int height, float refreshRate, int flags, String address,
+                Surface surface) {
+            super(WifiDisplayAdapter.this, displayToken);
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mRefreshRate = refreshRate;
+            mFlags = flags;
+            mAddress = address;
+            mSurface = surface;
+        }
+
+        public void destroyLocked() {
+            if (mSurface != null) {
+                mSurface.release();
+                mSurface = null;
+            }
+            SurfaceControl.destroyDisplay(getDisplayTokenLocked());
+        }
+
+        public void setNameLocked(String name) {
+            mName = name;
+            mInfo = null;
+        }
+
+        @Override
+        public void performTraversalInTransactionLocked() {
+            if (mSurface != null) {
+                setSurfaceInTransactionLocked(mSurface);
+            }
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = mName;
+                mInfo.width = mWidth;
+                mInfo.height = mHeight;
+                mInfo.refreshRate = mRefreshRate;
+                mInfo.flags = mFlags;
+                mInfo.type = Display.TYPE_WIFI;
+                mInfo.address = mAddress;
+                mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
+            }
+            return mInfo;
+        }
+    }
+
+    private final class WifiDisplayHandler extends Handler {
+        public WifiDisplayHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SEND_STATUS_CHANGE_BROADCAST:
+                    handleSendStatusChangeBroadcast();
+                    break;
+
+                case MSG_UPDATE_NOTIFICATION:
+                    handleUpdateNotification();
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/display/WifiDisplayController.java b/services/core/java/com/android/server/display/WifiDisplayController.java
new file mode 100644
index 0000000..dbb59b2
--- /dev/null
+++ b/services/core/java/com/android/server/display/WifiDisplayController.java
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.hardware.display.WifiDisplay;
+import android.hardware.display.WifiDisplaySessionInfo;
+import android.hardware.display.WifiDisplayStatus;
+import android.media.RemoteDisplay;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.net.wifi.WpsInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.WifiP2pManager.ActionListener;
+import android.net.wifi.p2p.WifiP2pManager.Channel;
+import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
+import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.Surface;
+
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+import libcore.util.Objects;
+
+/**
+ * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
+ * on behalf of {@link WifiDisplayAdapter}.
+ * <p>
+ * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
+ * accidentally introducing any deadlocks due to the display manager calling
+ * outside of itself while holding its lock.  It's also way easier to write this
+ * asynchronous code if we can assume that it is single-threaded.
+ * </p><p>
+ * The controller must be instantiated on the handler thread.
+ * </p>
+ */
+final class WifiDisplayController implements DumpUtils.Dump {
+    private static final String TAG = "WifiDisplayController";
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_CONTROL_PORT = 7236;
+    private static final int MAX_THROUGHPUT = 50;
+    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+    private static final int RTSP_TIMEOUT_SECONDS = 30;
+    private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120;
+
+    // We repeatedly issue calls to discover peers every so often for a few reasons.
+    // 1. The initial request may fail and need to retried.
+    // 2. Discovery will self-abort after any group is initiated, which may not necessarily
+    //    be what we want to have happen.
+    // 3. Discovery will self-timeout after 2 minutes, whereas we want discovery to
+    //    be occur for as long as a client is requesting it be.
+    // 4. We don't seem to get updated results for displays we've already found until
+    //    we ask to discover again, particularly for the isSessionAvailable() property.
+    private static final int DISCOVER_PEERS_INTERVAL_MILLIS = 10000;
+
+    private static final int CONNECT_MAX_RETRIES = 3;
+    private static final int CONNECT_RETRY_DELAY_MILLIS = 500;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Listener mListener;
+
+    private final WifiP2pManager mWifiP2pManager;
+    private final Channel mWifiP2pChannel;
+
+    private boolean mWifiP2pEnabled;
+    private boolean mWfdEnabled;
+    private boolean mWfdEnabling;
+    private NetworkInfo mNetworkInfo;
+
+    private final ArrayList<WifiP2pDevice> mAvailableWifiDisplayPeers =
+            new ArrayList<WifiP2pDevice>();
+
+    // True if Wifi display is enabled by the user.
+    private boolean mWifiDisplayOnSetting;
+
+    // True if a scan was requested independent of whether one is actually in progress.
+    private boolean mScanRequested;
+
+    // True if there is a call to discoverPeers in progress.
+    private boolean mDiscoverPeersInProgress;
+
+    // The device to which we want to connect, or null if we want to be disconnected.
+    private WifiP2pDevice mDesiredDevice;
+
+    // The device to which we are currently connecting, or null if we have already connected
+    // or are not trying to connect.
+    private WifiP2pDevice mConnectingDevice;
+
+    // The device from which we are currently disconnecting.
+    private WifiP2pDevice mDisconnectingDevice;
+
+    // The device to which we were previously trying to connect and are now canceling.
+    private WifiP2pDevice mCancelingDevice;
+
+    // The device to which we are currently connected, which means we have an active P2P group.
+    private WifiP2pDevice mConnectedDevice;
+
+    // The group info obtained after connecting.
+    private WifiP2pGroup mConnectedDeviceGroupInfo;
+
+    // Number of connection retries remaining.
+    private int mConnectionRetriesLeft;
+
+    // The remote display that is listening on the connection.
+    // Created after the Wifi P2P network is connected.
+    private RemoteDisplay mRemoteDisplay;
+
+    // The remote display interface.
+    private String mRemoteDisplayInterface;
+
+    // True if RTSP has connected.
+    private boolean mRemoteDisplayConnected;
+
+    // The information we have most recently told WifiDisplayAdapter about.
+    private WifiDisplay mAdvertisedDisplay;
+    private Surface mAdvertisedDisplaySurface;
+    private int mAdvertisedDisplayWidth;
+    private int mAdvertisedDisplayHeight;
+    private int mAdvertisedDisplayFlags;
+
+    // Certification
+    private boolean mWifiDisplayCertMode;
+    private int mWifiDisplayWpsConfig = WpsInfo.INVALID;
+
+    private WifiP2pDevice mThisDevice;
+
+    public WifiDisplayController(Context context, Handler handler, Listener listener) {
+        mContext = context;
+        mHandler = handler;
+        mListener = listener;
+
+        mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
+        mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
+        context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler);
+
+        ContentObserver settingsObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                updateSettings();
+            }
+        };
+
+        final ContentResolver resolver = mContext.getContentResolver();
+        resolver.registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver);
+        resolver.registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver);
+        resolver.registerContentObserver(Settings.Global.getUriFor(
+                Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver);
+        updateSettings();
+    }
+
+    private void updateSettings() {
+        final ContentResolver resolver = mContext.getContentResolver();
+        mWifiDisplayOnSetting = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DISPLAY_ON, 0) != 0;
+        mWifiDisplayCertMode = Settings.Global.getInt(resolver,
+                Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0;
+
+        mWifiDisplayWpsConfig = WpsInfo.INVALID;
+        if (mWifiDisplayCertMode) {
+            mWifiDisplayWpsConfig = Settings.Global.getInt(resolver,
+                  Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID);
+        }
+
+        updateWfdEnableState();
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("mWifiDisplayOnSetting=" + mWifiDisplayOnSetting);
+        pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
+        pw.println("mWfdEnabled=" + mWfdEnabled);
+        pw.println("mWfdEnabling=" + mWfdEnabling);
+        pw.println("mNetworkInfo=" + mNetworkInfo);
+        pw.println("mScanRequested=" + mScanRequested);
+        pw.println("mDiscoverPeersInProgress=" + mDiscoverPeersInProgress);
+        pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
+        pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
+        pw.println("mDisconnectingDisplay=" + describeWifiP2pDevice(mDisconnectingDevice));
+        pw.println("mCancelingDisplay=" + describeWifiP2pDevice(mCancelingDevice));
+        pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
+        pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
+        pw.println("mRemoteDisplay=" + mRemoteDisplay);
+        pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
+        pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
+        pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay);
+        pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface);
+        pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth);
+        pw.println("mAdvertisedDisplayHeight=" + mAdvertisedDisplayHeight);
+        pw.println("mAdvertisedDisplayFlags=" + mAdvertisedDisplayFlags);
+
+        pw.println("mAvailableWifiDisplayPeers: size=" + mAvailableWifiDisplayPeers.size());
+        for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
+            pw.println("  " + describeWifiP2pDevice(device));
+        }
+    }
+
+    public void requestStartScan() {
+        if (!mScanRequested) {
+            mScanRequested = true;
+            updateScanState();
+        }
+    }
+
+    public void requestStopScan() {
+        if (mScanRequested) {
+            mScanRequested = false;
+            updateScanState();
+        }
+    }
+
+    public void requestConnect(String address) {
+        for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
+            if (device.deviceAddress.equals(address)) {
+                connect(device);
+            }
+        }
+    }
+
+    public void requestPause() {
+        if (mRemoteDisplay != null) {
+            mRemoteDisplay.pause();
+        }
+    }
+
+    public void requestResume() {
+        if (mRemoteDisplay != null) {
+            mRemoteDisplay.resume();
+        }
+    }
+
+    public void requestDisconnect() {
+        disconnect();
+    }
+
+    private void updateWfdEnableState() {
+        if (mWifiDisplayOnSetting && mWifiP2pEnabled) {
+            // WFD should be enabled.
+            if (!mWfdEnabled && !mWfdEnabling) {
+                mWfdEnabling = true;
+
+                WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+                wfdInfo.setWfdEnabled(true);
+                wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+                wfdInfo.setSessionAvailable(true);
+                wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
+                wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
+                mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+                    @Override
+                    public void onSuccess() {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Successfully set WFD info.");
+                        }
+                        if (mWfdEnabling) {
+                            mWfdEnabling = false;
+                            mWfdEnabled = true;
+                            reportFeatureState();
+                            updateScanState();
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(int reason) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+                        }
+                        mWfdEnabling = false;
+                    }
+                });
+            }
+        } else {
+            // WFD should be disabled.
+            if (mWfdEnabled || mWfdEnabling) {
+                WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+                wfdInfo.setWfdEnabled(false);
+                mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+                    @Override
+                    public void onSuccess() {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Successfully set WFD info.");
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(int reason) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+                        }
+                    }
+                });
+            }
+            mWfdEnabling = false;
+            mWfdEnabled = false;
+            reportFeatureState();
+            updateScanState();
+            disconnect();
+        }
+    }
+
+    private void reportFeatureState() {
+        final int featureState = computeFeatureState();
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onFeatureStateChanged(featureState);
+            }
+        });
+    }
+
+    private int computeFeatureState() {
+        if (!mWifiP2pEnabled) {
+            return WifiDisplayStatus.FEATURE_STATE_DISABLED;
+        }
+        return mWifiDisplayOnSetting ? WifiDisplayStatus.FEATURE_STATE_ON :
+                WifiDisplayStatus.FEATURE_STATE_OFF;
+    }
+
+    private void updateScanState() {
+        if (mScanRequested && mWfdEnabled && mDesiredDevice == null) {
+            if (!mDiscoverPeersInProgress) {
+                Slog.i(TAG, "Starting Wifi display scan.");
+                mDiscoverPeersInProgress = true;
+                handleScanStarted();
+                tryDiscoverPeers();
+            }
+        } else {
+            if (mDiscoverPeersInProgress) {
+                // Cancel automatic retry right away.
+                mHandler.removeCallbacks(mDiscoverPeers);
+
+                // Defer actually stopping discovery if we have a connection attempt in progress.
+                // The wifi display connection attempt often fails if we are not in discovery
+                // mode.  So we allow discovery to continue until we give up trying to connect.
+                if (mDesiredDevice == null || mDesiredDevice == mConnectedDevice) {
+                    Slog.i(TAG, "Stopping Wifi display scan.");
+                    mDiscoverPeersInProgress = false;
+                    stopPeerDiscovery();
+                    handleScanFinished();
+                }
+            }
+        }
+    }
+
+    private void tryDiscoverPeers() {
+        mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
+            @Override
+            public void onSuccess() {
+                if (DEBUG) {
+                    Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
+                }
+
+                if (mDiscoverPeersInProgress) {
+                    requestPeers();
+                }
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
+                }
+
+                // Ignore the error.
+                // We will retry automatically in a little bit.
+            }
+        });
+
+        // Retry discover peers periodically until stopped.
+        mHandler.postDelayed(mDiscoverPeers, DISCOVER_PEERS_INTERVAL_MILLIS);
+    }
+
+    private void stopPeerDiscovery() {
+        mWifiP2pManager.stopPeerDiscovery(mWifiP2pChannel, new ActionListener() {
+            @Override
+            public void onSuccess() {
+                if (DEBUG) {
+                    Slog.d(TAG, "Stop peer discovery succeeded.");
+                }
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Stop peer discovery failed with reason " + reason + ".");
+                }
+            }
+        });
+    }
+
+    private void requestPeers() {
+        mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
+            @Override
+            public void onPeersAvailable(WifiP2pDeviceList peers) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Received list of peers.");
+                }
+
+                mAvailableWifiDisplayPeers.clear();
+                for (WifiP2pDevice device : peers.getDeviceList()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "  " + describeWifiP2pDevice(device));
+                    }
+
+                    if (isWifiDisplay(device)) {
+                        mAvailableWifiDisplayPeers.add(device);
+                    }
+                }
+
+                if (mDiscoverPeersInProgress) {
+                    handleScanResults();
+                }
+            }
+        });
+    }
+
+    private void handleScanStarted() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanStarted();
+            }
+        });
+    }
+
+    private void handleScanResults() {
+        final int count = mAvailableWifiDisplayPeers.size();
+        final WifiDisplay[] displays = WifiDisplay.CREATOR.newArray(count);
+        for (int i = 0; i < count; i++) {
+            WifiP2pDevice device = mAvailableWifiDisplayPeers.get(i);
+            displays[i] = createWifiDisplay(device);
+            updateDesiredDevice(device);
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanResults(displays);
+            }
+        });
+    }
+
+    private void handleScanFinished() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onScanFinished();
+            }
+        });
+    }
+
+    private void updateDesiredDevice(WifiP2pDevice device) {
+        // Handle the case where the device to which we are connecting or connected
+        // may have been renamed or reported different properties in the latest scan.
+        final String address = device.deviceAddress;
+        if (mDesiredDevice != null && mDesiredDevice.deviceAddress.equals(address)) {
+            if (DEBUG) {
+                Slog.d(TAG, "updateDesiredDevice: new information "
+                        + describeWifiP2pDevice(device));
+            }
+            mDesiredDevice.update(device);
+            if (mAdvertisedDisplay != null
+                    && mAdvertisedDisplay.getDeviceAddress().equals(address)) {
+                readvertiseDisplay(createWifiDisplay(mDesiredDevice));
+            }
+        }
+    }
+
+    private void connect(final WifiP2pDevice device) {
+        if (mDesiredDevice != null
+                && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
+            if (DEBUG) {
+                Slog.d(TAG, "connect: nothing to do, already connecting to "
+                        + describeWifiP2pDevice(device));
+            }
+            return;
+        }
+
+        if (mConnectedDevice != null
+                && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
+                && mDesiredDevice == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "connect: nothing to do, already connected to "
+                        + describeWifiP2pDevice(device) + " and not part way through "
+                        + "connecting to a different device.");
+            }
+            return;
+        }
+
+        if (!mWfdEnabled) {
+            Slog.i(TAG, "Ignoring request to connect to Wifi display because the "
+                    +" feature is currently disabled: " + device.deviceName);
+            return;
+        }
+
+        mDesiredDevice = device;
+        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
+        updateConnection();
+    }
+
+    private void disconnect() {
+        mDesiredDevice = null;
+        updateConnection();
+    }
+
+    private void retryConnection() {
+        // Cheap hack.  Make a new instance of the device object so that we
+        // can distinguish it from the previous connection attempt.
+        // This will cause us to tear everything down before we try again.
+        mDesiredDevice = new WifiP2pDevice(mDesiredDevice);
+        updateConnection();
+    }
+
+    /**
+     * This function is called repeatedly after each asynchronous operation
+     * until all preconditions for the connection have been satisfied and the
+     * connection is established (or not).
+     */
+    private void updateConnection() {
+        // Step 0. Stop scans if necessary to prevent interference while connected.
+        // Resume scans later when no longer attempting to connect.
+        updateScanState();
+
+        // Step 1. Before we try to connect to a new device, tell the system we
+        // have disconnected from the old one.
+        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
+            Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
+                    + " from Wifi display: " + mConnectedDevice.deviceName);
+
+            mRemoteDisplay.dispose();
+            mRemoteDisplay = null;
+            mRemoteDisplayInterface = null;
+            mRemoteDisplayConnected = false;
+            mHandler.removeCallbacks(mRtspTimeout);
+
+            mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED);
+            unadvertiseDisplay();
+
+            // continue to next step
+        }
+
+        // Step 2. Before we try to connect to a new device, disconnect from the old one.
+        if (mDisconnectingDevice != null) {
+            return; // wait for asynchronous callback
+        }
+        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
+            Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
+            mDisconnectingDevice = mConnectedDevice;
+            mConnectedDevice = null;
+            mConnectedDeviceGroupInfo = null;
+
+            unadvertiseDisplay();
+
+            final WifiP2pDevice oldDevice = mDisconnectingDevice;
+            mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
+                    next();
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Slog.i(TAG, "Failed to disconnect from Wifi display: "
+                            + oldDevice.deviceName + ", reason=" + reason);
+                    next();
+                }
+
+                private void next() {
+                    if (mDisconnectingDevice == oldDevice) {
+                        mDisconnectingDevice = null;
+                        updateConnection();
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 3. Before we try to connect to a new device, stop trying to connect
+        // to the old one.
+        if (mCancelingDevice != null) {
+            return; // wait for asynchronous callback
+        }
+        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
+            Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
+            mCancelingDevice = mConnectingDevice;
+            mConnectingDevice = null;
+
+            unadvertiseDisplay();
+            mHandler.removeCallbacks(mConnectionTimeout);
+
+            final WifiP2pDevice oldDevice = mCancelingDevice;
+            mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
+                    next();
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Slog.i(TAG, "Failed to cancel connection to Wifi display: "
+                            + oldDevice.deviceName + ", reason=" + reason);
+                    next();
+                }
+
+                private void next() {
+                    if (mCancelingDevice == oldDevice) {
+                        mCancelingDevice = null;
+                        updateConnection();
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 4. If we wanted to disconnect, or we're updating after starting an
+        // autonomous GO, then mission accomplished.
+        if (mDesiredDevice == null) {
+            if (mWifiDisplayCertMode) {
+                mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0));
+            }
+            unadvertiseDisplay();
+            return; // done
+        }
+
+        // Step 5. Try to connect.
+        if (mConnectedDevice == null && mConnectingDevice == null) {
+            Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
+
+            mConnectingDevice = mDesiredDevice;
+            WifiP2pConfig config = new WifiP2pConfig();
+            WpsInfo wps = new WpsInfo();
+            if (mWifiDisplayWpsConfig != WpsInfo.INVALID) {
+                wps.setup = mWifiDisplayWpsConfig;
+            } else if (mConnectingDevice.wpsPbcSupported()) {
+                wps.setup = WpsInfo.PBC;
+            } else if (mConnectingDevice.wpsDisplaySupported()) {
+                // We do keypad if peer does display
+                wps.setup = WpsInfo.KEYPAD;
+            } else {
+                wps.setup = WpsInfo.DISPLAY;
+            }
+            config.wps = wps;
+            config.deviceAddress = mConnectingDevice.deviceAddress;
+            // Helps with STA & P2P concurrency
+            config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;
+
+            WifiDisplay display = createWifiDisplay(mConnectingDevice);
+            advertiseDisplay(display, null, 0, 0, 0);
+
+            final WifiP2pDevice newDevice = mDesiredDevice;
+            mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    // The connection may not yet be established.  We still need to wait
+                    // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
+                    // get that broadcast, so we register a timeout.
+                    Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
+
+                    mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    if (mConnectingDevice == newDevice) {
+                        Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+                                + newDevice.deviceName + ", reason=" + reason);
+                        mConnectingDevice = null;
+                        handleConnectionFailure(false);
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 6. Listen for incoming RTSP connection.
+        if (mConnectedDevice != null && mRemoteDisplay == null) {
+            Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
+            if (addr == null) {
+                Slog.i(TAG, "Failed to get local interface address for communicating "
+                        + "with Wifi display: " + mConnectedDevice.deviceName);
+                handleConnectionFailure(false);
+                return; // done
+            }
+
+            mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE);
+
+            final WifiP2pDevice oldDevice = mConnectedDevice;
+            final int port = getPortNumber(mConnectedDevice);
+            final String iface = addr.getHostAddress() + ":" + port;
+            mRemoteDisplayInterface = iface;
+
+            Slog.i(TAG, "Listening for RTSP connection on " + iface
+                    + " from Wifi display: " + mConnectedDevice.deviceName);
+
+            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
+                @Override
+                public void onDisplayConnected(Surface surface,
+                        int width, int height, int flags, int session) {
+                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
+                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
+                                + mConnectedDevice.deviceName);
+                        mRemoteDisplayConnected = true;
+                        mHandler.removeCallbacks(mRtspTimeout);
+
+                        if (mWifiDisplayCertMode) {
+                            mListener.onDisplaySessionInfo(
+                                    getSessionInfo(mConnectedDeviceGroupInfo, session));
+                        }
+
+                        final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+                        advertiseDisplay(display, surface, width, height, flags);
+                    }
+                }
+
+                @Override
+                public void onDisplayDisconnected() {
+                    if (mConnectedDevice == oldDevice) {
+                        Slog.i(TAG, "Closed RTSP connection with Wifi display: "
+                                + mConnectedDevice.deviceName);
+                        mHandler.removeCallbacks(mRtspTimeout);
+                        disconnect();
+                    }
+                }
+
+                @Override
+                public void onDisplayError(int error) {
+                    if (mConnectedDevice == oldDevice) {
+                        Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
+                                + error + ": " + mConnectedDevice.deviceName);
+                        mHandler.removeCallbacks(mRtspTimeout);
+                        handleConnectionFailure(false);
+                    }
+                }
+            }, mHandler);
+
+            // Use extended timeout value for certification, as some tests require user inputs
+            int rtspTimeout = mWifiDisplayCertMode ?
+                    RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS;
+
+            mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000);
+        }
+    }
+
+    private WifiDisplaySessionInfo getSessionInfo(WifiP2pGroup info, int session) {
+        if (info == null) {
+            return null;
+        }
+        Inet4Address addr = getInterfaceAddress(info);
+        WifiDisplaySessionInfo sessionInfo = new WifiDisplaySessionInfo(
+                !info.getOwner().deviceAddress.equals(mThisDevice.deviceAddress),
+                session,
+                info.getOwner().deviceAddress + " " + info.getNetworkName(),
+                info.getPassphrase(),
+                (addr != null) ? addr.getHostAddress() : "");
+        if (DEBUG) {
+            Slog.d(TAG, sessionInfo.toString());
+        }
+        return sessionInfo;
+    }
+
+    private void handleStateChanged(boolean enabled) {
+        mWifiP2pEnabled = enabled;
+        updateWfdEnableState();
+    }
+
+    private void handlePeersChanged() {
+        // Even if wfd is disabled, it is best to get the latest set of peers to
+        // keep in sync with the p2p framework
+        requestPeers();
+    }
+
+    private void handleConnectionChanged(NetworkInfo networkInfo) {
+        mNetworkInfo = networkInfo;
+        if (mWfdEnabled && networkInfo.isConnected()) {
+            if (mDesiredDevice != null || mWifiDisplayCertMode) {
+                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
+                    @Override
+                    public void onGroupInfoAvailable(WifiP2pGroup info) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
+                        }
+
+                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
+                            Slog.i(TAG, "Aborting connection to Wifi display because "
+                                    + "the current P2P group does not contain the device "
+                                    + "we expected to find: " + mConnectingDevice.deviceName
+                                    + ", group info was: " + describeWifiP2pGroup(info));
+                            handleConnectionFailure(false);
+                            return;
+                        }
+
+                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
+                            disconnect();
+                            return;
+                        }
+
+                        if (mWifiDisplayCertMode) {
+                            boolean owner = info.getOwner().deviceAddress
+                                    .equals(mThisDevice.deviceAddress);
+                            if (owner && info.getClientList().isEmpty()) {
+                                // this is the case when we started Autonomous GO,
+                                // and no client has connected, save group info
+                                // and updateConnection()
+                                mConnectingDevice = mDesiredDevice = null;
+                                mConnectedDeviceGroupInfo = info;
+                                updateConnection();
+                            } else if (mConnectingDevice == null && mDesiredDevice == null) {
+                                // this is the case when we received an incoming connection
+                                // from the sink, update both mConnectingDevice and mDesiredDevice
+                                // then proceed to updateConnection() below
+                                mConnectingDevice = mDesiredDevice = owner ?
+                                        info.getClientList().iterator().next() : info.getOwner();
+                            }
+                        }
+
+                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+                            Slog.i(TAG, "Connected to Wifi display: "
+                                    + mConnectingDevice.deviceName);
+
+                            mHandler.removeCallbacks(mConnectionTimeout);
+                            mConnectedDeviceGroupInfo = info;
+                            mConnectedDevice = mConnectingDevice;
+                            mConnectingDevice = null;
+                            updateConnection();
+                        }
+                    }
+                });
+            }
+        } else {
+            mConnectedDeviceGroupInfo = null;
+
+            // Disconnect if we lost the network while connecting or connected to a display.
+            if (mConnectingDevice != null || mConnectedDevice != null) {
+                disconnect();
+            }
+
+            // After disconnection for a group, for some reason we have a tendency
+            // to get a peer change notification with an empty list of peers.
+            // Perform a fresh scan.
+            if (mWfdEnabled) {
+                requestPeers();
+            }
+        }
+    }
+
+    private final Runnable mDiscoverPeers = new Runnable() {
+        @Override
+        public void run() {
+            tryDiscoverPeers();
+        }
+    };
+
+    private final Runnable mConnectionTimeout = new Runnable() {
+        @Override
+        public void run() {
+            if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+                Slog.i(TAG, "Timed out waiting for Wifi display connection after "
+                        + CONNECTION_TIMEOUT_SECONDS + " seconds: "
+                        + mConnectingDevice.deviceName);
+                handleConnectionFailure(true);
+            }
+        }
+    };
+
+    private final Runnable mRtspTimeout = new Runnable() {
+        @Override
+        public void run() {
+            if (mConnectedDevice != null
+                    && mRemoteDisplay != null && !mRemoteDisplayConnected) {
+                Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
+                        + RTSP_TIMEOUT_SECONDS + " seconds: "
+                        + mConnectedDevice.deviceName);
+                handleConnectionFailure(true);
+            }
+        }
+    };
+
+    private void handleConnectionFailure(boolean timeoutOccurred) {
+        Slog.i(TAG, "Wifi display connection failed!");
+
+        if (mDesiredDevice != null) {
+            if (mConnectionRetriesLeft > 0) {
+                final WifiP2pDevice oldDevice = mDesiredDevice;
+                mHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mDesiredDevice == oldDevice && mConnectionRetriesLeft > 0) {
+                            mConnectionRetriesLeft -= 1;
+                            Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
+                                    + mConnectionRetriesLeft);
+                            retryConnection();
+                        }
+                    }
+                }, timeoutOccurred ? 0 : CONNECT_RETRY_DELAY_MILLIS);
+            } else {
+                disconnect();
+            }
+        }
+    }
+
+    private void advertiseDisplay(final WifiDisplay display,
+            final Surface surface, final int width, final int height, final int flags) {
+        if (!Objects.equal(mAdvertisedDisplay, display)
+                || mAdvertisedDisplaySurface != surface
+                || mAdvertisedDisplayWidth != width
+                || mAdvertisedDisplayHeight != height
+                || mAdvertisedDisplayFlags != flags) {
+            final WifiDisplay oldDisplay = mAdvertisedDisplay;
+            final Surface oldSurface = mAdvertisedDisplaySurface;
+
+            mAdvertisedDisplay = display;
+            mAdvertisedDisplaySurface = surface;
+            mAdvertisedDisplayWidth = width;
+            mAdvertisedDisplayHeight = height;
+            mAdvertisedDisplayFlags = flags;
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (oldSurface != null && surface != oldSurface) {
+                        mListener.onDisplayDisconnected();
+                    } else if (oldDisplay != null && !oldDisplay.hasSameAddress(display)) {
+                        mListener.onDisplayConnectionFailed();
+                    }
+
+                    if (display != null) {
+                        if (!display.hasSameAddress(oldDisplay)) {
+                            mListener.onDisplayConnecting(display);
+                        } else if (!display.equals(oldDisplay)) {
+                            // The address is the same but some other property such as the
+                            // name must have changed.
+                            mListener.onDisplayChanged(display);
+                        }
+                        if (surface != null && surface != oldSurface) {
+                            mListener.onDisplayConnected(display, surface, width, height, flags);
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    private void unadvertiseDisplay() {
+        advertiseDisplay(null, null, 0, 0, 0);
+    }
+
+    private void readvertiseDisplay(WifiDisplay display) {
+        advertiseDisplay(display, mAdvertisedDisplaySurface,
+                mAdvertisedDisplayWidth, mAdvertisedDisplayHeight,
+                mAdvertisedDisplayFlags);
+    }
+
+    private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
+        NetworkInterface iface;
+        try {
+            iface = NetworkInterface.getByName(info.getInterface());
+        } catch (SocketException ex) {
+            Slog.w(TAG, "Could not obtain address of network interface "
+                    + info.getInterface(), ex);
+            return null;
+        }
+
+        Enumeration<InetAddress> addrs = iface.getInetAddresses();
+        while (addrs.hasMoreElements()) {
+            InetAddress addr = addrs.nextElement();
+            if (addr instanceof Inet4Address) {
+                return (Inet4Address)addr;
+            }
+        }
+
+        Slog.w(TAG, "Could not obtain address of network interface "
+                + info.getInterface() + " because it had no IPv4 addresses.");
+        return null;
+    }
+
+    private static int getPortNumber(WifiP2pDevice device) {
+        if (device.deviceName.startsWith("DIRECT-")
+                && device.deviceName.endsWith("Broadcom")) {
+            // These dongles ignore the port we broadcast in our WFD IE.
+            return 8554;
+        }
+        return DEFAULT_CONTROL_PORT;
+    }
+
+    private static boolean isWifiDisplay(WifiP2pDevice device) {
+        return device.wfdInfo != null
+                && device.wfdInfo.isWfdEnabled()
+                && isPrimarySinkDeviceType(device.wfdInfo.getDeviceType());
+    }
+
+    private static boolean isPrimarySinkDeviceType(int deviceType) {
+        return deviceType == WifiP2pWfdInfo.PRIMARY_SINK
+                || deviceType == WifiP2pWfdInfo.SOURCE_OR_PRIMARY_SINK;
+    }
+
+    private static String describeWifiP2pDevice(WifiP2pDevice device) {
+        return device != null ? device.toString().replace('\n', ',') : "null";
+    }
+
+    private static String describeWifiP2pGroup(WifiP2pGroup group) {
+        return group != null ? group.toString().replace('\n', ',') : "null";
+    }
+
+    private static WifiDisplay createWifiDisplay(WifiP2pDevice device) {
+        return new WifiDisplay(device.deviceAddress, device.deviceName, null,
+                true, device.wfdInfo.isSessionAvailable(), false);
+    }
+
+    private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
+                // This broadcast is sticky so we'll always get the initial Wifi P2P state
+                // on startup.
+                boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+                        WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
+                        WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
+                            + enabled);
+                }
+
+                handleStateChanged(enabled);
+            } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
+                }
+
+                handlePeersChanged();
+            } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+                        WifiP2pManager.EXTRA_NETWORK_INFO);
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
+                            + networkInfo);
+                }
+
+                handleConnectionChanged(networkInfo);
+            } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) {
+                mThisDevice = (WifiP2pDevice) intent.getParcelableExtra(
+                        WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: mThisDevice= "
+                            + mThisDevice);
+                }
+            }
+        }
+    };
+
+    /**
+     * Called on the handler thread when displays are connected or disconnected.
+     */
+    public interface Listener {
+        void onFeatureStateChanged(int featureState);
+
+        void onScanStarted();
+        void onScanResults(WifiDisplay[] availableDisplays);
+        void onScanFinished();
+
+        void onDisplayConnecting(WifiDisplay display);
+        void onDisplayConnectionFailed();
+        void onDisplayChanged(WifiDisplay display);
+        void onDisplayConnected(WifiDisplay display,
+                Surface surface, int width, int height, int flags);
+        void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo);
+        void onDisplayDisconnected();
+    }
+}
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
new file mode 100644
index 0000000..85ef33e
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.IBinder.DeathRecipient;
+import android.os.UserHandle;
+import android.service.dreams.DreamService;
+import android.service.dreams.IDreamService;
+import android.util.Slog;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+
+import java.io.PrintWriter;
+import java.util.NoSuchElementException;
+
+/**
+ * Internal controller for starting and stopping the current dream and managing related state.
+ *
+ * Assumes all operations are called from the dream handler thread.
+ */
+final class DreamController {
+    private static final String TAG = "DreamController";
+
+    // How long we wait for a newly bound dream to create the service connection
+    private static final int DREAM_CONNECTION_TIMEOUT = 5 * 1000;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Listener mListener;
+    private final IWindowManager mIWindowManager;
+
+    private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED)
+            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+    private final Intent mDreamingStoppedIntent = new Intent(Intent.ACTION_DREAMING_STOPPED)
+            .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+    private final Intent mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+
+    private DreamRecord mCurrentDream;
+
+    private final Runnable mStopUnconnectedDreamRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (mCurrentDream != null && mCurrentDream.mBound && !mCurrentDream.mConnected) {
+                Slog.w(TAG, "Bound dream did not connect in the time allotted");
+                stopDream();
+            }
+        }
+    };
+
+    public DreamController(Context context, Handler handler, Listener listener) {
+        mContext = context;
+        mHandler = handler;
+        mListener = listener;
+        mIWindowManager = WindowManagerGlobal.getWindowManagerService();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("Dreamland:");
+        if (mCurrentDream != null) {
+            pw.println("  mCurrentDream:");
+            pw.println("    mToken=" + mCurrentDream.mToken);
+            pw.println("    mName=" + mCurrentDream.mName);
+            pw.println("    mIsTest=" + mCurrentDream.mIsTest);
+            pw.println("    mUserId=" + mCurrentDream.mUserId);
+            pw.println("    mBound=" + mCurrentDream.mBound);
+            pw.println("    mService=" + mCurrentDream.mService);
+            pw.println("    mSentStartBroadcast=" + mCurrentDream.mSentStartBroadcast);
+        } else {
+            pw.println("  mCurrentDream: null");
+        }
+    }
+
+    public void startDream(Binder token, ComponentName name, boolean isTest, int userId) {
+        stopDream();
+
+        // Close the notification shade. Don't need to send to all, but better to be explicit.
+        mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
+
+        Slog.i(TAG, "Starting dream: name=" + name + ", isTest=" + isTest + ", userId=" + userId);
+
+        mCurrentDream = new DreamRecord(token, name, isTest, userId);
+
+        try {
+            mIWindowManager.addWindowToken(token, WindowManager.LayoutParams.TYPE_DREAM);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Unable to add window token for dream.", ex);
+            stopDream();
+            return;
+        }
+
+        Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
+        intent.setComponent(name);
+        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        try {
+            if (!mContext.bindServiceAsUser(intent, mCurrentDream,
+                    Context.BIND_AUTO_CREATE, new UserHandle(userId))) {
+                Slog.e(TAG, "Unable to bind dream service: " + intent);
+                stopDream();
+                return;
+            }
+        } catch (SecurityException ex) {
+            Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
+            stopDream();
+            return;
+        }
+
+        mCurrentDream.mBound = true;
+        mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
+    }
+
+    public void stopDream() {
+        if (mCurrentDream == null) {
+            return;
+        }
+
+        final DreamRecord oldDream = mCurrentDream;
+        mCurrentDream = null;
+        Slog.i(TAG, "Stopping dream: name=" + oldDream.mName
+                + ", isTest=" + oldDream.mIsTest + ", userId=" + oldDream.mUserId);
+
+        mHandler.removeCallbacks(mStopUnconnectedDreamRunnable);
+
+        if (oldDream.mSentStartBroadcast) {
+            mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+        }
+
+        if (oldDream.mService != null) {
+            // Tell the dream that it's being stopped so that
+            // it can shut down nicely before we yank its window token out from
+            // under it.
+            try {
+                oldDream.mService.detach();
+            } catch (RemoteException ex) {
+                // we don't care; this thing is on the way out
+            }
+
+            try {
+                oldDream.mService.asBinder().unlinkToDeath(oldDream, 0);
+            } catch (NoSuchElementException ex) {
+                // don't care
+            }
+            oldDream.mService = null;
+        }
+
+        if (oldDream.mBound) {
+            mContext.unbindService(oldDream);
+        }
+
+        try {
+            mIWindowManager.removeWindowToken(oldDream.mToken);
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Error removing window token for dream.", ex);
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDreamStopped(oldDream.mToken);
+            }
+        });
+    }
+
+    private void attach(IDreamService service) {
+        try {
+            service.asBinder().linkToDeath(mCurrentDream, 0);
+            service.attach(mCurrentDream.mToken);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "The dream service died unexpectedly.", ex);
+            stopDream();
+            return;
+        }
+
+        mCurrentDream.mService = service;
+
+        if (!mCurrentDream.mIsTest) {
+            mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
+            mCurrentDream.mSentStartBroadcast = true;
+        }
+    }
+
+    /**
+     * Callback interface to be implemented by the {@link DreamManagerService}.
+     */
+    public interface Listener {
+        void onDreamStopped(Binder token);
+    }
+
+    private final class DreamRecord implements DeathRecipient, ServiceConnection {
+        public final Binder mToken;
+        public final ComponentName mName;
+        public final boolean mIsTest;
+        public final int mUserId;
+
+        public boolean mBound;
+        public boolean mConnected;
+        public IDreamService mService;
+        public boolean mSentStartBroadcast;
+
+        public DreamRecord(Binder token, ComponentName name,
+                boolean isTest, int userId) {
+            mToken = token;
+            mName = name;
+            mIsTest = isTest;
+            mUserId  = userId;
+        }
+
+        // May be called on any thread.
+        @Override
+        public void binderDied() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mService = null;
+                    if (mCurrentDream == DreamRecord.this) {
+                        stopDream();
+                    }
+                }
+            });
+        }
+
+        // May be called on any thread.
+        @Override
+        public void onServiceConnected(ComponentName name, final IBinder service) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mConnected = true;
+                    if (mCurrentDream == DreamRecord.this && mService == null) {
+                        attach(IDreamService.Stub.asInterface(service));
+                    }
+                }
+            });
+        }
+
+        // May be called on any thread.
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mService = null;
+                    if (mCurrentDream == DreamRecord.this) {
+                        stopDream();
+                    }
+                }
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
new file mode 100644
index 0000000..b6e7781
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import com.android.internal.util.DumpUtils;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.dreams.IDreamManager;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+import libcore.util.Objects;
+
+/**
+ * Service api for managing dreams.
+ *
+ * @hide
+ */
+public final class DreamManagerService extends IDreamManager.Stub {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "DreamManagerService";
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final DreamHandler mHandler;
+    private final DreamController mController;
+    private final PowerManager mPowerManager;
+
+    private Binder mCurrentDreamToken;
+    private ComponentName mCurrentDreamName;
+    private int mCurrentDreamUserId;
+    private boolean mCurrentDreamIsTest;
+
+    public DreamManagerService(Context context, Handler mainHandler) {
+        mContext = context;
+        mHandler = new DreamHandler(mainHandler.getLooper());
+        mController = new DreamController(context, mHandler, mControllerListener);
+
+        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+    }
+
+    public void systemRunning() {
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (mLock) {
+                    stopDreamLocked();
+                }
+            }
+        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump DreamManager from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("DREAM MANAGER (dumpsys dreams)");
+        pw.println();
+
+        pw.println("mCurrentDreamToken=" + mCurrentDreamToken);
+        pw.println("mCurrentDreamName=" + mCurrentDreamName);
+        pw.println("mCurrentDreamUserId=" + mCurrentDreamUserId);
+        pw.println("mCurrentDreamIsTest=" + mCurrentDreamIsTest);
+        pw.println();
+
+        DumpUtils.dumpAsync(mHandler, new DumpUtils.Dump() {
+            @Override
+            public void dump(PrintWriter pw) {
+                mController.dump(pw);
+            }
+        }, pw, 200);
+    }
+
+    @Override // Binder call
+    public ComponentName[] getDreamComponents() {
+        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return getDreamComponentsForUser(userId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void setDreamComponents(ComponentName[] componentNames) {
+        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.SCREENSAVER_COMPONENTS,
+                    componentsToString(componentNames),
+                    userId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public ComponentName getDefaultDreamComponent() {
+        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+        final int userId = UserHandle.getCallingUserId();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            String name = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                    Settings.Secure.SCREENSAVER_DEFAULT_COMPONENT,
+                    userId);
+            return name == null ? null : ComponentName.unflattenFromString(name);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public boolean isDreaming() {
+        checkPermission(android.Manifest.permission.READ_DREAM_STATE);
+
+        synchronized (mLock) {
+            return mCurrentDreamToken != null && !mCurrentDreamIsTest;
+        }
+    }
+
+    @Override // Binder call
+    public void dream() {
+        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            // Ask the power manager to nap.  It will eventually call back into
+            // startDream() if/when it is appropriate to start dreaming.
+            // Because napping could cause the screen to turn off immediately if the dream
+            // cannot be started, we keep one eye open and gently poke user activity.
+            long time = SystemClock.uptimeMillis();
+            mPowerManager.userActivity(time, true /*noChangeLights*/);
+            mPowerManager.nap(time);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void testDream(ComponentName dream) {
+        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+        if (dream == null) {
+            throw new IllegalArgumentException("dream must not be null");
+        }
+
+        final int callingUserId = UserHandle.getCallingUserId();
+        final int currentUserId = ActivityManager.getCurrentUser();
+        if (callingUserId != currentUserId) {
+            // This check is inherently prone to races but at least it's something.
+            Slog.w(TAG, "Aborted attempt to start a test dream while a different "
+                    + " user is active: callingUserId=" + callingUserId
+                    + ", currentUserId=" + currentUserId);
+            return;
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                startDreamLocked(dream, true /*isTest*/, callingUserId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void awaken() {
+        checkPermission(android.Manifest.permission.WRITE_DREAM_STATE);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            // Treat an explicit request to awaken as user activity so that the
+            // device doesn't immediately go to sleep if the timeout expired,
+            // for example when being undocked.
+            long time = SystemClock.uptimeMillis();
+            mPowerManager.userActivity(time, false /*noChangeLights*/);
+            stopDream();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override // Binder call
+    public void finishSelf(IBinder token) {
+        // Requires no permission, called by Dream from an arbitrary process.
+        if (token == null) {
+            throw new IllegalArgumentException("token must not be null");
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            if (DEBUG) {
+                Slog.d(TAG, "Dream finished: " + token);
+            }
+
+            // Note that a dream finishing and self-terminating is not
+            // itself considered user activity.  If the dream is ending because
+            // the user interacted with the device then user activity will already
+            // have been poked so the device will stay awake a bit longer.
+            // If the dream is ending on its own for other reasons and no wake
+            // locks are held and the user activity timeout has expired then the
+            // device may simply go to sleep.
+            synchronized (mLock) {
+                if (mCurrentDreamToken == token) {
+                    stopDreamLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Called by the power manager to start a dream.
+     */
+    public void startDream() {
+        int userId = ActivityManager.getCurrentUser();
+        ComponentName dream = chooseDreamForUser(userId);
+        if (dream != null) {
+            synchronized (mLock) {
+                startDreamLocked(dream, false /*isTest*/, userId);
+            }
+        }
+    }
+
+    /**
+     * Called by the power manager to stop a dream.
+     */
+    public void stopDream() {
+        synchronized (mLock) {
+            stopDreamLocked();
+        }
+    }
+
+    private ComponentName chooseDreamForUser(int userId) {
+        ComponentName[] dreams = getDreamComponentsForUser(userId);
+        return dreams != null && dreams.length != 0 ? dreams[0] : null;
+    }
+
+    private ComponentName[] getDreamComponentsForUser(int userId) {
+        String names = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.SCREENSAVER_COMPONENTS,
+                userId);
+        ComponentName[] components = componentsFromString(names);
+
+        // first, ensure components point to valid services
+        List<ComponentName> validComponents = new ArrayList<ComponentName>();
+        if (components != null) {
+            for (ComponentName component : components) {
+                if (serviceExists(component)) {
+                    validComponents.add(component);
+                } else {
+                    Slog.w(TAG, "Dream " + component + " does not exist");
+                }
+            }
+        }
+
+        // fallback to the default dream component if necessary
+        if (validComponents.isEmpty()) {
+            ComponentName defaultDream = getDefaultDreamComponent();
+            if (defaultDream != null) {
+                Slog.w(TAG, "Falling back to default dream " + defaultDream);
+                validComponents.add(defaultDream);
+            }
+        }
+        return validComponents.toArray(new ComponentName[validComponents.size()]);
+    }
+
+    private boolean serviceExists(ComponentName name) {
+        try {
+            return name != null && mContext.getPackageManager().getServiceInfo(name, 0) != null;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    private void startDreamLocked(final ComponentName name,
+            final boolean isTest, final int userId) {
+        if (Objects.equal(mCurrentDreamName, name)
+                && mCurrentDreamIsTest == isTest
+                && mCurrentDreamUserId == userId) {
+            return;
+        }
+
+        stopDreamLocked();
+
+        if (DEBUG) Slog.i(TAG, "Entering dreamland.");
+
+        final Binder newToken = new Binder();
+        mCurrentDreamToken = newToken;
+        mCurrentDreamName = name;
+        mCurrentDreamIsTest = isTest;
+        mCurrentDreamUserId = userId;
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mController.startDream(newToken, name, isTest, userId);
+            }
+        });
+    }
+
+    private void stopDreamLocked() {
+        if (mCurrentDreamToken != null) {
+            if (DEBUG) Slog.i(TAG, "Leaving dreamland.");
+
+            cleanupDreamLocked();
+
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mController.stopDream();
+                }
+            });
+        }
+    }
+
+    private void cleanupDreamLocked() {
+        mCurrentDreamToken = null;
+        mCurrentDreamName = null;
+        mCurrentDreamIsTest = false;
+        mCurrentDreamUserId = 0;
+    }
+
+    private void checkPermission(String permission) {
+        if (mContext.checkCallingOrSelfPermission(permission)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
+                    + ", must have permission " + permission);
+        }
+    }
+
+    private static String componentsToString(ComponentName[] componentNames) {
+        StringBuilder names = new StringBuilder();
+        if (componentNames != null) {
+            for (ComponentName componentName : componentNames) {
+                if (names.length() > 0) {
+                    names.append(',');
+                }
+                names.append(componentName.flattenToString());
+            }
+        }
+        return names.toString();
+    }
+
+    private static ComponentName[] componentsFromString(String names) {
+        if (names == null) {
+            return null;
+        }
+        String[] namesArray = names.split(",");
+        ComponentName[] componentNames = new ComponentName[namesArray.length];
+        for (int i = 0; i < namesArray.length; i++) {
+            componentNames[i] = ComponentName.unflattenFromString(namesArray[i]);
+        }
+        return componentNames;
+    }
+
+    private final DreamController.Listener mControllerListener = new DreamController.Listener() {
+        @Override
+        public void onDreamStopped(Binder token) {
+            synchronized (mLock) {
+                if (mCurrentDreamToken == token) {
+                    cleanupDreamLocked();
+                }
+            }
+        }
+    };
+
+    /**
+     * Handler for asynchronous operations performed by the dream manager.
+     * Ensures operations to {@link DreamController} are single-threaded.
+     */
+    private final class DreamHandler extends Handler {
+        public DreamHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/firewall/AndFilter.java b/services/core/java/com/android/server/firewall/AndFilter.java
new file mode 100644
index 0000000..13551de
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/AndFilter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class AndFilter extends FilterList {
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        for (int i=0; i<children.size(); i++) {
+            if (!children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid,
+                    resolvedType, receivingUid)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("and") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new AndFilter().readFromXml(parser);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/CategoryFilter.java b/services/core/java/com/android/server/firewall/CategoryFilter.java
new file mode 100644
index 0000000..246c096
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/CategoryFilter.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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.Set;
+
+class CategoryFilter implements Filter {
+    private static final String ATTR_NAME = "name";
+
+    private final String mCategoryName;
+
+    private CategoryFilter(String categoryName) {
+        mCategoryName = categoryName;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        Set<String> categories = intent.getCategories();
+        if (categories == null) {
+            return false;
+        }
+        return categories.contains(mCategoryName);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("category") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            String categoryName = parser.getAttributeValue(null, ATTR_NAME);
+            if (categoryName == null) {
+                throw new XmlPullParserException("Category name must be specified.",
+                        parser, null);
+            }
+            return new CategoryFilter(categoryName);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/Filter.java b/services/core/java/com/android/server/firewall/Filter.java
new file mode 100644
index 0000000..0a124f7
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/Filter.java
@@ -0,0 +1,36 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+
+interface Filter {
+    /**
+     * Does the given intent + context info match this filter?
+     *
+     * @param ifw The IntentFirewall instance
+     * @param resolvedComponent The actual component that the intent was resolved to
+     * @param intent The intent being started/bound/broadcast
+     * @param callerUid The uid of the caller
+     * @param callerPid The pid of the caller
+     * @param resolvedType The resolved mime type of the intent
+     * @param receivingUid The uid of the component receiving the intent
+     */
+    boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid);
+}
diff --git a/services/core/java/com/android/server/firewall/FilterFactory.java b/services/core/java/com/android/server/firewall/FilterFactory.java
new file mode 100644
index 0000000..dea8b40
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/FilterFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.firewall;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+public abstract class FilterFactory {
+    private final String mTag;
+
+    protected FilterFactory(String tag) {
+        if (tag == null) {
+            throw new NullPointerException();
+        }
+        mTag = tag;
+    }
+
+    public String getTagName() {
+        return mTag;
+    }
+
+    public abstract Filter newFilter(XmlPullParser parser)
+            throws IOException, XmlPullParserException;
+}
diff --git a/services/core/java/com/android/server/firewall/FilterList.java b/services/core/java/com/android/server/firewall/FilterList.java
new file mode 100644
index 0000000..d34b203
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/FilterList.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.firewall;
+
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+abstract class FilterList implements Filter {
+    protected final ArrayList<Filter> children = new ArrayList<Filter>();
+
+    public FilterList readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            readChild(parser);
+        }
+        return this;
+    }
+
+    protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+        Filter filter = IntentFirewall.parseFilter(parser);
+        children.add(filter);
+    }
+}
diff --git a/services/core/java/com/android/server/firewall/IntentFirewall.java b/services/core/java/com/android/server/firewall/IntentFirewall.java
new file mode 100644
index 0000000..aaa0b58
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/IntentFirewall.java
@@ -0,0 +1,601 @@
+/*
+ * 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.firewall;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.XmlUtils;
+import com.android.server.EventLogTags;
+import com.android.server.IntentResolver;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+public class IntentFirewall {
+    static final String TAG = "IntentFirewall";
+
+    // e.g. /data/system/ifw or /data/secure/system/ifw
+    private static final File RULES_DIR = new File(Environment.getSystemSecureDirectory(), "ifw");
+
+    private static final int LOG_PACKAGES_MAX_LENGTH = 150;
+    private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125;
+
+    private static final String TAG_RULES = "rules";
+    private static final String TAG_ACTIVITY = "activity";
+    private static final String TAG_SERVICE = "service";
+    private static final String TAG_BROADCAST = "broadcast";
+
+    private static final int TYPE_ACTIVITY = 0;
+    private static final int TYPE_BROADCAST = 1;
+    private static final int TYPE_SERVICE = 2;
+
+    private static final HashMap<String, FilterFactory> factoryMap;
+
+    private final AMSInterface mAms;
+
+    private final RuleObserver mObserver;
+
+    private FirewallIntentResolver mActivityResolver = new FirewallIntentResolver();
+    private FirewallIntentResolver mBroadcastResolver = new FirewallIntentResolver();
+    private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver();
+
+    static {
+        FilterFactory[] factories = new FilterFactory[] {
+                AndFilter.FACTORY,
+                OrFilter.FACTORY,
+                NotFilter.FACTORY,
+
+                StringFilter.ACTION,
+                StringFilter.COMPONENT,
+                StringFilter.COMPONENT_NAME,
+                StringFilter.COMPONENT_PACKAGE,
+                StringFilter.DATA,
+                StringFilter.HOST,
+                StringFilter.MIME_TYPE,
+                StringFilter.SCHEME,
+                StringFilter.PATH,
+                StringFilter.SSP,
+
+                CategoryFilter.FACTORY,
+                SenderFilter.FACTORY,
+                SenderPermissionFilter.FACTORY,
+                PortFilter.FACTORY
+        };
+
+        // load factor ~= .75
+        factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3);
+        for (int i=0; i<factories.length; i++) {
+            FilterFactory factory = factories[i];
+            factoryMap.put(factory.getTagName(), factory);
+        }
+    }
+
+    public IntentFirewall(AMSInterface ams) {
+        mAms = ams;
+        File rulesDir = getRulesDir();
+        rulesDir.mkdirs();
+
+        readRulesDir(rulesDir);
+
+        mObserver = new RuleObserver(rulesDir);
+        mObserver.startWatching();
+    }
+
+    /**
+     * This is called from ActivityManager to check if a start activity intent should be allowed.
+     * It is assumed the caller is already holding the global ActivityManagerService lock.
+     */
+    public boolean checkStartActivity(Intent intent, int callerUid, int callerPid,
+            String resolvedType, ApplicationInfo resolvedApp) {
+        return checkIntent(mActivityResolver, intent.getComponent(), TYPE_ACTIVITY, intent,
+                callerUid, callerPid, resolvedType, resolvedApp.uid);
+    }
+
+    public boolean checkService(ComponentName resolvedService, Intent intent, int callerUid,
+            int callerPid, String resolvedType, ApplicationInfo resolvedApp) {
+        return checkIntent(mServiceResolver, resolvedService, TYPE_SERVICE, intent, callerUid,
+                callerPid, resolvedType, resolvedApp.uid);
+    }
+
+    public boolean checkBroadcast(Intent intent, int callerUid, int callerPid,
+            String resolvedType, int receivingUid) {
+        return checkIntent(mBroadcastResolver, intent.getComponent(), TYPE_BROADCAST, intent,
+                callerUid, callerPid, resolvedType, receivingUid);
+    }
+
+    public boolean checkIntent(FirewallIntentResolver resolver, ComponentName resolvedComponent,
+            int intentType, Intent intent, int callerUid, int callerPid, String resolvedType,
+            int receivingUid) {
+        boolean log = false;
+        boolean block = false;
+
+        // For the first pass, find all the rules that have at least one intent-filter or
+        // component-filter that matches this intent
+        List<Rule> candidateRules;
+        candidateRules = resolver.queryIntent(intent, resolvedType, false, 0);
+        if (candidateRules == null) {
+            candidateRules = new ArrayList<Rule>();
+        }
+        resolver.queryByComponent(resolvedComponent, candidateRules);
+
+        // For the second pass, try to match the potentially more specific conditions in each
+        // rule against the intent
+        for (int i=0; i<candidateRules.size(); i++) {
+            Rule rule = candidateRules.get(i);
+            if (rule.matches(this, resolvedComponent, intent, callerUid, callerPid, resolvedType,
+                    receivingUid)) {
+                block |= rule.getBlock();
+                log |= rule.getLog();
+
+                // if we've already determined that we should both block and log, there's no need
+                // to continue trying rules
+                if (block && log) {
+                    break;
+                }
+            }
+        }
+
+        if (log) {
+            logIntent(intentType, intent, callerUid, resolvedType);
+        }
+
+        return !block;
+    }
+
+    private static void logIntent(int intentType, Intent intent, int callerUid,
+            String resolvedType) {
+        // The component shouldn't be null, but let's double check just to be safe
+        ComponentName cn = intent.getComponent();
+        String shortComponent = null;
+        if (cn != null) {
+            shortComponent = cn.flattenToShortString();
+        }
+
+        String callerPackages = null;
+        int callerPackageCount = 0;
+        IPackageManager pm = AppGlobals.getPackageManager();
+        if (pm != null) {
+            try {
+                String[] callerPackagesArray = pm.getPackagesForUid(callerUid);
+                if (callerPackagesArray != null) {
+                    callerPackageCount = callerPackagesArray.length;
+                    callerPackages = joinPackages(callerPackagesArray);
+                }
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Remote exception while retrieving packages", ex);
+            }
+        }
+
+        EventLogTags.writeIfwIntentMatched(intentType, shortComponent, callerUid,
+                callerPackageCount, callerPackages, intent.getAction(), resolvedType,
+                intent.getDataString(), intent.getFlags());
+    }
+
+    /**
+     * Joins a list of package names such that the resulting string is no more than
+     * LOG_PACKAGES_MAX_LENGTH.
+     *
+     * Only full package names will be added to the result, unless every package is longer than the
+     * limit, in which case one of the packages will be truncated and added. In this case, an
+     * additional '-' character will be added to the end of the string, to denote the truncation.
+     *
+     * If it encounters a package that won't fit in the remaining space, it will continue on to the
+     * next package, unless the total length of the built string so far is greater than
+     * LOG_PACKAGES_SUFFICIENT_LENGTH, in which case it will stop and return what it has.
+     */
+    private static String joinPackages(String[] packages) {
+        boolean first = true;
+        StringBuilder sb = new StringBuilder();
+        for (int i=0; i<packages.length; i++) {
+            String pkg = packages[i];
+
+            // + 1 length for the comma. This logic technically isn't correct for the first entry,
+            // but it's not critical.
+            if (sb.length() + pkg.length() + 1 < LOG_PACKAGES_MAX_LENGTH) {
+                if (!first) {
+                    sb.append(',');
+                } else {
+                    first = false;
+                }
+                sb.append(pkg);
+            } else if (sb.length() >= LOG_PACKAGES_SUFFICIENT_LENGTH) {
+                return sb.toString();
+            }
+        }
+        if (sb.length() == 0 && packages.length > 0) {
+            String pkg = packages[0];
+            // truncating from the end - the last part of the package name is more likely to be
+            // interesting/unique
+            return pkg.substring(pkg.length() - LOG_PACKAGES_MAX_LENGTH + 1) + '-';
+        }
+        return null;
+    }
+
+    public static File getRulesDir() {
+        return RULES_DIR;
+    }
+
+    /**
+     * Reads rules from all xml files (*.xml) in the given directory, and replaces our set of rules
+     * with the newly read rules.
+     *
+     * We only check for files ending in ".xml", to allow for temporary files that are atomically
+     * renamed to .xml
+     *
+     * All calls to this method from the file observer come through a handler and are inherently
+     * serialized
+     */
+    private void readRulesDir(File rulesDir) {
+        FirewallIntentResolver[] resolvers = new FirewallIntentResolver[3];
+        for (int i=0; i<resolvers.length; i++) {
+            resolvers[i] = new FirewallIntentResolver();
+        }
+
+        File[] files = rulesDir.listFiles();
+        for (int i=0; i<files.length; i++) {
+            File file = files[i];
+
+            if (file.getName().endsWith(".xml")) {
+                readRules(file, resolvers);
+            }
+        }
+
+        Slog.i(TAG, "Read new rules (A:" + resolvers[TYPE_ACTIVITY].filterSet().size() +
+                " B:" + resolvers[TYPE_BROADCAST].filterSet().size() +
+                " S:" + resolvers[TYPE_SERVICE].filterSet().size() + ")");
+
+        synchronized (mAms.getAMSLock()) {
+            mActivityResolver = resolvers[TYPE_ACTIVITY];
+            mBroadcastResolver = resolvers[TYPE_BROADCAST];
+            mServiceResolver = resolvers[TYPE_SERVICE];
+        }
+    }
+
+    /**
+     * Reads rules from the given file and add them to the given resolvers
+     */
+    private void readRules(File rulesFile, FirewallIntentResolver[] resolvers) {
+        // some temporary lists to hold the rules while we parse the xml file, so that we can
+        // add the rules all at once, after we know there weren't any major structural problems
+        // with the xml file
+        List<List<Rule>> rulesByType = new ArrayList<List<Rule>>(3);
+        for (int i=0; i<3; i++) {
+            rulesByType.add(new ArrayList<Rule>());
+        }
+
+        FileInputStream fis;
+        try {
+            fis = new FileInputStream(rulesFile);
+        } catch (FileNotFoundException ex) {
+            // Nope, no rules. Nothing else to do!
+            return;
+        }
+
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+
+            parser.setInput(fis, null);
+
+            XmlUtils.beginDocument(parser, TAG_RULES);
+
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                int ruleType = -1;
+
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_ACTIVITY)) {
+                    ruleType = TYPE_ACTIVITY;
+                } else if (tagName.equals(TAG_BROADCAST)) {
+                    ruleType = TYPE_BROADCAST;
+                } else if (tagName.equals(TAG_SERVICE)) {
+                    ruleType = TYPE_SERVICE;
+                }
+
+                if (ruleType != -1) {
+                    Rule rule = new Rule();
+
+                    List<Rule> rules = rulesByType.get(ruleType);
+
+                    // if we get an error while parsing a particular rule, we'll just ignore
+                    // that rule and continue on with the next rule
+                    try {
+                        rule.readFromXml(parser);
+                    } catch (XmlPullParserException ex) {
+                        Slog.e(TAG, "Error reading an intent firewall rule from " + rulesFile, ex);
+                        continue;
+                    }
+
+                    rules.add(rule);
+                }
+            }
+        } catch (XmlPullParserException ex) {
+            // if there was an error outside of a specific rule, then there are probably
+            // structural problems with the xml file, and we should completely ignore it
+            Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex);
+            return;
+        } catch (IOException ex) {
+            Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex);
+            return;
+        } finally {
+            try {
+                fis.close();
+            } catch (IOException ex) {
+                Slog.e(TAG, "Error while closing " + rulesFile, ex);
+            }
+        }
+
+        for (int ruleType=0; ruleType<rulesByType.size(); ruleType++) {
+            List<Rule> rules = rulesByType.get(ruleType);
+            FirewallIntentResolver resolver = resolvers[ruleType];
+
+            for (int ruleIndex=0; ruleIndex<rules.size(); ruleIndex++) {
+                Rule rule = rules.get(ruleIndex);
+                for (int i=0; i<rule.getIntentFilterCount(); i++) {
+                    resolver.addFilter(rule.getIntentFilter(i));
+                }
+                for (int i=0; i<rule.getComponentFilterCount(); i++) {
+                    resolver.addComponentFilter(rule.getComponentFilter(i), rule);
+                }
+            }
+        }
+    }
+
+    static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+        String elementName = parser.getName();
+
+        FilterFactory factory = factoryMap.get(elementName);
+
+        if (factory == null) {
+            throw new XmlPullParserException("Unknown element in filter list: " + elementName);
+        }
+        return factory.newFilter(parser);
+    }
+
+    /**
+     * Represents a single activity/service/broadcast rule within one of the xml files.
+     *
+     * Rules are matched against an incoming intent in two phases. The goal of the first phase
+     * is to select a subset of rules that might match a given intent.
+     *
+     * For the first phase, we use a combination of intent filters (via an IntentResolver)
+     * and component filters to select which rules to check. If a rule has multiple intent or
+     * component filters, only a single filter must match for the rule to be passed on to the
+     * second phase.
+     *
+     * In the second phase, we check the specific conditions in each rule against the values in the
+     * intent. All top level conditions (but not filters) in the rule must match for the rule as a
+     * whole to match.
+     *
+     * If the rule matches, then we block or log the intent, as specified by the rule. If multiple
+     * rules match, we combine the block/log flags from any matching rule.
+     */
+    private static class Rule extends AndFilter {
+        private static final String TAG_INTENT_FILTER = "intent-filter";
+        private static final String TAG_COMPONENT_FILTER = "component-filter";
+        private static final String ATTR_NAME = "name";
+
+        private static final String ATTR_BLOCK = "block";
+        private static final String ATTR_LOG = "log";
+
+        private final ArrayList<FirewallIntentFilter> mIntentFilters =
+                new ArrayList<FirewallIntentFilter>(1);
+        private final ArrayList<ComponentName> mComponentFilters = new ArrayList<ComponentName>(0);
+        private boolean block;
+        private boolean log;
+
+        @Override
+        public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
+            block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK));
+            log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG));
+
+            super.readFromXml(parser);
+            return this;
+        }
+
+        @Override
+        protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
+            String currentTag = parser.getName();
+
+            if (currentTag.equals(TAG_INTENT_FILTER)) {
+                FirewallIntentFilter intentFilter = new FirewallIntentFilter(this);
+                intentFilter.readFromXml(parser);
+                mIntentFilters.add(intentFilter);
+            } else if (currentTag.equals(TAG_COMPONENT_FILTER)) {
+                String componentStr = parser.getAttributeValue(null, ATTR_NAME);
+                if (componentStr == null) {
+                    throw new XmlPullParserException("Component name must be specified.",
+                            parser, null);
+                }
+
+                ComponentName componentName = ComponentName.unflattenFromString(componentStr);
+                if (componentName == null) {
+                    throw new XmlPullParserException("Invalid component name: " + componentStr);
+                }
+
+                mComponentFilters.add(componentName);
+            } else {
+                super.readChild(parser);
+            }
+        }
+
+        public int getIntentFilterCount() {
+            return mIntentFilters.size();
+        }
+
+        public FirewallIntentFilter getIntentFilter(int index) {
+            return mIntentFilters.get(index);
+        }
+
+        public int getComponentFilterCount() {
+            return mComponentFilters.size();
+        }
+
+        public ComponentName getComponentFilter(int index) {
+            return mComponentFilters.get(index);
+        }
+        public boolean getBlock() {
+            return block;
+        }
+
+        public boolean getLog() {
+            return log;
+        }
+    }
+
+    private static class FirewallIntentFilter extends IntentFilter {
+        private final Rule rule;
+
+        public FirewallIntentFilter(Rule rule) {
+            this.rule = rule;
+        }
+    }
+
+    private static class FirewallIntentResolver
+            extends IntentResolver<FirewallIntentFilter, Rule> {
+        @Override
+        protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) {
+            return !dest.contains(filter.rule);
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) {
+            return true;
+        }
+
+        @Override
+        protected FirewallIntentFilter[] newArray(int size) {
+            return new FirewallIntentFilter[size];
+        }
+
+        @Override
+        protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
+            return filter.rule;
+        }
+
+        @Override
+        protected void sortResults(List<Rule> results) {
+            // there's no need to sort the results
+            return;
+        }
+
+        public void queryByComponent(ComponentName componentName, List<Rule> candidateRules) {
+            Rule[] rules = mRulesByComponent.get(componentName);
+            if (rules != null) {
+                candidateRules.addAll(Arrays.asList(rules));
+            }
+        }
+
+        public void addComponentFilter(ComponentName componentName, Rule rule) {
+            Rule[] rules = mRulesByComponent.get(componentName);
+            rules = ArrayUtils.appendElement(Rule.class, rules, rule);
+            mRulesByComponent.put(componentName, rules);
+        }
+
+        private final ArrayMap<ComponentName, Rule[]> mRulesByComponent =
+                new ArrayMap<ComponentName, Rule[]>(0);
+    }
+
+    final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            readRulesDir(getRulesDir());
+        }
+    };
+
+    /**
+     * Monitors for the creation/deletion/modification of any .xml files in the rule directory
+     */
+    private class RuleObserver extends FileObserver {
+        private static final int MONITORED_EVENTS = FileObserver.CREATE|FileObserver.MOVED_TO|
+                FileObserver.CLOSE_WRITE|FileObserver.DELETE|FileObserver.MOVED_FROM;
+
+        public RuleObserver(File monitoredDir) {
+            super(monitoredDir.getAbsolutePath(), MONITORED_EVENTS);
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            if (path.endsWith(".xml")) {
+                // we wait 250ms before taking any action on an event, in order to dedup multiple
+                // events. E.g. a delete event followed by a create event followed by a subsequent
+                // write+close event
+                mHandler.removeMessages(0);
+                mHandler.sendEmptyMessageDelayed(0, 250);
+            }
+        }
+    }
+
+    /**
+     * This interface contains the methods we need from ActivityManagerService. This allows AMS to
+     * export these methods to us without making them public, and also makes it easier to test this
+     * component.
+     */
+    public interface AMSInterface {
+        int checkComponentPermission(String permission, int pid, int uid,
+                int owningUid, boolean exported);
+        Object getAMSLock();
+    }
+
+    /**
+     * Checks if the caller has access to a component
+     *
+     * @param permission If present, the caller must have this permission
+     * @param pid The pid of the caller
+     * @param uid The uid of the caller
+     * @param owningUid The uid of the application that owns the component
+     * @param exported Whether the component is exported
+     * @return True if the caller can access the described component
+     */
+    boolean checkComponentPermission(String permission, int pid, int uid, int owningUid,
+            boolean exported) {
+        return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) ==
+                PackageManager.PERMISSION_GRANTED;
+    }
+
+    boolean signaturesMatch(int uid1, int uid2) {
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH;
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "Remote exception while checking signatures", ex);
+            return false;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/firewall/NotFilter.java b/services/core/java/com/android/server/firewall/NotFilter.java
new file mode 100644
index 0000000..09bf629
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/NotFilter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class NotFilter implements Filter {
+    private final Filter mChild;
+
+    private NotFilter(Filter child) {
+        mChild = child;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        return !mChild.matches(ifw, resolvedComponent, intent, callerUid, callerPid, resolvedType,
+                receivingUid);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("not") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            Filter child = null;
+            int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                Filter filter = IntentFirewall.parseFilter(parser);
+                if (child == null) {
+                    child = filter;
+                } else {
+                    throw new XmlPullParserException(
+                            "<not> tag can only contain a single child filter.", parser, null);
+                }
+            }
+            return new NotFilter(child);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/OrFilter.java b/services/core/java/com/android/server/firewall/OrFilter.java
new file mode 100644
index 0000000..f6a6f22
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/OrFilter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class OrFilter extends FilterList {
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        for (int i=0; i<children.size(); i++) {
+            if (children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid,
+                    resolvedType, receivingUid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("or") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return new OrFilter().readFromXml(parser);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/PortFilter.java b/services/core/java/com/android/server/firewall/PortFilter.java
new file mode 100644
index 0000000..84ace553
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/PortFilter.java
@@ -0,0 +1,110 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class PortFilter implements Filter {
+    private static final String ATTR_EQUALS = "equals";
+    private static final String ATTR_MIN = "min";
+    private static final String ATTR_MAX = "max";
+
+    private static final int NO_BOUND = -1;
+
+    // both bounds are inclusive
+    private final int mLowerBound;
+    private final int mUpperBound;
+
+    private PortFilter(int lowerBound, int upperBound) {
+        mLowerBound = lowerBound;
+        mUpperBound = upperBound;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        int port = -1;
+        Uri uri = intent.getData();
+        if (uri != null) {
+            port = uri.getPort();
+        }
+        return port != -1 &&
+                (mLowerBound == NO_BOUND || mLowerBound <= port) &&
+                (mUpperBound == NO_BOUND || mUpperBound >= port);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("port") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            int lowerBound = NO_BOUND;
+            int upperBound = NO_BOUND;
+
+            String equalsValue = parser.getAttributeValue(null, ATTR_EQUALS);
+            if (equalsValue != null) {
+                int value;
+                try {
+                    value = Integer.parseInt(equalsValue);
+                } catch (NumberFormatException ex) {
+                    throw new XmlPullParserException("Invalid port value: " + equalsValue,
+                            parser, null);
+                }
+                lowerBound = value;
+                upperBound = value;
+            }
+
+            String lowerBoundString = parser.getAttributeValue(null, ATTR_MIN);
+            String upperBoundString = parser.getAttributeValue(null, ATTR_MAX);
+            if (lowerBoundString != null || upperBoundString != null) {
+                if (equalsValue != null) {
+                    throw new XmlPullParserException(
+                            "Port filter cannot use both equals and range filtering",
+                            parser, null);
+                }
+
+                if (lowerBoundString != null) {
+                    try {
+                        lowerBound = Integer.parseInt(lowerBoundString);
+                    } catch (NumberFormatException ex) {
+                        throw new XmlPullParserException(
+                                "Invalid minimum port value: " + lowerBoundString,
+                                parser, null);
+                    }
+                }
+
+                if (upperBoundString != null) {
+                    try {
+                        upperBound = Integer.parseInt(upperBoundString);
+                    } catch (NumberFormatException ex) {
+                        throw new XmlPullParserException(
+                                "Invalid maximum port value: " + upperBoundString,
+                                parser, null);
+                    }
+                }
+            }
+
+            // an empty port filter is explicitly allowed, and checks for the existence of a port
+            return new PortFilter(lowerBound, upperBound);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/SenderFilter.java b/services/core/java/com/android/server/firewall/SenderFilter.java
new file mode 100644
index 0000000..c0eee69
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/SenderFilter.java
@@ -0,0 +1,115 @@
+/*
+ * 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.firewall;
+
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderFilter {
+    private static final String ATTR_TYPE = "type";
+
+    private static final String VAL_SIGNATURE = "signature";
+    private static final String VAL_SYSTEM = "system";
+    private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature";
+    private static final String VAL_USER_ID = "userId";
+
+    static boolean isPrivilegedApp(int callerUid, int callerPid) {
+        if (callerUid == Process.SYSTEM_UID || callerUid == 0 ||
+                callerPid == Process.myPid() || callerPid == 0) {
+            return true;
+        }
+
+        IPackageManager pm = AppGlobals.getPackageManager();
+        try {
+            return (pm.getFlagsForUid(callerUid) & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+        } catch (RemoteException ex) {
+            Slog.e(IntentFirewall.TAG, "Remote exception while retrieving uid flags",
+                    ex);
+        }
+
+        return false;
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("sender") {
+        @Override
+        public Filter newFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
+            String typeString = parser.getAttributeValue(null, ATTR_TYPE);
+            if (typeString == null) {
+                throw new XmlPullParserException("type attribute must be specified for <sender>",
+                        parser, null);
+            }
+            if (typeString.equals(VAL_SYSTEM)) {
+                return SYSTEM;
+            } else if (typeString.equals(VAL_SIGNATURE)) {
+                return SIGNATURE;
+            } else if (typeString.equals(VAL_SYSTEM_OR_SIGNATURE)) {
+                return SYSTEM_OR_SIGNATURE;
+            } else if (typeString.equals(VAL_USER_ID)) {
+                return USER_ID;
+            }
+            throw new XmlPullParserException(
+                    "Invalid type attribute for <sender>: " + typeString, parser, null);
+        }
+    };
+
+    private static final Filter SIGNATURE = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+                int callerUid, int callerPid, String resolvedType, int receivingUid) {
+            return ifw.signaturesMatch(callerUid, receivingUid);
+        }
+    };
+
+    private static final Filter SYSTEM = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+                int callerUid, int callerPid, String resolvedType, int receivingUid) {
+            return isPrivilegedApp(callerUid, callerPid);
+        }
+    };
+
+    private static final Filter SYSTEM_OR_SIGNATURE = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+                int callerUid, int callerPid, String resolvedType, int receivingUid) {
+            return isPrivilegedApp(callerUid, callerPid) ||
+                    ifw.signaturesMatch(callerUid, receivingUid);
+        }
+    };
+
+    private static final Filter USER_ID = new Filter() {
+        @Override
+        public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+                int callerUid, int callerPid, String resolvedType, int receivingUid) {
+            // This checks whether the caller is either the system process, or has the same user id
+            // I.e. the same app, or an app that uses the same shared user id.
+            // This is the same set of applications that would be able to access the component if
+            // it wasn't exported.
+            return ifw.checkComponentPermission(null, callerPid, callerUid, receivingUid, false);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/SenderPermissionFilter.java b/services/core/java/com/android/server/firewall/SenderPermissionFilter.java
new file mode 100644
index 0000000..caa65f3
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/SenderPermissionFilter.java
@@ -0,0 +1,57 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+class SenderPermissionFilter implements Filter {
+    private static final String ATTR_NAME = "name";
+
+    private final String mPermission;
+
+    private SenderPermissionFilter(String permission) {
+        mPermission = permission;
+    }
+
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        // We assume the component is exported here. If the component is not exported, then
+        // ActivityManager would only resolve to this component for callers from the same uid.
+        // In this case, it doesn't matter whether the component is exported or not.
+        return ifw.checkComponentPermission(mPermission, callerPid, callerUid, receivingUid,
+                true);
+    }
+
+    public static final FilterFactory FACTORY = new FilterFactory("sender-permission") {
+        @Override
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            String permission = parser.getAttributeValue(null, ATTR_NAME);
+            if (permission == null) {
+                throw new XmlPullParserException("Permission name must be specified.",
+                        parser, null);
+            }
+            return new SenderPermissionFilter(permission);
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/firewall/StringFilter.java b/services/core/java/com/android/server/firewall/StringFilter.java
new file mode 100644
index 0000000..28e99b3
--- /dev/null
+++ b/services/core/java/com/android/server/firewall/StringFilter.java
@@ -0,0 +1,338 @@
+/*
+ * 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.firewall;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+abstract class StringFilter implements Filter {
+    private static final String ATTR_EQUALS = "equals";
+    private static final String ATTR_STARTS_WITH = "startsWith";
+    private static final String ATTR_CONTAINS = "contains";
+    private static final String ATTR_PATTERN = "pattern";
+    private static final String ATTR_REGEX = "regex";
+    private static final String ATTR_IS_NULL = "isNull";
+
+    private final ValueProvider mValueProvider;
+
+    private StringFilter(ValueProvider valueProvider) {
+        this.mValueProvider = valueProvider;
+    }
+
+    /**
+     * Constructs a new StringFilter based on the string filter attribute on the current
+     * element, and the given StringValueMatcher.
+     *
+     * The current node should contain exactly 1 string filter attribute. E.g. equals,
+     * contains, etc. Otherwise, an XmlPullParserException will be thrown.
+     *
+     * @param parser      An XmlPullParser object positioned at an element that should
+     *                    contain a string filter attribute
+     * @return This StringFilter object
+     */
+    public static StringFilter readFromXml(ValueProvider valueProvider, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        StringFilter filter = null;
+
+        for (int i=0; i<parser.getAttributeCount(); i++) {
+            StringFilter newFilter = getFilter(valueProvider, parser, i);
+            if (newFilter != null) {
+                if (filter != null) {
+                    throw new XmlPullParserException("Multiple string filter attributes found");
+                }
+                filter = newFilter;
+            }
+        }
+
+        if (filter == null) {
+            // if there are no string filter attributes, we default to isNull="false" so that an
+            // empty filter is equivalent to an existence check
+            filter = new IsNullFilter(valueProvider, false);
+        }
+
+        return filter;
+    }
+
+    private static StringFilter getFilter(ValueProvider valueProvider, XmlPullParser parser,
+            int attributeIndex) {
+        String attributeName = parser.getAttributeName(attributeIndex);
+
+        switch (attributeName.charAt(0)) {
+            case 'e':
+                if (!attributeName.equals(ATTR_EQUALS)) {
+                    return null;
+                }
+                return new EqualsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 'i':
+                if (!attributeName.equals(ATTR_IS_NULL)) {
+                    return null;
+                }
+                return new IsNullFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 's':
+                if (!attributeName.equals(ATTR_STARTS_WITH)) {
+                    return null;
+                }
+                return new StartsWithFilter(valueProvider,
+                        parser.getAttributeValue(attributeIndex));
+            case 'c':
+                if (!attributeName.equals(ATTR_CONTAINS)) {
+                    return null;
+                }
+                return new ContainsFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+            case 'p':
+                if (!attributeName.equals(ATTR_PATTERN)) {
+                    return null;
+                }
+                return new PatternStringFilter(valueProvider,
+                        parser.getAttributeValue(attributeIndex));
+            case 'r':
+                if (!attributeName.equals(ATTR_REGEX)) {
+                    return null;
+                }
+                return new RegexFilter(valueProvider, parser.getAttributeValue(attributeIndex));
+        }
+        return null;
+    }
+
+    protected abstract boolean matchesValue(String value);
+
+    @Override
+    public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent,
+            int callerUid, int callerPid, String resolvedType, int receivingUid) {
+        String value = mValueProvider.getValue(resolvedComponent, intent, resolvedType);
+        return matchesValue(value);
+    }
+
+    private static abstract class ValueProvider extends FilterFactory {
+        protected ValueProvider(String tag) {
+            super(tag);
+        }
+
+        public Filter newFilter(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return StringFilter.readFromXml(this, parser);
+        }
+
+        public abstract String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType);
+    }
+
+    private static class EqualsFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public EqualsFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.equals(mFilterValue);
+        }
+    }
+
+    private static class ContainsFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public ContainsFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.contains(mFilterValue);
+        }
+    }
+
+    private static class StartsWithFilter extends StringFilter {
+        private final String mFilterValue;
+
+        public StartsWithFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mFilterValue = attrValue;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && value.startsWith(mFilterValue);
+        }
+    }
+
+    private static class PatternStringFilter extends StringFilter {
+        private final PatternMatcher mPattern;
+
+        public PatternStringFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mPattern = new PatternMatcher(attrValue, PatternMatcher.PATTERN_SIMPLE_GLOB);
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && mPattern.match(value);
+        }
+    }
+
+    private static class RegexFilter extends StringFilter {
+        private final Pattern mPattern;
+
+        public RegexFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            this.mPattern = Pattern.compile(attrValue);
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return value != null && mPattern.matcher(value).matches();
+        }
+    }
+
+    private static class IsNullFilter extends StringFilter {
+        private final boolean mIsNull;
+
+        public IsNullFilter(ValueProvider valueProvider, String attrValue) {
+            super(valueProvider);
+            mIsNull = Boolean.parseBoolean(attrValue);
+        }
+
+        public IsNullFilter(ValueProvider valueProvider, boolean isNull) {
+            super(valueProvider);
+            mIsNull = isNull;
+        }
+
+        @Override
+        public boolean matchesValue(String value) {
+            return (value == null) == mIsNull;
+        }
+    }
+
+    public static final ValueProvider COMPONENT = new ValueProvider("component") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            if (resolvedComponent != null) {
+                return resolvedComponent.flattenToString();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            if (resolvedComponent != null) {
+                return resolvedComponent.getClassName();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            if (resolvedComponent != null) {
+                return resolvedComponent.getPackageName();
+            }
+            return null;
+        }
+    };
+
+    public static final FilterFactory ACTION = new ValueProvider("action") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            return intent.getAction();
+        }
+    };
+
+    public static final ValueProvider DATA = new ValueProvider("data") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.toString();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            return resolvedType;
+        }
+    };
+
+    public static final ValueProvider SCHEME = new ValueProvider("scheme") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getScheme();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getSchemeSpecificPart();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider HOST = new ValueProvider("host") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getHost();
+            }
+            return null;
+        }
+    };
+
+    public static final ValueProvider PATH = new ValueProvider("path") {
+        @Override
+        public String getValue(ComponentName resolvedComponent, Intent intent,
+                String resolvedType) {
+            Uri data = intent.getData();
+            if (data != null) {
+                return data.getPath();
+            }
+            return null;
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/input/InputApplicationHandle.java b/services/core/java/com/android/server/input/InputApplicationHandle.java
new file mode 100644
index 0000000..42c1052
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputApplicationHandle.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+/**
+ * Functions as a handle for an application that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's
+ * application window token.
+ * @hide
+ */
+public final class InputApplicationHandle {
+    // Pointer to the native input application handle.
+    // This field is lazily initialized via JNI.
+    @SuppressWarnings("unused")
+    private int ptr;
+
+    // The window manager's application window token.
+    public final Object appWindowToken;
+
+    // Application name.
+    public String name;
+
+    // Dispatching timeout.
+    public long dispatchingTimeoutNanos;
+
+    private native void nativeDispose();
+
+    public InputApplicationHandle(Object appWindowToken) {
+        this.appWindowToken = appWindowToken;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDispose();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
new file mode 100644
index 0000000..3145805
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -0,0 +1,1642 @@
+/*
+ * Copyright (C) 2010 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.input;
+
+import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
+import com.android.server.Watchdog;
+import com.android.server.display.DisplayManagerService;
+import com.android.server.display.DisplayViewport;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.Manifest;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.database.ContentObserver;
+import android.hardware.input.IInputDevicesChangedListener;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.hardware.input.KeyboardLayout;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+import android.view.IInputFilter;
+import android.view.IInputFilterHost;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.PointerIcon;
+import android.view.ViewConfiguration;
+import android.view.WindowManagerPolicy;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import libcore.io.Streams;
+import libcore.util.Objects;
+
+/*
+ * Wraps the C++ InputManager and provides its callbacks.
+ */
+public class InputManagerService extends IInputManager.Stub
+        implements Watchdog.Monitor, DisplayManagerService.InputManagerFuncs {
+    static final String TAG = "InputManager";
+    static final boolean DEBUG = false;
+
+    private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
+
+    private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
+    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
+    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
+    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
+    private static final int MSG_RELOAD_DEVICE_ALIASES = 5;
+
+    // Pointer to native input manager service object.
+    private final int mPtr;
+
+    private final Context mContext;
+    private final InputManagerHandler mHandler;
+
+    private WindowManagerCallbacks mWindowManagerCallbacks;
+    private WiredAccessoryCallbacks mWiredAccessoryCallbacks;
+    private boolean mSystemReady;
+    private NotificationManager mNotificationManager;
+
+    // Persistent data store.  Must be locked each time during use.
+    private final PersistentDataStore mDataStore = new PersistentDataStore();
+
+    // List of currently registered input devices changed listeners by process id.
+    private Object mInputDevicesLock = new Object();
+    private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock
+    private InputDevice[] mInputDevices = new InputDevice[0];
+    private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners =
+            new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock
+    private final ArrayList<InputDevicesChangedListenerRecord>
+            mTempInputDevicesChangedListenersToNotify =
+                    new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+    private final ArrayList<InputDevice>
+            mTempFullKeyboards = new ArrayList<InputDevice>(); // handler thread only
+    private boolean mKeyboardLayoutNotificationShown;
+    private PendingIntent mKeyboardLayoutIntent;
+    private Toast mSwitchedKeyboardLayoutToast;
+
+    // State for vibrator tokens.
+    private Object mVibratorLock = new Object();
+    private HashMap<IBinder, VibratorToken> mVibratorTokens =
+            new HashMap<IBinder, VibratorToken>();
+    private int mNextVibratorTokenValue;
+
+    // State for the currently installed input filter.
+    final Object mInputFilterLock = new Object();
+    IInputFilter mInputFilter; // guarded by mInputFilterLock
+    InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
+
+    private static native int nativeInit(InputManagerService service,
+            Context context, MessageQueue messageQueue);
+    private static native void nativeStart(int ptr);
+    private static native void nativeSetDisplayViewport(int ptr, boolean external,
+            int displayId, int rotation,
+            int logicalLeft, int logicalTop, int logicalRight, int logicalBottom,
+            int physicalLeft, int physicalTop, int physicalRight, int physicalBottom,
+            int deviceWidth, int deviceHeight);
+
+    private static native int nativeGetScanCodeState(int ptr,
+            int deviceId, int sourceMask, int scanCode);
+    private static native int nativeGetKeyCodeState(int ptr,
+            int deviceId, int sourceMask, int keyCode);
+    private static native int nativeGetSwitchState(int ptr,
+            int deviceId, int sourceMask, int sw);
+    private static native boolean nativeHasKeys(int ptr,
+            int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
+    private static native void nativeRegisterInputChannel(int ptr, InputChannel inputChannel,
+            InputWindowHandle inputWindowHandle, boolean monitor);
+    private static native void nativeUnregisterInputChannel(int ptr, InputChannel inputChannel);
+    private static native void nativeSetInputFilterEnabled(int ptr, boolean enable);
+    private static native int nativeInjectInputEvent(int ptr, InputEvent event,
+            int injectorPid, int injectorUid, int syncMode, int timeoutMillis,
+            int policyFlags);
+    private static native void nativeSetInputWindows(int ptr, InputWindowHandle[] windowHandles);
+    private static native void nativeSetInputDispatchMode(int ptr, boolean enabled, boolean frozen);
+    private static native void nativeSetSystemUiVisibility(int ptr, int visibility);
+    private static native void nativeSetFocusedApplication(int ptr,
+            InputApplicationHandle application);
+    private static native boolean nativeTransferTouchFocus(int ptr,
+            InputChannel fromChannel, InputChannel toChannel);
+    private static native void nativeSetPointerSpeed(int ptr, int speed);
+    private static native void nativeSetShowTouches(int ptr, boolean enabled);
+    private static native void nativeVibrate(int ptr, int deviceId, long[] pattern,
+            int repeat, int token);
+    private static native void nativeCancelVibrate(int ptr, int deviceId, int token);
+    private static native void nativeReloadKeyboardLayouts(int ptr);
+    private static native void nativeReloadDeviceAliases(int ptr);
+    private static native String nativeDump(int ptr);
+    private static native void nativeMonitor(int ptr);
+
+    // Input event injection constants defined in InputDispatcher.h.
+    private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
+    private static final int INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1;
+    private static final int INPUT_EVENT_INJECTION_FAILED = 2;
+    private static final int INPUT_EVENT_INJECTION_TIMED_OUT = 3;
+
+    // Maximum number of milliseconds to wait for input event injection.
+    private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
+
+    // Key states (may be returned by queries about the current state of a
+    // particular key code, scan code or switch).
+
+    /** The key state is unknown or the requested key itself is not supported. */
+    public static final int KEY_STATE_UNKNOWN = -1;
+
+    /** The key is up. /*/
+    public static final int KEY_STATE_UP = 0;
+
+    /** The key is down. */
+    public static final int KEY_STATE_DOWN = 1;
+
+    /** The key is down but is a virtual key press that is being emulated by the system. */
+    public static final int KEY_STATE_VIRTUAL = 2;
+
+    /** Scan code: Mouse / trackball button. */
+    public static final int BTN_MOUSE = 0x110;
+
+    // Switch code values must match bionic/libc/kernel/common/linux/input.h
+    /** Switch code: Lid switch.  When set, lid is shut. */
+    public static final int SW_LID = 0x00;
+
+    /** Switch code: Keypad slide.  When set, keyboard is exposed. */
+    public static final int SW_KEYPAD_SLIDE = 0x0a;
+
+    /** Switch code: Headphone.  When set, headphone is inserted. */
+    public static final int SW_HEADPHONE_INSERT = 0x02;
+
+    /** Switch code: Microphone.  When set, microphone is inserted. */
+    public static final int SW_MICROPHONE_INSERT = 0x04;
+
+    /** Switch code: Headphone/Microphone Jack.  When set, something is inserted. */
+    public static final int SW_JACK_PHYSICAL_INSERT = 0x07;
+
+    public static final int SW_LID_BIT = 1 << SW_LID;
+    public static final int SW_KEYPAD_SLIDE_BIT = 1 << SW_KEYPAD_SLIDE;
+    public static final int SW_HEADPHONE_INSERT_BIT = 1 << SW_HEADPHONE_INSERT;
+    public static final int SW_MICROPHONE_INSERT_BIT = 1 << SW_MICROPHONE_INSERT;
+    public static final int SW_JACK_PHYSICAL_INSERT_BIT = 1 << SW_JACK_PHYSICAL_INSERT;
+    public static final int SW_JACK_BITS =
+            SW_HEADPHONE_INSERT_BIT | SW_MICROPHONE_INSERT_BIT | SW_JACK_PHYSICAL_INSERT_BIT;
+
+    /** Whether to use the dev/input/event or uevent subsystem for the audio jack. */
+    final boolean mUseDevInputEventForAudioJack;
+
+    public InputManagerService(Context context, Handler handler) {
+        this.mContext = context;
+        this.mHandler = new InputManagerHandler(handler.getLooper());
+
+        mUseDevInputEventForAudioJack =
+                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
+        Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
+                + mUseDevInputEventForAudioJack);
+        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
+    }
+
+    public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) {
+        mWindowManagerCallbacks = callbacks;
+    }
+
+    public void setWiredAccessoryCallbacks(WiredAccessoryCallbacks callbacks) {
+        mWiredAccessoryCallbacks = callbacks;
+    }
+
+    public void start() {
+        Slog.i(TAG, "Starting input manager");
+        nativeStart(mPtr);
+
+        // Add ourself to the Watchdog monitors.
+        Watchdog.getInstance().addMonitor(this);
+
+        registerPointerSpeedSettingObserver();
+        registerShowTouchesSettingObserver();
+
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updatePointerSpeedFromSettings();
+                updateShowTouchesFromSettings();
+            }
+        }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
+
+        updatePointerSpeedFromSettings();
+        updateShowTouchesFromSettings();
+    }
+
+    // TODO(BT) Pass in paramter for bluetooth system
+    public void systemRunning() {
+        if (DEBUG) {
+            Slog.d(TAG, "System ready.");
+        }
+        mNotificationManager = (NotificationManager)mContext.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        mSystemReady = true;
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                updateKeyboardLayouts();
+            }
+        }, filter, null, mHandler);
+
+        filter = new IntentFilter(BluetoothDevice.ACTION_ALIAS_CHANGED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                reloadDeviceAliases();
+            }
+        }, filter, null, mHandler);
+
+        mHandler.sendEmptyMessage(MSG_RELOAD_DEVICE_ALIASES);
+        mHandler.sendEmptyMessage(MSG_UPDATE_KEYBOARD_LAYOUTS);
+    }
+
+    private void reloadKeyboardLayouts() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading keyboard layouts.");
+        }
+        nativeReloadKeyboardLayouts(mPtr);
+    }
+
+    private void reloadDeviceAliases() {
+        if (DEBUG) {
+            Slog.d(TAG, "Reloading device names.");
+        }
+        nativeReloadDeviceAliases(mPtr);
+    }
+
+    @Override
+    public void setDisplayViewports(DisplayViewport defaultViewport,
+            DisplayViewport externalTouchViewport) {
+        if (defaultViewport.valid) {
+            setDisplayViewport(false, defaultViewport);
+        }
+
+        if (externalTouchViewport.valid) {
+            setDisplayViewport(true, externalTouchViewport);
+        } else if (defaultViewport.valid) {
+            setDisplayViewport(true, defaultViewport);
+        }
+    }
+
+    private void setDisplayViewport(boolean external, DisplayViewport viewport) {
+        nativeSetDisplayViewport(mPtr, external,
+                viewport.displayId, viewport.orientation,
+                viewport.logicalFrame.left, viewport.logicalFrame.top,
+                viewport.logicalFrame.right, viewport.logicalFrame.bottom,
+                viewport.physicalFrame.left, viewport.physicalFrame.top,
+                viewport.physicalFrame.right, viewport.physicalFrame.bottom,
+                viewport.deviceWidth, viewport.deviceHeight);
+    }
+
+    /**
+     * Gets the current state of a key or button by key code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param keyCode The key code to check.
+     * @return The key state.
+     */
+    public int getKeyCodeState(int deviceId, int sourceMask, int keyCode) {
+        return nativeGetKeyCodeState(mPtr, deviceId, sourceMask, keyCode);
+    }
+
+    /**
+     * Gets the current state of a key or button by scan code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param scanCode The scan code to check.
+     * @return The key state.
+     */
+    public int getScanCodeState(int deviceId, int sourceMask, int scanCode) {
+        return nativeGetScanCodeState(mPtr, deviceId, sourceMask, scanCode);
+    }
+    
+    /**
+     * Gets the current state of a switch by switch code.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param switchCode The switch code to check.
+     * @return The switch state.
+     */
+    public int getSwitchState(int deviceId, int sourceMask, int switchCode) {
+        return nativeGetSwitchState(mPtr, deviceId, sourceMask, switchCode);
+    }
+
+    /**
+     * Determines whether the specified key codes are supported by a particular device.
+     * @param deviceId The input device id, or -1 to consult all devices.
+     * @param sourceMask The input sources to consult, or {@link InputDevice#SOURCE_ANY} to
+     * consider all input sources.  An input device is consulted if at least one of its
+     * non-class input source bits matches the specified source mask.
+     * @param keyCodes The array of key codes to check.
+     * @param keyExists An array at least as large as keyCodes whose entries will be set
+     * to true or false based on the presence or absence of support for the corresponding
+     * key codes.
+     * @return True if the lookup was successful, false otherwise.
+     */
+    @Override // Binder call
+    public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) {
+        if (keyCodes == null) {
+            throw new IllegalArgumentException("keyCodes must not be null.");
+        }
+        if (keyExists == null || keyExists.length < keyCodes.length) {
+            throw new IllegalArgumentException("keyExists must not be null and must be at "
+                    + "least as large as keyCodes.");
+        }
+        
+        return nativeHasKeys(mPtr, deviceId, sourceMask, keyCodes, keyExists);
+    }
+    
+    /**
+     * Creates an input channel that will receive all input from the input dispatcher.
+     * @param inputChannelName The input channel name.
+     * @return The input channel.
+     */
+    public InputChannel monitorInput(String inputChannelName) {
+        if (inputChannelName == null) {
+            throw new IllegalArgumentException("inputChannelName must not be null.");
+        }
+        
+        InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
+        nativeRegisterInputChannel(mPtr, inputChannels[0], null, true);
+        inputChannels[0].dispose(); // don't need to retain the Java object reference
+        return inputChannels[1];
+    }
+
+    /**
+     * Registers an input channel so that it can be used as an input event target.
+     * @param inputChannel The input channel to register.
+     * @param inputWindowHandle The handle of the input window associated with the
+     * input channel, or null if none.
+     */
+    public void registerInputChannel(InputChannel inputChannel,
+            InputWindowHandle inputWindowHandle) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
+    }
+    
+    /**
+     * Unregisters an input channel.
+     * @param inputChannel The input channel to unregister.
+     */
+    public void unregisterInputChannel(InputChannel inputChannel) {
+        if (inputChannel == null) {
+            throw new IllegalArgumentException("inputChannel must not be null.");
+        }
+        
+        nativeUnregisterInputChannel(mPtr, inputChannel);
+    }
+
+    /**
+     * Sets an input filter that will receive all input events before they are dispatched.
+     * The input filter may then reinterpret input events or inject new ones.
+     *
+     * To ensure consistency, the input dispatcher automatically drops all events
+     * in progress whenever an input filter is installed or uninstalled.  After an input
+     * filter is uninstalled, it can no longer send input events unless it is reinstalled.
+     * Any events it attempts to send after it has been uninstalled will be dropped.
+     *
+     * @param filter The input filter, or null to remove the current filter.
+     */
+    public void setInputFilter(IInputFilter filter) {
+        synchronized (mInputFilterLock) {
+            final IInputFilter oldFilter = mInputFilter;
+            if (oldFilter == filter) {
+                return; // nothing to do
+            }
+
+            if (oldFilter != null) {
+                mInputFilter = null;
+                mInputFilterHost.disconnectLocked();
+                mInputFilterHost = null;
+                try {
+                    oldFilter.uninstall();
+                } catch (RemoteException re) {
+                    /* ignore */
+                }
+            }
+
+            if (filter != null) {
+                mInputFilter = filter;
+                mInputFilterHost = new InputFilterHost();
+                try {
+                    filter.install(mInputFilterHost);
+                } catch (RemoteException re) {
+                    /* ignore */
+                }
+            }
+
+            nativeSetInputFilterEnabled(mPtr, filter != null);
+        }
+    }
+
+    @Override // Binder call
+    public boolean injectInputEvent(InputEvent event, int mode) {
+        if (event == null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        if (mode != InputManager.INJECT_INPUT_EVENT_MODE_ASYNC
+                && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
+                && mode != InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
+            throw new IllegalArgumentException("mode is invalid");
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        final int result;
+        try {
+            result = nativeInjectInputEvent(mPtr, event, pid, uid, mode,
+                    INJECTION_TIMEOUT_MILLIS, WindowManagerPolicy.FLAG_DISABLE_KEY_REPEAT);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        switch (result) {
+            case INPUT_EVENT_INJECTION_PERMISSION_DENIED:
+                Slog.w(TAG, "Input event injection from pid " + pid + " permission denied.");
+                throw new SecurityException(
+                        "Injecting to another application requires INJECT_EVENTS permission");
+            case INPUT_EVENT_INJECTION_SUCCEEDED:
+                return true;
+            case INPUT_EVENT_INJECTION_TIMED_OUT:
+                Slog.w(TAG, "Input event injection from pid " + pid + " timed out.");
+                return false;
+            case INPUT_EVENT_INJECTION_FAILED:
+            default:
+                Slog.w(TAG, "Input event injection from pid " + pid + " failed.");
+                return false;
+        }
+    }
+
+    /**
+     * Gets information about the input device with the specified id.
+     * @param deviceId The device id.
+     * @return The input device or null if not found.
+     */
+    @Override // Binder call
+    public InputDevice getInputDevice(int deviceId) {
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            for (int i = 0; i < count; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                if (inputDevice.getId() == deviceId) {
+                    return inputDevice;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the ids of all input devices in the system.
+     * @return The input device ids.
+     */
+    @Override // Binder call
+    public int[] getInputDeviceIds() {
+        synchronized (mInputDevicesLock) {
+            final int count = mInputDevices.length;
+            int[] ids = new int[count];
+            for (int i = 0; i < count; i++) {
+                ids[i] = mInputDevices[i].getId();
+            }
+            return ids;
+        }
+    }
+
+    /**
+     * Gets all input devices in the system.
+     * @return The array of input devices.
+     */
+    public InputDevice[] getInputDevices() {
+        synchronized (mInputDevicesLock) {
+            return mInputDevices;
+        }
+    }
+
+    @Override // Binder call
+    public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+
+        synchronized (mInputDevicesLock) {
+            int callingPid = Binder.getCallingPid();
+            if (mInputDevicesChangedListeners.get(callingPid) != null) {
+                throw new SecurityException("The calling process has already "
+                        + "registered an InputDevicesChangedListener.");
+            }
+
+            InputDevicesChangedListenerRecord record =
+                    new InputDevicesChangedListenerRecord(callingPid, listener);
+            try {
+                IBinder binder = listener.asBinder();
+                binder.linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                // give up
+                throw new RuntimeException(ex);
+            }
+
+            mInputDevicesChangedListeners.put(callingPid, record);
+        }
+    }
+
+    private void onInputDevicesChangedListenerDied(int pid) {
+        synchronized (mInputDevicesLock) {
+            mInputDevicesChangedListeners.remove(pid);
+        }
+    }
+
+    // Must be called on handler.
+    private void deliverInputDevicesChanged(InputDevice[] oldInputDevices) {
+        // Scan for changes.
+        int numFullKeyboardsAdded = 0;
+        mTempInputDevicesChangedListenersToNotify.clear();
+        mTempFullKeyboards.clear();
+        final int numListeners;
+        final int[] deviceIdAndGeneration;
+        synchronized (mInputDevicesLock) {
+            if (!mInputDevicesChangedPending) {
+                return;
+            }
+            mInputDevicesChangedPending = false;
+
+            numListeners = mInputDevicesChangedListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                mTempInputDevicesChangedListenersToNotify.add(
+                        mInputDevicesChangedListeners.valueAt(i));
+            }
+
+            final int numDevices = mInputDevices.length;
+            deviceIdAndGeneration = new int[numDevices * 2];
+            for (int i = 0; i < numDevices; i++) {
+                final InputDevice inputDevice = mInputDevices[i];
+                deviceIdAndGeneration[i * 2] = inputDevice.getId();
+                deviceIdAndGeneration[i * 2 + 1] = inputDevice.getGeneration();
+
+                if (!inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+                    if (!containsInputDeviceWithDescriptor(oldInputDevices,
+                            inputDevice.getDescriptor())) {
+                        mTempFullKeyboards.add(numFullKeyboardsAdded++, inputDevice);
+                    } else {
+                        mTempFullKeyboards.add(inputDevice);
+                    }
+                }
+            }
+        }
+
+        // Notify listeners.
+        for (int i = 0; i < numListeners; i++) {
+            mTempInputDevicesChangedListenersToNotify.get(i).notifyInputDevicesChanged(
+                    deviceIdAndGeneration);
+        }
+        mTempInputDevicesChangedListenersToNotify.clear();
+
+        // Check for missing keyboard layouts.
+        if (mNotificationManager != null) {
+            final int numFullKeyboards = mTempFullKeyboards.size();
+            boolean missingLayoutForExternalKeyboard = false;
+            boolean missingLayoutForExternalKeyboardAdded = false;
+            synchronized (mDataStore) {
+                for (int i = 0; i < numFullKeyboards; i++) {
+                    final InputDevice inputDevice = mTempFullKeyboards.get(i);
+                    if (mDataStore.getCurrentKeyboardLayout(inputDevice.getDescriptor()) == null) {
+                        missingLayoutForExternalKeyboard = true;
+                        if (i < numFullKeyboardsAdded) {
+                            missingLayoutForExternalKeyboardAdded = true;
+                        }
+                    }
+                }
+            }
+            if (missingLayoutForExternalKeyboard) {
+                if (missingLayoutForExternalKeyboardAdded) {
+                    showMissingKeyboardLayoutNotification();
+                }
+            } else if (mKeyboardLayoutNotificationShown) {
+                hideMissingKeyboardLayoutNotification();
+            }
+        }
+        mTempFullKeyboards.clear();
+    }
+
+    // Must be called on handler.
+    private void showMissingKeyboardLayoutNotification() {
+        if (!mKeyboardLayoutNotificationShown) {
+            if (mKeyboardLayoutIntent == null) {
+                final Intent intent = new Intent("android.settings.INPUT_METHOD_SETTINGS");
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                        | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                mKeyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                        intent, 0, null, UserHandle.CURRENT);
+            }
+
+            Resources r = mContext.getResources();
+            Notification notification = new Notification.Builder(mContext)
+                    .setContentTitle(r.getString(
+                            R.string.select_keyboard_layout_notification_title))
+                    .setContentText(r.getString(
+                            R.string.select_keyboard_layout_notification_message))
+                    .setContentIntent(mKeyboardLayoutIntent)
+                    .setSmallIcon(R.drawable.ic_settings_language)
+                    .setPriority(Notification.PRIORITY_LOW)
+                    .build();
+            mNotificationManager.notifyAsUser(null,
+                    R.string.select_keyboard_layout_notification_title,
+                    notification, UserHandle.ALL);
+            mKeyboardLayoutNotificationShown = true;
+        }
+    }
+
+    // Must be called on handler.
+    private void hideMissingKeyboardLayoutNotification() {
+        if (mKeyboardLayoutNotificationShown) {
+            mKeyboardLayoutNotificationShown = false;
+            mNotificationManager.cancelAsUser(null,
+                    R.string.select_keyboard_layout_notification_title,
+                    UserHandle.ALL);
+        }
+    }
+
+    // Must be called on handler.
+    private void updateKeyboardLayouts() {
+        // Scan all input devices state for keyboard layouts that have been uninstalled.
+        final HashSet<String> availableKeyboardLayouts = new HashSet<String>();
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    String descriptor, String label, String collection, int keyboardLayoutResId) {
+                availableKeyboardLayouts.add(descriptor);
+            }
+        });
+        synchronized (mDataStore) {
+            try {
+                mDataStore.removeUninstalledKeyboardLayouts(availableKeyboardLayouts);
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+
+        // Reload keyboard layouts.
+        reloadKeyboardLayouts();
+    }
+
+    private static boolean containsInputDeviceWithDescriptor(InputDevice[] inputDevices,
+            String descriptor) {
+        final int numDevices = inputDevices.length;
+        for (int i = 0; i < numDevices; i++) {
+            final InputDevice inputDevice = inputDevices[i];
+            if (inputDevice.getDescriptor().equals(descriptor)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override // Binder call
+    public KeyboardLayout[] getKeyboardLayouts() {
+        final ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>();
+        visitAllKeyboardLayouts(new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    String descriptor, String label, String collection, int keyboardLayoutResId) {
+                list.add(new KeyboardLayout(descriptor, label, collection));
+            }
+        });
+        return list.toArray(new KeyboardLayout[list.size()]);
+    }
+
+    @Override // Binder call
+    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        final KeyboardLayout[] result = new KeyboardLayout[1];
+        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    String descriptor, String label, String collection, int keyboardLayoutResId) {
+                result[0] = new KeyboardLayout(descriptor, label, collection);
+            }
+        });
+        if (result[0] == null) {
+            Log.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+        }
+        return result[0];
+    }
+
+    private void visitAllKeyboardLayouts(KeyboardLayoutVisitor visitor) {
+        final PackageManager pm = mContext.getPackageManager();
+        Intent intent = new Intent(InputManager.ACTION_QUERY_KEYBOARD_LAYOUTS);
+        for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent,
+                PackageManager.GET_META_DATA)) {
+            visitKeyboardLayoutsInPackage(pm, resolveInfo.activityInfo, null, visitor);
+        }
+    }
+
+    private void visitKeyboardLayout(String keyboardLayoutDescriptor,
+            KeyboardLayoutVisitor visitor) {
+        KeyboardLayoutDescriptor d = KeyboardLayoutDescriptor.parse(keyboardLayoutDescriptor);
+        if (d != null) {
+            final PackageManager pm = mContext.getPackageManager();
+            try {
+                ActivityInfo receiver = pm.getReceiverInfo(
+                        new ComponentName(d.packageName, d.receiverName),
+                        PackageManager.GET_META_DATA);
+                visitKeyboardLayoutsInPackage(pm, receiver, d.keyboardLayoutName, visitor);
+            } catch (NameNotFoundException ex) {
+            }
+        }
+    }
+
+    private void visitKeyboardLayoutsInPackage(PackageManager pm, ActivityInfo receiver,
+            String keyboardName, KeyboardLayoutVisitor visitor) {
+        Bundle metaData = receiver.metaData;
+        if (metaData == null) {
+            return;
+        }
+
+        int configResId = metaData.getInt(InputManager.META_DATA_KEYBOARD_LAYOUTS);
+        if (configResId == 0) {
+            Log.w(TAG, "Missing meta-data '" + InputManager.META_DATA_KEYBOARD_LAYOUTS
+                    + "' on receiver " + receiver.packageName + "/" + receiver.name);
+            return;
+        }
+
+        CharSequence receiverLabel = receiver.loadLabel(pm);
+        String collection = receiverLabel != null ? receiverLabel.toString() : "";
+
+        try {
+            Resources resources = pm.getResourcesForApplication(receiver.applicationInfo);
+            XmlResourceParser parser = resources.getXml(configResId);
+            try {
+                XmlUtils.beginDocument(parser, "keyboard-layouts");
+
+                for (;;) {
+                    XmlUtils.nextElement(parser);
+                    String element = parser.getName();
+                    if (element == null) {
+                        break;
+                    }
+                    if (element.equals("keyboard-layout")) {
+                        TypedArray a = resources.obtainAttributes(
+                                parser, com.android.internal.R.styleable.KeyboardLayout);
+                        try {
+                            String name = a.getString(
+                                    com.android.internal.R.styleable.KeyboardLayout_name);
+                            String label = a.getString(
+                                    com.android.internal.R.styleable.KeyboardLayout_label);
+                            int keyboardLayoutResId = a.getResourceId(
+                                    com.android.internal.R.styleable.KeyboardLayout_keyboardLayout,
+                                    0);
+                            if (name == null || label == null || keyboardLayoutResId == 0) {
+                                Log.w(TAG, "Missing required 'name', 'label' or 'keyboardLayout' "
+                                        + "attributes in keyboard layout "
+                                        + "resource from receiver "
+                                        + receiver.packageName + "/" + receiver.name);
+                            } else {
+                                String descriptor = KeyboardLayoutDescriptor.format(
+                                        receiver.packageName, receiver.name, name);
+                                if (keyboardName == null || name.equals(keyboardName)) {
+                                    visitor.visitKeyboardLayout(resources, descriptor,
+                                            label, collection, keyboardLayoutResId);
+                                }
+                            }
+                        } finally {
+                            a.recycle();
+                        }
+                    } else {
+                        Log.w(TAG, "Skipping unrecognized element '" + element
+                                + "' in keyboard layout resource from receiver "
+                                + receiver.packageName + "/" + receiver.name);
+                    }
+                }
+            } finally {
+                parser.close();
+            }
+        } catch (Exception ex) {
+            Log.w(TAG, "Could not parse keyboard layout resource from receiver "
+                    + receiver.packageName + "/" + receiver.name, ex);
+        }
+    }
+
+    @Override // Binder call
+    public String getCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            return mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+        }
+    }
+
+    @Override // Binder call
+    public void setCurrentKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "setCurrentKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            try {
+                if (mDataStore.setCurrentKeyboardLayout(
+                        inputDeviceDescriptor, keyboardLayoutDescriptor)) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    @Override // Binder call
+    public String[] getKeyboardLayoutsForInputDevice(String inputDeviceDescriptor) {
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            return mDataStore.getKeyboardLayouts(inputDeviceDescriptor);
+        }
+    }
+
+    @Override // Binder call
+    public void addKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "addKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+                if (mDataStore.addKeyboardLayout(inputDeviceDescriptor, keyboardLayoutDescriptor)
+                        && !Objects.equal(oldLayout,
+                                mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void removeKeyboardLayoutForInputDevice(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT,
+                "removeKeyboardLayoutForInputDevice()")) {
+            throw new SecurityException("Requires SET_KEYBOARD_LAYOUT permission");
+        }
+        if (inputDeviceDescriptor == null) {
+            throw new IllegalArgumentException("inputDeviceDescriptor must not be null");
+        }
+        if (keyboardLayoutDescriptor == null) {
+            throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null");
+        }
+
+        synchronized (mDataStore) {
+            try {
+                String oldLayout = mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor);
+                if (mDataStore.removeKeyboardLayout(inputDeviceDescriptor,
+                        keyboardLayoutDescriptor)
+                        && !Objects.equal(oldLayout,
+                                mDataStore.getCurrentKeyboardLayout(inputDeviceDescriptor))) {
+                    mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
+    }
+
+    // Must be called on handler.
+    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+        final InputDevice device = getInputDevice(deviceId);
+        if (device != null) {
+            final String inputDeviceDescriptor = device.getDescriptor();
+            final boolean changed;
+            final String keyboardLayoutDescriptor;
+            synchronized (mDataStore) {
+                try {
+                    changed = mDataStore.switchKeyboardLayout(inputDeviceDescriptor, direction);
+                    keyboardLayoutDescriptor = mDataStore.getCurrentKeyboardLayout(
+                            inputDeviceDescriptor);
+                } finally {
+                    mDataStore.saveIfNeeded();
+                }
+            }
+
+            if (changed) {
+                if (mSwitchedKeyboardLayoutToast != null) {
+                    mSwitchedKeyboardLayoutToast.cancel();
+                    mSwitchedKeyboardLayoutToast = null;
+                }
+                if (keyboardLayoutDescriptor != null) {
+                    KeyboardLayout keyboardLayout = getKeyboardLayout(keyboardLayoutDescriptor);
+                    if (keyboardLayout != null) {
+                        mSwitchedKeyboardLayoutToast = Toast.makeText(
+                                mContext, keyboardLayout.getLabel(), Toast.LENGTH_SHORT);
+                        mSwitchedKeyboardLayoutToast.show();
+                    }
+                }
+
+                reloadKeyboardLayouts();
+            }
+        }
+    }
+
+    public void setInputWindows(InputWindowHandle[] windowHandles) {
+        nativeSetInputWindows(mPtr, windowHandles);
+    }
+    
+    public void setFocusedApplication(InputApplicationHandle application) {
+        nativeSetFocusedApplication(mPtr, application);
+    }
+    
+    public void setInputDispatchMode(boolean enabled, boolean frozen) {
+        nativeSetInputDispatchMode(mPtr, enabled, frozen);
+    }
+
+    public void setSystemUiVisibility(int visibility) {
+        nativeSetSystemUiVisibility(mPtr, visibility);
+    }
+
+    /**
+     * Atomically transfers touch focus from one window to another as identified by
+     * their input channels.  It is possible for multiple windows to have
+     * touch focus if they support split touch dispatch
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+     * method only transfers touch focus of the specified window without affecting
+     * other windows that may also have touch focus at the same time.
+     * @param fromChannel The channel of a window that currently has touch focus.
+     * @param toChannel The channel of the window that should receive touch focus in
+     * place of the first.
+     * @return True if the transfer was successful.  False if the window with the
+     * specified channel did not actually have touch focus at the time of the request.
+     */
+    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
+        if (fromChannel == null) {
+            throw new IllegalArgumentException("fromChannel must not be null.");
+        }
+        if (toChannel == null) {
+            throw new IllegalArgumentException("toChannel must not be null.");
+        }
+        return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+    }
+
+    @Override // Binder call
+    public void tryPointerSpeed(int speed) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_POINTER_SPEED,
+                "tryPointerSpeed()")) {
+            throw new SecurityException("Requires SET_POINTER_SPEED permission");
+        }
+
+        if (speed < InputManager.MIN_POINTER_SPEED || speed > InputManager.MAX_POINTER_SPEED) {
+            throw new IllegalArgumentException("speed out of range");
+        }
+
+        setPointerSpeedUnchecked(speed);
+    }
+
+    public void updatePointerSpeedFromSettings() {
+        int speed = getPointerSpeedSetting();
+        setPointerSpeedUnchecked(speed);
+    }
+
+    private void setPointerSpeedUnchecked(int speed) {
+        speed = Math.min(Math.max(speed, InputManager.MIN_POINTER_SPEED),
+                InputManager.MAX_POINTER_SPEED);
+        nativeSetPointerSpeed(mPtr, speed);
+    }
+
+    private void registerPointerSpeedSettingObserver() {
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.POINTER_SPEED), true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updatePointerSpeedFromSettings();
+                    }
+                }, UserHandle.USER_ALL);
+    }
+
+    private int getPointerSpeedSetting() {
+        int speed = InputManager.DEFAULT_POINTER_SPEED;
+        try {
+            speed = Settings.System.getIntForUser(mContext.getContentResolver(),
+                    Settings.System.POINTER_SPEED, UserHandle.USER_CURRENT);
+        } catch (SettingNotFoundException snfe) {
+        }
+        return speed;
+    }
+
+    public void updateShowTouchesFromSettings() {
+        int setting = getShowTouchesSetting(0);
+        nativeSetShowTouches(mPtr, setting != 0);
+    }
+
+    private void registerShowTouchesSettingObserver() {
+        mContext.getContentResolver().registerContentObserver(
+                Settings.System.getUriFor(Settings.System.SHOW_TOUCHES), true,
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        updateShowTouchesFromSettings();
+                    }
+                }, UserHandle.USER_ALL);
+    }
+
+    private int getShowTouchesSetting(int defaultValue) {
+        int result = defaultValue;
+        try {
+            result = Settings.System.getIntForUser(mContext.getContentResolver(),
+                    Settings.System.SHOW_TOUCHES, UserHandle.USER_CURRENT);
+        } catch (SettingNotFoundException snfe) {
+        }
+        return result;
+    }
+
+    // Binder call
+    @Override
+    public void vibrate(int deviceId, long[] pattern, int repeat, IBinder token) {
+        if (repeat >= pattern.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+
+        VibratorToken v;
+        synchronized (mVibratorLock) {
+            v = mVibratorTokens.get(token);
+            if (v == null) {
+                v = new VibratorToken(deviceId, token, mNextVibratorTokenValue++);
+                try {
+                    token.linkToDeath(v, 0);
+                } catch (RemoteException ex) {
+                    // give up
+                    throw new RuntimeException(ex);
+                }
+                mVibratorTokens.put(token, v);
+            }
+        }
+
+        synchronized (v) {
+            v.mVibrating = true;
+            nativeVibrate(mPtr, deviceId, pattern, repeat, v.mTokenValue);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void cancelVibrate(int deviceId, IBinder token) {
+        VibratorToken v;
+        synchronized (mVibratorLock) {
+            v = mVibratorTokens.get(token);
+            if (v == null || v.mDeviceId != deviceId) {
+                return; // nothing to cancel
+            }
+        }
+
+        cancelVibrateIfNeeded(v);
+    }
+
+    void onVibratorTokenDied(VibratorToken v) {
+        synchronized (mVibratorLock) {
+            mVibratorTokens.remove(v.mToken);
+        }
+
+        cancelVibrateIfNeeded(v);
+    }
+
+    private void cancelVibrateIfNeeded(VibratorToken v) {
+        synchronized (v) {
+            if (v.mVibrating) {
+                nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+                v.mVibrating = false;
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump InputManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("INPUT MANAGER (dumpsys input)\n");
+        String dumpStr = nativeDump(mPtr);
+        if (dumpStr != null) {
+            pw.println(dumpStr);
+        }
+    }
+
+    private boolean checkCallingPermission(String permission, String func) {
+        // Quick check: if the calling permission is me, it's all okay.
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return true;
+        }
+
+        if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires " + permission;
+        Slog.w(TAG, msg);
+        return false;
+    }
+
+    // Called by the heartbeat to ensure locks are not held indefinitely (for deadlock detection).
+    @Override
+    public void monitor() {
+        synchronized (mInputFilterLock) { }
+        nativeMonitor(mPtr);
+    }
+
+    // Native callback.
+    private void notifyConfigurationChanged(long whenNanos) {
+        mWindowManagerCallbacks.notifyConfigurationChanged();
+    }
+
+    // Native callback.
+    private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
+        synchronized (mInputDevicesLock) {
+            if (!mInputDevicesChangedPending) {
+                mInputDevicesChangedPending = true;
+                mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
+                        mInputDevices).sendToTarget();
+            }
+
+            mInputDevices = inputDevices;
+        }
+    }
+
+    // Native callback.
+    private void notifySwitch(long whenNanos, int switchValues, int switchMask) {
+        if (DEBUG) {
+            Slog.d(TAG, "notifySwitch: values=" + Integer.toHexString(switchValues)
+                    + ", mask=" + Integer.toHexString(switchMask));
+        }
+
+        if ((switchMask & SW_LID_BIT) != 0) {
+            final boolean lidOpen = ((switchValues & SW_LID_BIT) == 0);
+            mWindowManagerCallbacks.notifyLidSwitchChanged(whenNanos, lidOpen);
+        }
+
+        if (mUseDevInputEventForAudioJack && (switchMask & SW_JACK_BITS) != 0) {
+            mWiredAccessoryCallbacks.notifyWiredAccessoryChanged(whenNanos, switchValues,
+                    switchMask);
+        }
+    }
+
+    // Native callback.
+    private void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
+        mWindowManagerCallbacks.notifyInputChannelBroken(inputWindowHandle);
+    }
+
+    // Native callback.
+    private long notifyANR(InputApplicationHandle inputApplicationHandle,
+            InputWindowHandle inputWindowHandle, String reason) {
+        return mWindowManagerCallbacks.notifyANR(
+                inputApplicationHandle, inputWindowHandle, reason);
+    }
+
+    // Native callback.
+    final boolean filterInputEvent(InputEvent event, int policyFlags) {
+        synchronized (mInputFilterLock) {
+            if (mInputFilter != null) {
+                try {
+                    mInputFilter.filterInputEvent(event, policyFlags);
+                } catch (RemoteException e) {
+                    /* ignore */
+                }
+                return false;
+            }
+        }
+        event.recycle();
+        return true;
+    }
+
+    // Native callback.
+    private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn) {
+        return mWindowManagerCallbacks.interceptKeyBeforeQueueing(
+                event, policyFlags, isScreenOn);
+    }
+
+    // Native callback.
+    private int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) {
+        return mWindowManagerCallbacks.interceptMotionBeforeQueueingWhenScreenOff(policyFlags);
+    }
+
+    // Native callback.
+    private long interceptKeyBeforeDispatching(InputWindowHandle focus,
+            KeyEvent event, int policyFlags) {
+        return mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event, policyFlags);
+    }
+
+    // Native callback.
+    private KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+            KeyEvent event, int policyFlags) {
+        return mWindowManagerCallbacks.dispatchUnhandledKey(focus, event, policyFlags);
+    }
+
+    // Native callback.
+    private boolean checkInjectEventsPermission(int injectorPid, int injectorUid) {
+        return mContext.checkPermission(android.Manifest.permission.INJECT_EVENTS,
+                injectorPid, injectorUid) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    // Native callback.
+    private int getVirtualKeyQuietTimeMillis() {
+        return mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_virtualKeyQuietTimeMillis);
+    }
+
+    // Native callback.
+    private String[] getExcludedDeviceNames() {
+        ArrayList<String> names = new ArrayList<String>();
+
+        // Read partner-provided list of excluded input devices
+        XmlPullParser parser = null;
+        // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
+        File confFile = new File(Environment.getRootDirectory(), EXCLUDED_DEVICES_PATH);
+        FileReader confreader = null;
+        try {
+            confreader = new FileReader(confFile);
+            parser = Xml.newPullParser();
+            parser.setInput(confreader);
+            XmlUtils.beginDocument(parser, "devices");
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+                if (!"device".equals(parser.getName())) {
+                    break;
+                }
+                String name = parser.getAttributeValue(null, "name");
+                if (name != null) {
+                    names.add(name);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // It's ok if the file does not exist.
+        } catch (Exception e) {
+            Slog.e(TAG, "Exception while parsing '" + confFile.getAbsolutePath() + "'", e);
+        } finally {
+            try { if (confreader != null) confreader.close(); } catch (IOException e) { }
+        }
+
+        return names.toArray(new String[names.size()]);
+    }
+
+    // Native callback.
+    private int getKeyRepeatTimeout() {
+        return ViewConfiguration.getKeyRepeatTimeout();
+    }
+
+    // Native callback.
+    private int getKeyRepeatDelay() {
+        return ViewConfiguration.getKeyRepeatDelay();
+    }
+
+    // Native callback.
+    private int getHoverTapTimeout() {
+        return ViewConfiguration.getHoverTapTimeout();
+    }
+
+    // Native callback.
+    private int getHoverTapSlop() {
+        return ViewConfiguration.getHoverTapSlop();
+    }
+
+    // Native callback.
+    private int getDoubleTapTimeout() {
+        return ViewConfiguration.getDoubleTapTimeout();
+    }
+
+    // Native callback.
+    private int getLongPressTimeout() {
+        return ViewConfiguration.getLongPressTimeout();
+    }
+
+    // Native callback.
+    private int getPointerLayer() {
+        return mWindowManagerCallbacks.getPointerLayer();
+    }
+
+    // Native callback.
+    private PointerIcon getPointerIcon() {
+        return PointerIcon.getDefaultIcon(mContext);
+    }
+
+    // Native callback.
+    private String[] getKeyboardLayoutOverlay(String inputDeviceDescriptor) {
+        if (!mSystemReady) {
+            return null;
+        }
+
+        String keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(
+                inputDeviceDescriptor);
+        if (keyboardLayoutDescriptor == null) {
+            return null;
+        }
+
+        final String[] result = new String[2];
+        visitKeyboardLayout(keyboardLayoutDescriptor, new KeyboardLayoutVisitor() {
+            @Override
+            public void visitKeyboardLayout(Resources resources,
+                    String descriptor, String label, String collection, int keyboardLayoutResId) {
+                try {
+                    result[0] = descriptor;
+                    result[1] = Streams.readFully(new InputStreamReader(
+                            resources.openRawResource(keyboardLayoutResId)));
+                } catch (IOException ex) {
+                } catch (NotFoundException ex) {
+                }
+            }
+        });
+        if (result[0] == null) {
+            Log.w(TAG, "Could not get keyboard layout with descriptor '"
+                    + keyboardLayoutDescriptor + "'.");
+            return null;
+        }
+        return result;
+    }
+
+    // Native callback.
+    private String getDeviceAlias(String uniqueId) {
+        if (BluetoothAdapter.checkBluetoothAddress(uniqueId)) {
+            // TODO(BT) mBluetoothService.getRemoteAlias(uniqueId)
+            return null;
+        }
+        return null;
+    }
+
+    /**
+     * Callback interface implemented by the Window Manager.
+     */
+    public interface WindowManagerCallbacks {
+        public void notifyConfigurationChanged();
+
+        public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+
+        public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle);
+
+        public long notifyANR(InputApplicationHandle inputApplicationHandle,
+                InputWindowHandle inputWindowHandle, String reason);
+
+        public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn);
+
+        public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags);
+
+        public long interceptKeyBeforeDispatching(InputWindowHandle focus,
+                KeyEvent event, int policyFlags);
+
+        public KeyEvent dispatchUnhandledKey(InputWindowHandle focus,
+                KeyEvent event, int policyFlags);
+
+        public int getPointerLayer();
+    }
+
+    /**
+     * Callback interface implemented by WiredAccessoryObserver.
+     */
+    public interface WiredAccessoryCallbacks {
+        public void notifyWiredAccessoryChanged(long whenNanos, int switchValues, int switchMask);
+    }
+
+    /**
+     * Private handler for the input manager.
+     */
+    private final class InputManagerHandler extends Handler {
+        public InputManagerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_DELIVER_INPUT_DEVICES_CHANGED:
+                    deliverInputDevicesChanged((InputDevice[])msg.obj);
+                    break;
+                case MSG_SWITCH_KEYBOARD_LAYOUT:
+                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                    break;
+                case MSG_RELOAD_KEYBOARD_LAYOUTS:
+                    reloadKeyboardLayouts();
+                    break;
+                case MSG_UPDATE_KEYBOARD_LAYOUTS:
+                    updateKeyboardLayouts();
+                    break;
+                case MSG_RELOAD_DEVICE_ALIASES:
+                    reloadDeviceAliases();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Hosting interface for input filters to call back into the input manager.
+     */
+    private final class InputFilterHost extends IInputFilterHost.Stub {
+        private boolean mDisconnected;
+
+        public void disconnectLocked() {
+            mDisconnected = true;
+        }
+
+        @Override
+        public void sendInputEvent(InputEvent event, int policyFlags) {
+            if (event == null) {
+                throw new IllegalArgumentException("event must not be null");
+            }
+
+            synchronized (mInputFilterLock) {
+                if (!mDisconnected) {
+                    nativeInjectInputEvent(mPtr, event, 0, 0,
+                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0,
+                            policyFlags | WindowManagerPolicy.FLAG_FILTERED);
+                }
+            }
+        }
+    }
+
+    private static final class KeyboardLayoutDescriptor {
+        public String packageName;
+        public String receiverName;
+        public String keyboardLayoutName;
+
+        public static String format(String packageName,
+                String receiverName, String keyboardName) {
+            return packageName + "/" + receiverName + "/" + keyboardName;
+        }
+
+        public static KeyboardLayoutDescriptor parse(String descriptor) {
+            int pos = descriptor.indexOf('/');
+            if (pos < 0 || pos + 1 == descriptor.length()) {
+                return null;
+            }
+            int pos2 = descriptor.indexOf('/', pos + 1);
+            if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) {
+                return null;
+            }
+
+            KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor();
+            result.packageName = descriptor.substring(0, pos);
+            result.receiverName = descriptor.substring(pos + 1, pos2);
+            result.keyboardLayoutName = descriptor.substring(pos2 + 1);
+            return result;
+        }
+    }
+
+    private interface KeyboardLayoutVisitor {
+        void visitKeyboardLayout(Resources resources,
+                String descriptor, String label, String collection, int keyboardLayoutResId);
+    }
+
+    private final class InputDevicesChangedListenerRecord implements DeathRecipient {
+        private final int mPid;
+        private final IInputDevicesChangedListener mListener;
+
+        public InputDevicesChangedListenerRecord(int pid, IInputDevicesChangedListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Input devices changed listener for pid " + mPid + " died.");
+            }
+            onInputDevicesChangedListenerDied(mPid);
+        }
+
+        public void notifyInputDevicesChanged(int[] info) {
+            try {
+                mListener.onInputDevicesChanged(info);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process "
+                        + mPid + " that input devices changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+
+    private final class VibratorToken implements DeathRecipient {
+        public final int mDeviceId;
+        public final IBinder mToken;
+        public final int mTokenValue;
+
+        public boolean mVibrating;
+
+        public VibratorToken(int deviceId, IBinder token, int tokenValue) {
+            mDeviceId = deviceId;
+            mToken = token;
+            mTokenValue = tokenValue;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Vibrator token died.");
+            }
+            onVibratorTokenDied(this);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/InputWindowHandle.java b/services/core/java/com/android/server/input/InputWindowHandle.java
new file mode 100644
index 0000000..9eb9a33
--- /dev/null
+++ b/services/core/java/com/android/server/input/InputWindowHandle.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.graphics.Region;
+import android.view.InputChannel;
+
+/**
+ * Functions as a handle for a window that can receive input.
+ * Enables the native input dispatcher to refer indirectly to the window manager's window state.
+ * @hide
+ */
+public final class InputWindowHandle {
+    // Pointer to the native input window handle.
+    // This field is lazily initialized via JNI.
+    @SuppressWarnings("unused")
+    private int ptr;
+
+    // The input application handle.
+    public final InputApplicationHandle inputApplicationHandle;
+
+    // The window manager's window state.
+    public final Object windowState;
+
+    // The input channel associated with the window.
+    public InputChannel inputChannel;
+
+    // The window name.
+    public String name;
+
+    // Window layout params attributes.  (WindowManager.LayoutParams)
+    public int layoutParamsFlags;
+    public int layoutParamsPrivateFlags;
+    public int layoutParamsType;
+
+    // Dispatching timeout.
+    public long dispatchingTimeoutNanos;
+
+    // Window frame.
+    public int frameLeft;
+    public int frameTop;
+    public int frameRight;
+    public int frameBottom;
+
+    // Global scaling factor applied to touch events when they are dispatched
+    // to the window
+    public float scaleFactor;
+
+    // Window touchable region.
+    public final Region touchableRegion = new Region();
+
+    // Window is visible.
+    public boolean visible;
+
+    // Window can receive keys.
+    public boolean canReceiveKeys;
+
+    // Window has focus.
+    public boolean hasFocus;
+
+    // Window has wallpaper.  (window is the current wallpaper target)
+    public boolean hasWallpaper;
+
+    // Input event dispatching is paused.
+    public boolean paused;
+
+    // Window layer.
+    public int layer;
+
+    // Id of process and user that owns the window.
+    public int ownerPid;
+    public int ownerUid;
+
+    // Window input features.
+    public int inputFeatures;
+
+    // Display this input is on.
+    public final int displayId;
+
+    private native void nativeDispose();
+
+    public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
+            Object windowState, int displayId) {
+        this.inputApplicationHandle = inputApplicationHandle;
+        this.windowState = windowState;
+        this.displayId = displayId;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeDispose();
+        } finally {
+            super.finalize();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
new file mode 100644
index 0000000..71de776
--- /dev/null
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+import libcore.util.Objects;
+
+/**
+ * Manages persistent state recorded by the input manager service as an XML file.
+ * Caller must acquire lock on the data store before accessing it.
+ *
+ * File format:
+ * <code>
+ * &lt;input-mananger-state>
+ *   &lt;input-devices>
+ *     &lt;input-device descriptor="xxxxx" keyboard-layout="yyyyy" />
+ *   &gt;input-devices>
+ * &gt;/input-manager-state>
+ * </code>
+ */
+final class PersistentDataStore {
+    static final String TAG = "InputManager";
+
+    // Input device state by descriptor.
+    private final HashMap<String, InputDeviceState> mInputDevices =
+            new HashMap<String, InputDeviceState>();
+    private final AtomicFile mAtomicFile;
+
+    // True if the data has been loaded.
+    private boolean mLoaded;
+
+    // True if there are changes to be saved.
+    private boolean mDirty;
+
+    public PersistentDataStore() {
+        mAtomicFile = new AtomicFile(new File("/data/system/input-manager-state.xml"));
+    }
+
+    public void saveIfNeeded() {
+        if (mDirty) {
+            save();
+            mDirty = false;
+        }
+    }
+
+    public String getCurrentKeyboardLayout(String inputDeviceDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        return state != null ? state.getCurrentKeyboardLayout() : null;
+    }
+
+    public boolean setCurrentKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.setCurrentKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state == null) {
+            return (String[])ArrayUtils.emptyArray(String.class);
+        }
+        return state.getKeyboardLayouts();
+    }
+
+    public boolean addKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.addKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean removeKeyboardLayout(String inputDeviceDescriptor,
+            String keyboardLayoutDescriptor) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, true);
+        if (state.removeKeyboardLayout(keyboardLayoutDescriptor)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean switchKeyboardLayout(String inputDeviceDescriptor, int direction) {
+        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor, false);
+        if (state != null && state.switchKeyboardLayout(direction)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+        boolean changed = false;
+        for (InputDeviceState state : mInputDevices.values()) {
+            if (state.removeUninstalledKeyboardLayouts(availableKeyboardLayouts)) {
+                changed = true;
+            }
+        }
+        if (changed) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
+    private InputDeviceState getInputDeviceState(String inputDeviceDescriptor,
+            boolean createIfAbsent) {
+        loadIfNeeded();
+        InputDeviceState state = mInputDevices.get(inputDeviceDescriptor);
+        if (state == null && createIfAbsent) {
+            state = new InputDeviceState();
+            mInputDevices.put(inputDeviceDescriptor, state);
+            setDirty();
+        }
+        return state;
+    }
+
+    private void loadIfNeeded() {
+        if (!mLoaded) {
+            load();
+            mLoaded = true;
+        }
+    }
+
+    private void setDirty() {
+        mDirty = true;
+    }
+
+    private void clearState() {
+        mInputDevices.clear();
+    }
+
+    private void load() {
+        clearState();
+
+        final InputStream is;
+        try {
+            is = mAtomicFile.openRead();
+        } catch (FileNotFoundException ex) {
+            return;
+        }
+
+        XmlPullParser parser;
+        try {
+            parser = Xml.newPullParser();
+            parser.setInput(new BufferedInputStream(is), null);
+            loadFromXml(parser);
+        } catch (IOException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+            clearState();
+        } catch (XmlPullParserException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to load input manager persistent store data.", ex);
+            clearState();
+        } finally {
+            IoUtils.closeQuietly(is);
+        }
+    }
+
+    private void save() {
+        final FileOutputStream os;
+        try {
+            os = mAtomicFile.startWrite();
+            boolean success = false;
+            try {
+                XmlSerializer serializer = new FastXmlSerializer();
+                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
+                saveToXml(serializer);
+                serializer.flush();
+                success = true;
+            } finally {
+                if (success) {
+                    mAtomicFile.finishWrite(os);
+                } else {
+                    mAtomicFile.failWrite(os);
+                }
+            }
+        } catch (IOException ex) {
+            Slog.w(InputManagerService.TAG, "Failed to save input manager persistent store data.", ex);
+        }
+    }
+
+    private void loadFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        XmlUtils.beginDocument(parser, "input-manager-state");
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("input-devices")) {
+                loadInputDevicesFromXml(parser);
+            }
+        }
+    }
+
+    private void loadInputDevicesFromXml(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("input-device")) {
+                String descriptor = parser.getAttributeValue(null, "descriptor");
+                if (descriptor == null) {
+                    throw new XmlPullParserException(
+                            "Missing descriptor attribute on input-device.");
+                }
+                if (mInputDevices.containsKey(descriptor)) {
+                    throw new XmlPullParserException("Found duplicate input device.");
+                }
+
+                InputDeviceState state = new InputDeviceState();
+                state.loadFromXml(parser);
+                mInputDevices.put(descriptor, state);
+            }
+        }
+    }
+
+    private void saveToXml(XmlSerializer serializer) throws IOException {
+        serializer.startDocument(null, true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(null, "input-manager-state");
+        serializer.startTag(null, "input-devices");
+        for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
+            final String descriptor = entry.getKey();
+            final InputDeviceState state = entry.getValue();
+            serializer.startTag(null, "input-device");
+            serializer.attribute(null, "descriptor", descriptor);
+            state.saveToXml(serializer);
+            serializer.endTag(null, "input-device");
+        }
+        serializer.endTag(null, "input-devices");
+        serializer.endTag(null, "input-manager-state");
+        serializer.endDocument();
+    }
+
+    private static final class InputDeviceState {
+        private String mCurrentKeyboardLayout;
+        private ArrayList<String> mKeyboardLayouts = new ArrayList<String>();
+
+        public String getCurrentKeyboardLayout() {
+            return mCurrentKeyboardLayout;
+        }
+
+        public boolean setCurrentKeyboardLayout(String keyboardLayout) {
+            if (Objects.equal(mCurrentKeyboardLayout, keyboardLayout)) {
+                return false;
+            }
+            addKeyboardLayout(keyboardLayout);
+            mCurrentKeyboardLayout = keyboardLayout;
+            return true;
+        }
+
+        public String[] getKeyboardLayouts() {
+            if (mKeyboardLayouts.isEmpty()) {
+                return (String[])ArrayUtils.emptyArray(String.class);
+            }
+            return mKeyboardLayouts.toArray(new String[mKeyboardLayouts.size()]);
+        }
+
+        public boolean addKeyboardLayout(String keyboardLayout) {
+            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            if (index >= 0) {
+                return false;
+            }
+            mKeyboardLayouts.add(-index - 1, keyboardLayout);
+            if (mCurrentKeyboardLayout == null) {
+                mCurrentKeyboardLayout = keyboardLayout;
+            }
+            return true;
+        }
+
+        public boolean removeKeyboardLayout(String keyboardLayout) {
+            int index = Collections.binarySearch(mKeyboardLayouts, keyboardLayout);
+            if (index < 0) {
+                return false;
+            }
+            mKeyboardLayouts.remove(index);
+            updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, index);
+            return true;
+        }
+
+        private void updateCurrentKeyboardLayoutIfRemoved(
+                String removedKeyboardLayout, int removedIndex) {
+            if (Objects.equal(mCurrentKeyboardLayout, removedKeyboardLayout)) {
+                if (!mKeyboardLayouts.isEmpty()) {
+                    int index = removedIndex;
+                    if (index == mKeyboardLayouts.size()) {
+                        index = 0;
+                    }
+                    mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+                } else {
+                    mCurrentKeyboardLayout = null;
+                }
+            }
+        }
+
+        public boolean switchKeyboardLayout(int direction) {
+            final int size = mKeyboardLayouts.size();
+            if (size < 2) {
+                return false;
+            }
+            int index = Collections.binarySearch(mKeyboardLayouts, mCurrentKeyboardLayout);
+            assert index >= 0;
+            if (direction > 0) {
+                index = (index + 1) % size;
+            } else {
+                index = (index + size - 1) % size;
+            }
+            mCurrentKeyboardLayout = mKeyboardLayouts.get(index);
+            return true;
+        }
+
+        public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
+            boolean changed = false;
+            for (int i = mKeyboardLayouts.size(); i-- > 0; ) {
+                String keyboardLayout = mKeyboardLayouts.get(i);
+                if (!availableKeyboardLayouts.contains(keyboardLayout)) {
+                    Slog.i(TAG, "Removing uninstalled keyboard layout " + keyboardLayout);
+                    mKeyboardLayouts.remove(i);
+                    updateCurrentKeyboardLayoutIfRemoved(keyboardLayout, i);
+                    changed = true;
+                }
+            }
+            return changed;
+        }
+
+        public void loadFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            final int outerDepth = parser.getDepth();
+            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+                if (parser.getName().equals("keyboard-layout")) {
+                    String descriptor = parser.getAttributeValue(null, "descriptor");
+                    if (descriptor == null) {
+                        throw new XmlPullParserException(
+                                "Missing descriptor attribute on keyboard-layout.");
+                    }
+                    String current = parser.getAttributeValue(null, "current");
+                    if (mKeyboardLayouts.contains(descriptor)) {
+                        throw new XmlPullParserException(
+                                "Found duplicate keyboard layout.");
+                    }
+
+                    mKeyboardLayouts.add(descriptor);
+                    if (current != null && current.equals("true")) {
+                        if (mCurrentKeyboardLayout != null) {
+                            throw new XmlPullParserException(
+                                    "Found multiple current keyboard layouts.");
+                        }
+                        mCurrentKeyboardLayout = descriptor;
+                    }
+                }
+            }
+
+            // Maintain invariant that layouts are sorted.
+            Collections.sort(mKeyboardLayouts);
+
+            // Maintain invariant that there is always a current keyboard layout unless
+            // there are none installed.
+            if (mCurrentKeyboardLayout == null && !mKeyboardLayouts.isEmpty()) {
+                mCurrentKeyboardLayout = mKeyboardLayouts.get(0);
+            }
+        }
+
+        public void saveToXml(XmlSerializer serializer) throws IOException {
+            for (String layout : mKeyboardLayouts) {
+                serializer.startTag(null, "keyboard-layout");
+                serializer.attribute(null, "descriptor", layout);
+                if (layout.equals(mCurrentKeyboardLayout)) {
+                    serializer.attribute(null, "current", "true");
+                }
+                serializer.endTag(null, "keyboard-layout");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java
new file mode 100644
index 0000000..b496b4c6
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/lights/LightsManager.java b/services/core/java/com/android/server/lights/LightsManager.java
new file mode 100644
index 0000000..2f20509
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java
new file mode 100644
index 0000000..d814785
--- /dev/null
+++ b/services/core/java/com/android/server/lights/LightsService.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.lights;
+
+import com.android.server.SystemService;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IHardwareService;
+import android.os.Message;
+import android.util.Slog;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+
+public class LightsService extends SystemService {
+    static final String TAG = "LightsService";
+    static final boolean DEBUG = false;
+
+    final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT];
+
+    private final class LightImpl extends Light {
+
+        private LightImpl(int id) {
+            mId = id;
+        }
+
+        @Override
+        public void setBrightness(int brightness) {
+            setBrightness(brightness, BRIGHTNESS_MODE_USER);
+        }
+
+        @Override
+        public void setBrightness(int brightness, int brightnessMode) {
+            synchronized (this) {
+                int color = brightness & 0x000000ff;
+                color = 0xff000000 | (color << 16) | (color << 8) | color;
+                setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode);
+            }
+        }
+
+        @Override
+        public void setColor(int color) {
+            synchronized (this) {
+                setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, 0);
+            }
+        }
+
+        @Override
+        public void setFlashing(int color, int mode, int onMS, int offMS) {
+            synchronized (this) {
+                setLightLocked(color, mode, onMS, offMS, BRIGHTNESS_MODE_USER);
+            }
+        }
+
+        @Override
+        public void pulse() {
+            pulse(0x00ffffff, 7);
+        }
+
+        @Override
+        public void pulse(int color, int onMS) {
+            synchronized (this) {
+                if (mColor == 0 && !mFlashing) {
+                    setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER);
+                    mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS);
+                }
+            }
+        }
+
+        @Override
+        public void turnOff() {
+            synchronized (this) {
+                setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, 0);
+            }
+        }
+
+        private void stopFlashing() {
+            synchronized (this) {
+                setLightLocked(mColor, LIGHT_FLASH_NONE, 0, 0, BRIGHTNESS_MODE_USER);
+            }
+        }
+
+        private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) {
+            if (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS) {
+                if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#"
+                        + Integer.toHexString(color));
+                mColor = color;
+                mMode = mode;
+                mOnMS = onMS;
+                mOffMS = offMS;
+                setLight_native(mNativePointer, mId, color, mode, onMS, offMS, brightnessMode);
+            }
+        }
+
+        private int mId;
+        private int mColor;
+        private int mMode;
+        private int mOnMS;
+        private int mOffMS;
+        private boolean mFlashing;
+    }
+
+    /* This class implements an obsolete API that was removed after eclair and re-added during the
+     * final moments of the froyo release to support flashlight apps that had been using the private
+     * IHardwareService API. This is expected to go away in the next release.
+     */
+    private final IHardwareService.Stub mLegacyFlashlightHack = new IHardwareService.Stub() {
+
+        private static final String FLASHLIGHT_FILE = "/sys/class/leds/spotlight/brightness";
+
+        public boolean getFlashlightEnabled() {
+            try {
+                FileInputStream fis = new FileInputStream(FLASHLIGHT_FILE);
+                int result = fis.read();
+                fis.close();
+                return (result != '0');
+            } catch (Exception e) {
+                return false;
+            }
+        }
+
+        public void setFlashlightEnabled(boolean on) {
+            final Context context = getContext();
+            if (context.checkCallingOrSelfPermission(android.Manifest.permission.FLASHLIGHT)
+                    != PackageManager.PERMISSION_GRANTED &&
+                    context.checkCallingOrSelfPermission(android.Manifest.permission.HARDWARE_TEST)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires FLASHLIGHT or HARDWARE_TEST permission");
+            }
+            try {
+                FileOutputStream fos = new FileOutputStream(FLASHLIGHT_FILE);
+                byte[] bytes = new byte[2];
+                bytes[0] = (byte)(on ? '1' : '0');
+                bytes[1] = '\n';
+                fos.write(bytes);
+                fos.close();
+            } catch (Exception e) {
+                // fail silently
+            }
+        }
+    };
+
+    @Override
+    public void onCreate(Context context) {
+        mNativePointer = init_native();
+
+        for (int i = 0; i < LightsManager.LIGHT_ID_COUNT; i++) {
+            mLights[i] = new LightImpl(i);
+        }
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService("hardware", mLegacyFlashlightHack);
+        publishLocalService(LightsManager.class, mService);
+    }
+
+    private final LightsManager mService = new LightsManager() {
+        @Override
+        public com.android.server.lights.Light getLight(int id) {
+            if (id < LIGHT_ID_COUNT) {
+                return mLights[id];
+            } else {
+                return null;
+            }
+        }
+    };
+
+    protected void finalize() throws Throwable {
+        finalize_native(mNativePointer);
+        super.finalize();
+    }
+
+    private Handler mH = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            LightImpl light = (LightImpl)msg.obj;
+            light.stopFlashing();
+        }
+    };
+
+    private static native int init_native();
+    private static native void finalize_native(int ptr);
+
+    static native void setLight_native(int ptr, int light, int color, int mode,
+            int onMS, int offMS, int brightnessMode);
+
+    int mNativePointer;
+}
diff --git a/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java b/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java
new file mode 100644
index 0000000..354858b
--- /dev/null
+++ b/services/core/java/com/android/server/location/ComprehensiveCountryDetector.java
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.location.Geocoder;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.Locale;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * This class is used to detect the country where the user is. The sources of
+ * country are queried in order of reliability, like
+ * <ul>
+ * <li>Mobile network</li>
+ * <li>Location</li>
+ * <li>SIM's country</li>
+ * <li>Phone's locale</li>
+ * </ul>
+ * <p>
+ * Call the {@link #detectCountry()} to get the available country immediately.
+ * <p>
+ * To be notified of the future country change, using the
+ * {@link #setCountryListener(CountryListener)}
+ * <p>
+ * Using the {@link #stop()} to stop listening to the country change.
+ * <p>
+ * The country information will be refreshed every
+ * {@link #LOCATION_REFRESH_INTERVAL} once the location based country is used.
+ *
+ * @hide
+ */
+public class ComprehensiveCountryDetector extends CountryDetectorBase {
+
+    private final static String TAG = "CountryDetector";
+    /* package */ static final boolean DEBUG = false;
+
+    /**
+     * Max length of logs to maintain for debugging.
+     */
+    private static final int MAX_LENGTH_DEBUG_LOGS = 20;
+
+    /**
+     * The refresh interval when the location based country was used
+     */
+    private final static long LOCATION_REFRESH_INTERVAL = 1000 * 60 * 60 * 24; // 1 day
+
+    protected CountryDetectorBase mLocationBasedCountryDetector;
+    protected Timer mLocationRefreshTimer;
+
+    private Country mCountry;
+    private final TelephonyManager mTelephonyManager;
+    private Country mCountryFromLocation;
+    private boolean mStopped = false;
+
+    private PhoneStateListener mPhoneStateListener;
+
+    /**
+     * List of the most recent country state changes for debugging. This should have
+     * a max length of MAX_LENGTH_LOGS.
+     */
+    private final ConcurrentLinkedQueue<Country> mDebugLogs = new ConcurrentLinkedQueue<Country>();
+
+    /**
+     * Most recent {@link Country} result that was added to the debug logs {@link #mDebugLogs}.
+     * We keep track of this value to help prevent adding many of the same {@link Country} objects
+     * to the logs.
+     */
+    private Country mLastCountryAddedToLogs;
+
+    /**
+     * Object used to synchronize access to {@link #mLastCountryAddedToLogs}. Be careful if
+     * using it to synchronize anything else in this file.
+     */
+    private final Object mObject = new Object();
+
+    /**
+     * Start time of the current session for which the detector has been active.
+     */
+    private long mStartTime;
+
+    /**
+     * Stop time of the most recent session for which the detector was active.
+     */
+    private long mStopTime;
+
+    /**
+     * The sum of all the time intervals in which the detector was active.
+     */
+    private long mTotalTime;
+
+    /**
+     * Number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events that
+     * have occurred for the current session for which the detector has been active.
+     */
+    private int mCountServiceStateChanges;
+
+    /**
+     * Total number of {@link PhoneStateListener#onServiceStateChanged(ServiceState state)} events
+     * that have occurred for all time intervals in which the detector has been active.
+     */
+    private int mTotalCountServiceStateChanges;
+
+    /**
+     * The listener for receiving the notification from LocationBasedCountryDetector.
+     */
+    private CountryListener mLocationBasedCountryDetectionListener = new CountryListener() {
+        @Override
+        public void onCountryDetected(Country country) {
+            if (DEBUG) Slog.d(TAG, "Country detected via LocationBasedCountryDetector");
+            mCountryFromLocation = country;
+            // Don't start the LocationBasedCountryDetector.
+            detectCountry(true, false);
+            stopLocationBasedDetector();
+        }
+    };
+
+    public ComprehensiveCountryDetector(Context context) {
+        super(context);
+        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    @Override
+    public Country detectCountry() {
+        // Don't start the LocationBasedCountryDetector if we have been stopped.
+        return detectCountry(false, !mStopped);
+    }
+
+    @Override
+    public void stop() {
+        // Note: this method in this subclass called only by tests.
+        Slog.i(TAG, "Stop the detector.");
+        cancelLocationRefresh();
+        removePhoneStateListener();
+        stopLocationBasedDetector();
+        mListener = null;
+        mStopped = true;
+    }
+
+    /**
+     * Get the country from different sources in order of the reliability.
+     */
+    private Country getCountry() {
+        Country result = null;
+        result = getNetworkBasedCountry();
+        if (result == null) {
+            result = getLastKnownLocationBasedCountry();
+        }
+        if (result == null) {
+            result = getSimBasedCountry();
+        }
+        if (result == null) {
+            result = getLocaleCountry();
+        }
+        addToLogs(result);
+        return result;
+    }
+
+    /**
+     * Attempt to add this {@link Country} to the debug logs.
+     */
+    private void addToLogs(Country country) {
+        if (country == null) {
+            return;
+        }
+        // If the country (ISO and source) are the same as before, then there is no
+        // need to add this country as another entry in the logs. Synchronize access to this
+        // variable since multiple threads could be calling this method.
+        synchronized (mObject) {
+            if (mLastCountryAddedToLogs != null && mLastCountryAddedToLogs.equals(country)) {
+                return;
+            }
+            mLastCountryAddedToLogs = country;
+        }
+        // Manually maintain a max limit for the list of logs
+        if (mDebugLogs.size() >= MAX_LENGTH_DEBUG_LOGS) {
+            mDebugLogs.poll();
+        }
+        if (DEBUG) {
+            Slog.d(TAG, country.toString());
+        }
+        mDebugLogs.add(country);
+    }
+
+    private boolean isNetworkCountryCodeAvailable() {
+        // On CDMA TelephonyManager.getNetworkCountryIso() just returns SIM country.  We don't want
+        // to prioritize it over location based country, so ignore it.
+        final int phoneType = mTelephonyManager.getPhoneType();
+        if (DEBUG) Slog.v(TAG, "    phonetype=" + phoneType);
+        return phoneType == TelephonyManager.PHONE_TYPE_GSM;
+    }
+
+    /**
+     * @return the country from the mobile network.
+     */
+    protected Country getNetworkBasedCountry() {
+        String countryIso = null;
+        if (isNetworkCountryCodeAvailable()) {
+            countryIso = mTelephonyManager.getNetworkCountryIso();
+            if (!TextUtils.isEmpty(countryIso)) {
+                return new Country(countryIso, Country.COUNTRY_SOURCE_NETWORK);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the cached location based country.
+     */
+    protected Country getLastKnownLocationBasedCountry() {
+        return mCountryFromLocation;
+    }
+
+    /**
+     * @return the country from SIM card
+     */
+    protected Country getSimBasedCountry() {
+        String countryIso = null;
+        countryIso = mTelephonyManager.getSimCountryIso();
+        if (!TextUtils.isEmpty(countryIso)) {
+            return new Country(countryIso, Country.COUNTRY_SOURCE_SIM);
+        }
+        return null;
+    }
+
+    /**
+     * @return the country from the system's locale.
+     */
+    protected Country getLocaleCountry() {
+        Locale defaultLocale = Locale.getDefault();
+        if (defaultLocale != null) {
+            return new Country(defaultLocale.getCountry(), Country.COUNTRY_SOURCE_LOCALE);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * @param notifyChange indicates whether the listener should be notified the change of the
+     * country
+     * @param startLocationBasedDetection indicates whether the LocationBasedCountryDetector could
+     * be started if the current country source is less reliable than the location.
+     * @return the current available UserCountry
+     */
+    private Country detectCountry(boolean notifyChange, boolean startLocationBasedDetection) {
+        Country country = getCountry();
+        runAfterDetectionAsync(mCountry != null ? new Country(mCountry) : mCountry, country,
+                notifyChange, startLocationBasedDetection);
+        mCountry = country;
+        return mCountry;
+    }
+
+    /**
+     * Run the tasks in the service's thread.
+     */
+    protected void runAfterDetectionAsync(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                runAfterDetection(
+                        country, detectedCountry, notifyChange, startLocationBasedDetection);
+            }
+        });
+    }
+
+    @Override
+    public void setCountryListener(CountryListener listener) {
+        CountryListener prevListener = mListener;
+        mListener = listener;
+        if (mListener == null) {
+            // Stop listening all services
+            removePhoneStateListener();
+            stopLocationBasedDetector();
+            cancelLocationRefresh();
+            mStopTime = SystemClock.elapsedRealtime();
+            mTotalTime += mStopTime;
+        } else if (prevListener == null) {
+            addPhoneStateListener();
+            detectCountry(false, true);
+            mStartTime = SystemClock.elapsedRealtime();
+            mStopTime = 0;
+            mCountServiceStateChanges = 0;
+        }
+    }
+
+    void runAfterDetection(final Country country, final Country detectedCountry,
+            final boolean notifyChange, final boolean startLocationBasedDetection) {
+        if (notifyChange) {
+            notifyIfCountryChanged(country, detectedCountry);
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "startLocationBasedDetection=" + startLocationBasedDetection
+                    + " detectCountry=" + (detectedCountry == null ? null :
+                        "(source: " + detectedCountry.getSource()
+                        + ", countryISO: " + detectedCountry.getCountryIso() + ")")
+                    + " isAirplaneModeOff()=" + isAirplaneModeOff()
+                    + " mListener=" + mListener
+                    + " isGeoCoderImplemnted()=" + isGeoCoderImplemented());
+        }
+
+        if (startLocationBasedDetection && (detectedCountry == null
+                || detectedCountry.getSource() > Country.COUNTRY_SOURCE_LOCATION)
+                && isAirplaneModeOff() && mListener != null && isGeoCoderImplemented()) {
+            if (DEBUG) Slog.d(TAG, "run startLocationBasedDetector()");
+            // Start finding location when the source is less reliable than the
+            // location and the airplane mode is off (as geocoder will not
+            // work).
+            // TODO : Shall we give up starting the detector within a
+            // period of time?
+            startLocationBasedDetector(mLocationBasedCountryDetectionListener);
+        }
+        if (detectedCountry == null
+                || detectedCountry.getSource() >= Country.COUNTRY_SOURCE_LOCATION) {
+            // Schedule the location refresh if the country source is
+            // not more reliable than the location or no country is
+            // found.
+            // TODO: Listen to the preference change of GPS, Wifi etc,
+            // and start detecting the country.
+            scheduleLocationRefresh();
+        } else {
+            // Cancel the location refresh once the current source is
+            // more reliable than the location.
+            cancelLocationRefresh();
+            stopLocationBasedDetector();
+        }
+    }
+
+    /**
+     * Find the country from LocationProvider.
+     */
+    private synchronized void startLocationBasedDetector(CountryListener listener) {
+        if (mLocationBasedCountryDetector != null) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "starts LocationBasedDetector to detect Country code via Location info "
+                    + "(e.g. GPS)");
+        }
+        mLocationBasedCountryDetector = createLocationBasedCountryDetector();
+        mLocationBasedCountryDetector.setCountryListener(listener);
+        mLocationBasedCountryDetector.detectCountry();
+    }
+
+    private synchronized void stopLocationBasedDetector() {
+        if (DEBUG) {
+            Slog.d(TAG, "tries to stop LocationBasedDetector "
+                    + "(current detector: " + mLocationBasedCountryDetector + ")");
+        }
+        if (mLocationBasedCountryDetector != null) {
+            mLocationBasedCountryDetector.stop();
+            mLocationBasedCountryDetector = null;
+        }
+    }
+
+    protected CountryDetectorBase createLocationBasedCountryDetector() {
+        return new LocationBasedCountryDetector(mContext);
+    }
+
+    protected boolean isAirplaneModeOff() {
+        return Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 0;
+    }
+
+    /**
+     * Notify the country change.
+     */
+    private void notifyIfCountryChanged(final Country country, final Country detectedCountry) {
+        if (detectedCountry != null && mListener != null
+                && (country == null || !country.equals(detectedCountry))) {
+            if (DEBUG) {
+                Slog.d(TAG, "" + country + " --> " + detectedCountry);
+            }
+            notifyListener(detectedCountry);
+        }
+    }
+
+    /**
+     * Schedule the next location refresh. We will do nothing if the scheduled task exists.
+     */
+    private synchronized void scheduleLocationRefresh() {
+        if (mLocationRefreshTimer != null) return;
+        if (DEBUG) {
+            Slog.d(TAG, "start periodic location refresh timer. Interval: "
+                    + LOCATION_REFRESH_INTERVAL);
+        }
+        mLocationRefreshTimer = new Timer();
+        mLocationRefreshTimer.schedule(new TimerTask() {
+            @Override
+            public void run() {
+                if (DEBUG) {
+                    Slog.d(TAG, "periodic location refresh event. Starts detecting Country code");
+                }
+                mLocationRefreshTimer = null;
+                detectCountry(false, true);
+            }
+        }, LOCATION_REFRESH_INTERVAL);
+    }
+
+    /**
+     * Cancel the scheduled refresh task if it exists
+     */
+    private synchronized void cancelLocationRefresh() {
+        if (mLocationRefreshTimer != null) {
+            mLocationRefreshTimer.cancel();
+            mLocationRefreshTimer = null;
+        }
+    }
+
+    protected synchronized void addPhoneStateListener() {
+        if (mPhoneStateListener == null) {
+            mPhoneStateListener = new PhoneStateListener() {
+                @Override
+                public void onServiceStateChanged(ServiceState serviceState) {
+                    mCountServiceStateChanges++;
+                    mTotalCountServiceStateChanges++;
+
+                    if (!isNetworkCountryCodeAvailable()) {
+                        return;
+                    }
+                    if (DEBUG) Slog.d(TAG, "onServiceStateChanged: " + serviceState.getState());
+
+                    detectCountry(true, true);
+                }
+            };
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        }
+    }
+
+    protected synchronized void removePhoneStateListener() {
+        if (mPhoneStateListener != null) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mPhoneStateListener = null;
+        }
+    }
+
+    protected boolean isGeoCoderImplemented() {
+        return Geocoder.isPresent();
+    }
+
+    @Override
+    public String toString() {
+        long currentTime = SystemClock.elapsedRealtime();
+        long currentSessionLength = 0;
+        StringBuilder sb = new StringBuilder();
+        sb.append("ComprehensiveCountryDetector{");
+        // The detector hasn't stopped yet --> still running
+        if (mStopTime == 0) {
+            currentSessionLength = currentTime - mStartTime;
+            sb.append("timeRunning=" + currentSessionLength + ", ");
+        } else {
+            // Otherwise, it has already stopped, so take the last session
+            sb.append("lastRunTimeLength=" + (mStopTime - mStartTime) + ", ");
+        }
+        sb.append("totalCountServiceStateChanges=" + mTotalCountServiceStateChanges + ", ");
+        sb.append("currentCountServiceStateChanges=" + mCountServiceStateChanges + ", ");
+        sb.append("totalTime=" + (mTotalTime + currentSessionLength) + ", ");
+        sb.append("currentTime=" + currentTime + ", ");
+        sb.append("countries=");
+        for (Country country : mDebugLogs) {
+            sb.append("\n   " + country.toString());
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/location/CountryDetectorBase.java b/services/core/java/com/android/server/location/CountryDetectorBase.java
new file mode 100644
index 0000000..8326ef9
--- /dev/null
+++ b/services/core/java/com/android/server/location/CountryDetectorBase.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import android.content.Context;
+import android.location.Country;
+import android.location.CountryListener;
+import android.os.Handler;
+
+/**
+ * This class defines the methods need to be implemented by the country
+ * detector.
+ * <p>
+ * Calling {@link #detectCountry} to start detecting the country. The country
+ * could be returned immediately if it is available.
+ *
+ * @hide
+ */
+public abstract class CountryDetectorBase {
+    protected final Handler mHandler;
+    protected final Context mContext;
+    protected CountryListener mListener;
+    protected Country mDetectedCountry;
+
+    public CountryDetectorBase(Context ctx) {
+        mContext = ctx;
+        mHandler = new Handler();
+    }
+
+    /**
+     * Start detecting the country that the user is in.
+     *
+     * @return the country if it is available immediately, otherwise null should
+     *         be returned.
+     */
+    public abstract Country detectCountry();
+
+    /**
+     * Register a listener to receive the notification when the country is detected or changed.
+     * <p>
+     * The previous listener will be replaced if it exists.
+     */
+    public void setCountryListener(CountryListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Stop detecting the country. The detector should release all system services and be ready to
+     * be freed
+     */
+    public abstract void stop();
+
+    protected void notifyListener(Country country) {
+        if (mListener != null) {
+            mListener.onCountryDetected(country);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/FlpHardwareProvider.java b/services/core/java/com/android/server/location/FlpHardwareProvider.java
new file mode 100644
index 0000000..fab84a8
--- /dev/null
+++ b/services/core/java/com/android/server/location/FlpHardwareProvider.java
@@ -0,0 +1,427 @@
+/*
+ * 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.location;
+
+import android.hardware.location.GeofenceHardware;
+import android.hardware.location.GeofenceHardwareImpl;
+import android.hardware.location.GeofenceHardwareRequestParcelable;
+import android.hardware.location.IFusedLocationHardware;
+import android.hardware.location.IFusedLocationHardwareSink;
+import android.location.IFusedGeofenceHardware;
+import android.location.FusedBatchOptions;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+/**
+ * This class is an interop layer for JVM types and the JNI code that interacts
+ * with the FLP HAL implementation.
+ *
+ * {@hide}
+ */
+public class FlpHardwareProvider {
+    private GeofenceHardwareImpl mGeofenceHardwareSink = null;
+    private IFusedLocationHardwareSink mLocationSink = null;
+
+    private static FlpHardwareProvider sSingletonInstance = null;
+
+    private final static String TAG = "FlpHardwareProvider";
+    private final Context mContext;
+    private final Object mLocationSinkLock = new Object();
+
+    // FlpHal result codes, they must be equal to the ones in fused_location.h
+    private static final int FLP_RESULT_SUCCESS = 0;
+    private static final int FLP_RESULT_ERROR = -1;
+    private static final int FLP_RESULT_INSUFFICIENT_MEMORY = -2;
+    private static final int FLP_RESULT_TOO_MANY_GEOFENCES = -3;
+    private static final int FLP_RESULT_ID_EXISTS = -4;
+    private static final int FLP_RESULT_ID_UNKNOWN = -5;
+    private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6;
+
+    public static FlpHardwareProvider getInstance(Context context) {
+        if (sSingletonInstance == null) {
+            sSingletonInstance = new FlpHardwareProvider(context);
+        }
+
+        return sSingletonInstance;
+    }
+
+    private FlpHardwareProvider(Context context) {
+        mContext = context;
+
+        // register for listening for passive provider data
+        LocationManager manager = (LocationManager) mContext.getSystemService(
+                Context.LOCATION_SERVICE);
+        final long minTime = 0;
+        final float minDistance = 0;
+        final boolean oneShot = false;
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                LocationManager.PASSIVE_PROVIDER,
+                minTime,
+                minDistance,
+                oneShot);
+        // Don't keep track of this request since it's done on behalf of other clients
+        // (which are kept track of separately).
+        request.setHideFromAppOps(true);
+        manager.requestLocationUpdates(
+                request,
+                new NetworkLocationListener(),
+                Looper.myLooper());
+    }
+
+    public static boolean isSupported() {
+        return nativeIsSupported();
+    }
+
+    /**
+     * Private callback functions used by FLP HAL.
+     */
+    // FlpCallbacks members
+    private void onLocationReport(Location[] locations) {
+        for (Location location : locations) {
+            location.setProvider(LocationManager.FUSED_PROVIDER);
+            // set the elapsed time-stamp just as GPS provider does
+            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        }
+
+        IFusedLocationHardwareSink sink;
+        synchronized (mLocationSinkLock) {
+            sink = mLocationSink;
+        }
+        try {
+            if (sink != null) {
+                sink.onLocationAvailable(locations);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException calling onLocationAvailable");
+        }
+    }
+
+    // FlpDiagnosticCallbacks members
+    private void onDataReport(String data) {
+        IFusedLocationHardwareSink sink;
+        synchronized (mLocationSinkLock) {
+            sink = mLocationSink;
+        }
+        try {
+            if (mLocationSink != null) {
+                sink.onDiagnosticDataAvailable(data);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException calling onDiagnosticDataAvailable");
+        }
+    }
+
+    // FlpGeofenceCallbacks members
+    private void onGeofenceTransition(
+            int geofenceId,
+            Location location,
+            int transition,
+            long timestamp,
+            int sourcesUsed) {
+        getGeofenceHardwareSink().reportGeofenceTransition(
+                geofenceId,
+                updateLocationInformation(location),
+                transition,
+                timestamp,
+                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                sourcesUsed);
+    }
+
+    private void onGeofenceMonitorStatus(int status, int source, Location location) {
+        // allow the location to be optional in this event
+        Location updatedLocation = null;
+        if(location != null) {
+            updatedLocation = updateLocationInformation(location);
+        }
+
+        getGeofenceHardwareSink().reportGeofenceMonitorStatus(
+                GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE,
+                status,
+                updatedLocation,
+                source);
+    }
+
+    private void onGeofenceAdd(int geofenceId, int result) {
+        getGeofenceHardwareSink().reportGeofenceAddStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
+    }
+
+    private void onGeofenceRemove(int geofenceId, int result) {
+        getGeofenceHardwareSink().reportGeofenceRemoveStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
+    }
+
+    private void onGeofencePause(int geofenceId, int result) {
+        getGeofenceHardwareSink().reportGeofencePauseStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
+    }
+
+    private void onGeofenceResume(int geofenceId, int result) {
+        getGeofenceHardwareSink().reportGeofenceResumeStatus(
+                geofenceId,
+                translateToGeofenceHardwareStatus(result));
+    }
+
+    /**
+     * Private native methods accessing FLP HAL.
+     */
+    static { nativeClassInit(); }
+
+    // Core members
+    private static native void nativeClassInit();
+    private static native boolean nativeIsSupported();
+
+    // FlpLocationInterface members
+    private native void nativeInit();
+    private native int nativeGetBatchSize();
+    private native void nativeStartBatching(int requestId, FusedBatchOptions options);
+    private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject);
+    private native void nativeStopBatching(int id);
+    private native void nativeRequestBatchedLocation(int lastNLocations);
+    private native void nativeInjectLocation(Location location);
+    // TODO [Fix] sort out the lifetime of the instance
+    private native void nativeCleanup();
+
+    // FlpDiagnosticsInterface members
+    private native boolean nativeIsDiagnosticSupported();
+    private native void nativeInjectDiagnosticData(String data);
+
+    // FlpDeviceContextInterface members
+    private native boolean nativeIsDeviceContextSupported();
+    private native void nativeInjectDeviceContext(int deviceEnabledContext);
+
+    // FlpGeofencingInterface members
+    private native boolean nativeIsGeofencingSupported();
+    private native void nativeAddGeofences(
+            GeofenceHardwareRequestParcelable[] geofenceRequestsArray);
+    private native void nativePauseGeofence(int geofenceId);
+    private native void  nativeResumeGeofence(int geofenceId, int monitorTransitions);
+    private native void nativeModifyGeofenceOption(
+        int geofenceId,
+        int lastTransition,
+        int monitorTransitions,
+        int notificationResponsiveness,
+        int unknownTimer,
+        int sourcesToUse);
+    private native void nativeRemoveGeofences(int[] geofenceIdsArray);
+
+    /**
+     * Interface implementations for services built on top of this functionality.
+     */
+    public static final String LOCATION = "Location";
+    public static final String GEOFENCING = "Geofencing";
+
+    public IFusedLocationHardware getLocationHardware() {
+        nativeInit();
+        return mLocationHardware;
+    }
+
+    public IFusedGeofenceHardware getGeofenceHardware() {
+        nativeInit();
+        return mGeofenceHardwareService;
+    }
+
+    private final IFusedLocationHardware mLocationHardware = new IFusedLocationHardware.Stub() {
+        @Override
+        public void registerSink(IFusedLocationHardwareSink eventSink) {
+            synchronized (mLocationSinkLock) {
+                // only one sink is allowed at the moment
+                if (mLocationSink != null) {
+                    throw new RuntimeException(
+                            "IFusedLocationHardware does not support multiple sinks");
+                }
+
+                mLocationSink = eventSink;
+            }
+        }
+
+        @Override
+        public void unregisterSink(IFusedLocationHardwareSink eventSink) {
+            synchronized (mLocationSinkLock) {
+                // don't throw if the sink is not registered, simply make it a no-op
+                if (mLocationSink == eventSink) {
+                    mLocationSink = null;
+                }
+            }
+        }
+
+        @Override
+        public int getSupportedBatchSize() {
+            return nativeGetBatchSize();
+        }
+
+        @Override
+        public void startBatching(int requestId, FusedBatchOptions options) {
+            nativeStartBatching(requestId, options);
+        }
+
+        @Override
+        public void stopBatching(int requestId) {
+            nativeStopBatching(requestId);
+        }
+
+        @Override
+        public void updateBatchingOptions(int requestId, FusedBatchOptions options) {
+            nativeUpdateBatchingOptions(requestId, options);
+        }
+
+        @Override
+        public void requestBatchOfLocations(int batchSizeRequested) {
+            nativeRequestBatchedLocation(batchSizeRequested);
+        }
+
+        @Override
+        public boolean supportsDiagnosticDataInjection() {
+            return nativeIsDiagnosticSupported();
+        }
+
+        @Override
+        public void injectDiagnosticData(String data) {
+            nativeInjectDiagnosticData(data);
+        }
+
+        @Override
+        public boolean supportsDeviceContextInjection() {
+            return nativeIsDeviceContextSupported();
+        }
+
+        @Override
+        public void injectDeviceContext(int deviceEnabledContext) {
+            nativeInjectDeviceContext(deviceEnabledContext);
+        }
+    };
+
+    private final IFusedGeofenceHardware mGeofenceHardwareService =
+            new IFusedGeofenceHardware.Stub() {
+        @Override
+        public boolean isSupported() {
+            return nativeIsGeofencingSupported();
+        }
+
+        @Override
+        public void addGeofences(GeofenceHardwareRequestParcelable[] geofenceRequestsArray) {
+            nativeAddGeofences(geofenceRequestsArray);
+        }
+
+        @Override
+        public void removeGeofences(int[] geofenceIds) {
+            nativeRemoveGeofences(geofenceIds);
+        }
+
+        @Override
+        public void pauseMonitoringGeofence(int geofenceId) {
+            nativePauseGeofence(geofenceId);
+        }
+
+        @Override
+        public void resumeMonitoringGeofence(int geofenceId, int monitorTransitions) {
+            nativeResumeGeofence(geofenceId, monitorTransitions);
+        }
+
+        @Override
+        public void modifyGeofenceOptions(int geofenceId,
+                int lastTransition,
+                int monitorTransitions,
+                int notificationResponsiveness,
+                int unknownTimer,
+                int sourcesToUse) {
+            nativeModifyGeofenceOption(
+                    geofenceId,
+                    lastTransition,
+                    monitorTransitions,
+                    notificationResponsiveness,
+                    unknownTimer,
+                    sourcesToUse);
+        }
+    };
+
+    /**
+     * Internal classes and functions used by the provider.
+     */
+    private final class NetworkLocationListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            if (
+                !LocationManager.NETWORK_PROVIDER.equals(location.getProvider()) ||
+                !location.hasAccuracy()
+                ) {
+                return;
+            }
+
+            nativeInjectLocation(location);
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) { }
+
+        @Override
+        public void onProviderEnabled(String provider) { }
+
+        @Override
+        public void onProviderDisabled(String provider) { }
+    }
+
+    private GeofenceHardwareImpl getGeofenceHardwareSink() {
+        if (mGeofenceHardwareSink == null) {
+            mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext);
+        }
+
+        return mGeofenceHardwareSink;
+    }
+
+    private static int translateToGeofenceHardwareStatus(int flpHalResult) {
+        switch(flpHalResult) {
+            case FLP_RESULT_SUCCESS:
+                return GeofenceHardware.GEOFENCE_SUCCESS;
+            case FLP_RESULT_ERROR:
+                return GeofenceHardware.GEOFENCE_FAILURE;
+            // TODO: uncomment this once the ERROR definition is marked public
+            //case FLP_RESULT_INSUFFICIENT_MEMORY:
+            //    return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY;
+            case FLP_RESULT_TOO_MANY_GEOFENCES:
+                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
+            case FLP_RESULT_ID_EXISTS:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
+            case FLP_RESULT_ID_UNKNOWN:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
+            case FLP_RESULT_INVALID_GEOFENCE_TRANSITION:
+                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
+            default:
+                Log.e(TAG, String.format("Invalid FlpHal result code: %d", flpHalResult));
+                return GeofenceHardware.GEOFENCE_FAILURE;
+        }
+    }
+
+    private Location updateLocationInformation(Location location) {
+        location.setProvider(LocationManager.FUSED_PROVIDER);
+        // set the elapsed time-stamp just as GPS provider does
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        return location;
+    }
+}
diff --git a/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java
new file mode 100644
index 0000000..389bd24
--- /dev/null
+++ b/services/core/java/com/android/server/location/FusedLocationHardwareSecure.java
@@ -0,0 +1,119 @@
+/*
+ * 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.location;
+
+import android.content.Context;
+import android.hardware.location.IFusedLocationHardware;
+import android.hardware.location.IFusedLocationHardwareSink;
+import android.location.FusedBatchOptions;
+import android.os.RemoteException;
+
+/**
+ * FusedLocationHardware decorator that adds permission checking.
+ * @hide
+ */
+public class FusedLocationHardwareSecure extends IFusedLocationHardware.Stub {
+    private final IFusedLocationHardware mLocationHardware;
+    private final Context mContext;
+    private final String mPermissionId;
+
+    public FusedLocationHardwareSecure(
+            IFusedLocationHardware locationHardware,
+            Context context,
+            String permissionId) {
+        mLocationHardware = locationHardware;
+        mContext = context;
+        mPermissionId = permissionId;
+    }
+
+    private void checkPermissions() {
+        mContext.enforceCallingPermission(
+                mPermissionId,
+                String.format(
+                        "Permission '%s' not granted to access FusedLocationHardware",
+                        mPermissionId));
+    }
+
+    @Override
+    public void registerSink(IFusedLocationHardwareSink eventSink) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.registerSink(eventSink);
+    }
+
+    @Override
+    public void unregisterSink(IFusedLocationHardwareSink eventSink) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.unregisterSink(eventSink);
+    }
+
+    @Override
+    public int getSupportedBatchSize() throws RemoteException {
+        checkPermissions();
+        return mLocationHardware.getSupportedBatchSize();
+    }
+
+    @Override
+    public void startBatching(int id, FusedBatchOptions batchOptions) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.startBatching(id, batchOptions);
+    }
+
+    @Override
+    public void stopBatching(int id) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.stopBatching(id);
+    }
+
+    @Override
+    public void updateBatchingOptions(
+            int id,
+            FusedBatchOptions batchoOptions
+            ) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.updateBatchingOptions(id, batchoOptions);
+    }
+
+    @Override
+    public void requestBatchOfLocations(int batchSizeRequested) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.requestBatchOfLocations(batchSizeRequested);
+    }
+
+    @Override
+    public boolean supportsDiagnosticDataInjection() throws RemoteException {
+        checkPermissions();
+        return mLocationHardware.supportsDiagnosticDataInjection();
+    }
+
+    @Override
+    public void injectDiagnosticData(String data) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.injectDiagnosticData(data);
+    }
+
+    @Override
+    public boolean supportsDeviceContextInjection() throws RemoteException {
+        checkPermissions();
+        return mLocationHardware.supportsDeviceContextInjection();
+    }
+
+    @Override
+    public void injectDeviceContext(int deviceEnabledContext) throws RemoteException {
+        checkPermissions();
+        mLocationHardware.injectDeviceContext(deviceEnabledContext);
+    }
+}
diff --git a/services/core/java/com/android/server/location/FusedProxy.java b/services/core/java/com/android/server/location/FusedProxy.java
new file mode 100644
index 0000000..f7fac77
--- /dev/null
+++ b/services/core/java/com/android/server/location/FusedProxy.java
@@ -0,0 +1,128 @@
+/*
+ * 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 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.location;
+
+import com.android.server.ServiceWatcher;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.location.IFusedLocationHardware;
+import android.location.IFusedProvider;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Proxy that helps bind GCore FusedProvider implementations to the Fused Hardware instances.
+ *
+ * @hide
+ */
+public final class FusedProxy {
+    private final String TAG = "FusedProxy";
+    private final ServiceWatcher mServiceWatcher;
+    private final FusedLocationHardwareSecure mLocationHardware;
+
+    /**
+     * Constructor of the class.
+     * This is private as the class follows a factory pattern for construction.
+     *
+     * @param context           The context needed for construction.
+     * @param handler           The handler needed for construction.
+     * @param locationHardware  The instance of the Fused location hardware assigned to the proxy.
+     */
+    private FusedProxy(
+            Context context,
+            Handler handler,
+            IFusedLocationHardware locationHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        mLocationHardware = new FusedLocationHardwareSecure(
+                locationHardware,
+                context,
+                Manifest.permission.LOCATION_HARDWARE);
+        Runnable newServiceWork = new Runnable() {
+            @Override
+            public void run() {
+                bindProvider(mLocationHardware);
+            }
+        };
+
+        // prepare the connection to the provider
+        mServiceWatcher = new ServiceWatcher(
+                context,
+                TAG,
+                "com.android.location.service.FusedProvider",
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId,
+                newServiceWork,
+                handler);
+    }
+
+    /**
+     * Creates an instance of the proxy and binds it to the appropriate FusedProvider.
+     *
+     * @param context           The context needed for construction.
+     * @param handler           The handler needed for construction.
+     * @param locationHardware  The instance of the Fused location hardware assigned to the proxy.
+     *
+     * @return An instance of the proxy if it could be bound, null otherwise.
+     */
+    public static FusedProxy createAndBind(
+            Context context,
+            Handler handler,
+            IFusedLocationHardware locationHardware,
+            int overlaySwitchResId,
+            int defaultServicePackageNameResId,
+            int initialPackageNameResId) {
+        FusedProxy fusedProxy = new FusedProxy(
+                context,
+                handler,
+                locationHardware,
+                overlaySwitchResId,
+                defaultServicePackageNameResId,
+                initialPackageNameResId);
+
+        // try to bind the Fused provider
+        if (!fusedProxy.mServiceWatcher.start()) {
+            return null;
+        }
+
+        return fusedProxy;
+    }
+
+    /**
+     * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance.
+     *
+     * @param locationHardware  The FusedLocationHardware instance to use for the binding operation.
+     */
+    private void bindProvider(IFusedLocationHardware locationHardware) {
+        IFusedProvider provider = IFusedProvider.Stub.asInterface(mServiceWatcher.getBinder());
+
+        if (provider == null) {
+            Log.e(TAG, "No instance of FusedProvider found on FusedLocationHardware connected.");
+            return;
+        }
+
+        try {
+            provider.onFusedLocationHardwareChange(locationHardware);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
new file mode 100644
index 0000000..5d4a770
--- /dev/null
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.GeocoderParams;
+import android.location.IGeocodeProvider;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.server.ServiceWatcher;
+import java.util.List;
+
+/**
+ * Proxy for IGeocodeProvider implementations.
+ */
+public class GeocoderProxy {
+    private static final String TAG = "GeocoderProxy";
+
+    private static final String SERVICE_ACTION = "com.android.location.service.GeocodeProvider";
+
+    private final Context mContext;
+    private final ServiceWatcher mServiceWatcher;
+
+    public static GeocoderProxy createAndBind(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
+        GeocoderProxy proxy = new GeocoderProxy(context, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, handler);
+        if (proxy.bind()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private GeocoderProxy(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
+        mContext = context;
+
+        mServiceWatcher = new ServiceWatcher(mContext, TAG, SERVICE_ACTION, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, null, handler);
+    }
+
+    private boolean bind () {
+        return mServiceWatcher.start();
+    }
+
+    private IGeocodeProvider getService() {
+        return IGeocodeProvider.Stub.asInterface(mServiceWatcher.getBinder());
+    }
+
+    public String getConnectedPackageName() {
+        return mServiceWatcher.getBestPackageName();
+    }
+
+    public String getFromLocation(double latitude, double longitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+        IGeocodeProvider provider = getService();
+        if (provider != null) {
+            try {
+                return provider.getFromLocation(latitude, longitude, maxResults, params, addrs);
+            } catch (RemoteException e) {
+                Log.w(TAG, e);
+            }
+        }
+        return "Service not Available";
+    }
+
+    public String getFromLocationName(String locationName,
+            double lowerLeftLatitude, double lowerLeftLongitude,
+            double upperRightLatitude, double upperRightLongitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+        IGeocodeProvider provider = getService();
+        if (provider != null) {
+            try {
+                return provider.getFromLocationName(locationName, lowerLeftLatitude,
+                        lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+                        maxResults, params, addrs);
+            } catch (RemoteException e) {
+                Log.w(TAG, e);
+            }
+        }
+        return "Service not Available";
+    }
+
+}
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
new file mode 100644
index 0000000..e24bf76
--- /dev/null
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Geofence;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationRequest;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Slog;
+
+import com.android.server.LocationManagerService;
+
+public class GeofenceManager implements LocationListener, PendingIntent.OnFinished {
+    private static final String TAG = "GeofenceManager";
+    private static final boolean D = LocationManagerService.D;
+
+    private static final int MSG_UPDATE_FENCES = 1;
+
+    /**
+     * Assume a maximum land speed, as a heuristic to throttle location updates.
+     * (Air travel should result in an airplane mode toggle which will
+     * force a new location update anyway).
+     */
+    private static final int MAX_SPEED_M_S = 100;  // 360 km/hr (high speed train)
+
+    /**
+     * Maximum age after which a location is no longer considered fresh enough to use.
+     */
+    private static final long MAX_AGE_NANOS = 5 * 60 * 1000000000L; // five minutes
+
+    /**
+     * Most frequent update interval allowed.
+     */
+    private static final long MIN_INTERVAL_MS = 1 * 60 * 1000; // one minute
+
+    /**
+     * Least frequent update interval allowed.
+     */
+    private static final long MAX_INTERVAL_MS = 2 * 60 * 60 * 1000; // two hours
+
+    private final Context mContext;
+    private final LocationManager mLocationManager;
+    private final AppOpsManager mAppOps;
+    private final PowerManager.WakeLock mWakeLock;
+    private final GeofenceHandler mHandler;
+    private final LocationBlacklist mBlacklist;
+
+    private Object mLock = new Object();
+
+    // access to members below is synchronized on mLock
+    /**
+     * A list containing all registered geofences.
+     */
+    private List<GeofenceState> mFences = new LinkedList<GeofenceState>();
+
+    /**
+     * This is set true when we have an active request for {@link Location} updates via
+     * {@link LocationManager#requestLocationUpdates(LocationRequest, LocationListener,
+     * android.os.Looper).
+     */
+    private boolean mReceivingLocationUpdates;
+
+    /**
+     * The update interval component of the current active {@link Location} update request.
+     */
+    private long mLocationUpdateInterval;
+
+    /**
+     * The {@link Location} most recently received via {@link #onLocationChanged(Location)}.
+     */
+    private Location mLastLocationUpdate;
+
+    /**
+     * This is set true when a {@link Location} is received via
+     * {@link #onLocationChanged(Location)} or {@link #scheduleUpdateFencesLocked()}, and cleared
+     * when that Location has been processed via {@link #updateFences()}
+     */
+    private boolean mPendingUpdate;
+
+    public GeofenceManager(Context context, LocationBlacklist blacklist) {
+        mContext = context;
+        mLocationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+        mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mHandler = new GeofenceHandler();
+        mBlacklist = blacklist;
+    }
+
+    public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
+            int allowedResolutionLevel, int uid, String packageName) {
+        if (D) {
+            Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
+                    + ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
+        }
+
+        GeofenceState state = new GeofenceState(geofence,
+                request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
+        synchronized (mLock) {
+            // first make sure it doesn't already exist
+            for (int i = mFences.size() - 1; i >= 0; i--) {
+                GeofenceState w = mFences.get(i);
+                if (geofence.equals(w.mFence) && intent.equals(w.mIntent)) {
+                    // already exists, remove the old one
+                    mFences.remove(i);
+                    break;
+                }
+            }
+            mFences.add(state);
+            scheduleUpdateFencesLocked();
+        }
+    }
+
+    public void removeFence(Geofence fence, PendingIntent intent) {
+        if (D) {
+            Slog.d(TAG, "removeFence: fence=" + fence + ", intent=" + intent);
+        }
+
+        synchronized (mLock) {
+            Iterator<GeofenceState> iter = mFences.iterator();
+            while (iter.hasNext()) {
+                GeofenceState state = iter.next();
+                if (state.mIntent.equals(intent)) {
+
+                    if (fence == null) {
+                        // always remove
+                        iter.remove();
+                    } else {
+                        // just remove matching fences
+                        if (fence.equals(state.mFence)) {
+                            iter.remove();
+                        }
+                    }
+                }
+            }
+            scheduleUpdateFencesLocked();
+        }
+    }
+
+    public void removeFence(String packageName) {
+        if (D) {
+            Slog.d(TAG, "removeFence: packageName=" + packageName);
+        }
+
+        synchronized (mLock) {
+            Iterator<GeofenceState> iter = mFences.iterator();
+            while (iter.hasNext()) {
+                GeofenceState state = iter.next();
+                if (state.mPackageName.equals(packageName)) {
+                    iter.remove();
+                }
+            }
+            scheduleUpdateFencesLocked();
+        }
+    }
+
+    private void removeExpiredFencesLocked() {
+        long time = SystemClock.elapsedRealtime();
+        Iterator<GeofenceState> iter = mFences.iterator();
+        while (iter.hasNext()) {
+            GeofenceState state = iter.next();
+            if (state.mExpireAt < time) {
+                iter.remove();
+            }
+        }
+    }
+
+    private void scheduleUpdateFencesLocked() {
+        if (!mPendingUpdate) {
+            mPendingUpdate = true;
+            mHandler.sendEmptyMessage(MSG_UPDATE_FENCES);
+        }
+    }
+
+    /**
+     * Returns the location received most recently from {@link #onLocationChanged(Location)},
+     * or consult {@link LocationManager#getLastLocation()} if none has arrived. Does not return
+     * either if the location would be too stale to be useful.
+     *
+     * @return a fresh, valid Location, or null if none is available
+     */
+    private Location getFreshLocationLocked() {
+        // Prefer mLastLocationUpdate to LocationManager.getLastLocation().
+        Location location = mReceivingLocationUpdates ? mLastLocationUpdate : null;
+        if (location == null && !mFences.isEmpty()) {
+            location = mLocationManager.getLastLocation();
+        }
+
+        // Early out for null location.
+        if (location == null) {
+            return null;
+        }
+
+        // Early out for stale location.
+        long now = SystemClock.elapsedRealtimeNanos();
+        if (now - location.getElapsedRealtimeNanos() > MAX_AGE_NANOS) {
+            return null;
+        }
+
+        // Made it this far? Return our fresh, valid location.
+        return location;
+    }
+
+    /**
+     * The geofence update loop. This function removes expired fences, then tests the most
+     * recently-received {@link Location} against each registered {@link GeofenceState}, sending
+     * {@link Intent}s for geofences that have been tripped. It also adjusts the active location
+     * update request with {@link LocationManager} as appropriate for any active geofences.
+     */
+    // Runs on the handler.
+    private void updateFences() {
+        List<PendingIntent> enterIntents = new LinkedList<PendingIntent>();
+        List<PendingIntent> exitIntents = new LinkedList<PendingIntent>();
+
+        synchronized (mLock) {
+            mPendingUpdate = false;
+
+            // Remove expired fences.
+            removeExpiredFencesLocked();
+
+            // Get a location to work with, either received via onLocationChanged() or
+            // via LocationManager.getLastLocation().
+            Location location = getFreshLocationLocked();
+
+            // Update all fences.
+            // Keep track of the distance to the nearest fence.
+            double minFenceDistance = Double.MAX_VALUE;
+            boolean needUpdates = false;
+            for (GeofenceState state : mFences) {
+                if (mBlacklist.isBlacklisted(state.mPackageName)) {
+                    if (D) {
+                        Slog.d(TAG, "skipping geofence processing for blacklisted app: "
+                                + state.mPackageName);
+                    }
+                    continue;
+                }
+
+                int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
+                if (op >= 0) {
+                    if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
+                            state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+                        if (D) {
+                            Slog.d(TAG, "skipping geofence processing for no op app: "
+                                    + state.mPackageName);
+                        }
+                        continue;
+                    }
+                }
+
+                needUpdates = true;
+                if (location != null) {
+                    int event = state.processLocation(location);
+                    if ((event & GeofenceState.FLAG_ENTER) != 0) {
+                        enterIntents.add(state.mIntent);
+                    }
+                    if ((event & GeofenceState.FLAG_EXIT) != 0) {
+                        exitIntents.add(state.mIntent);
+                    }
+
+                    // FIXME: Ideally this code should take into account the accuracy of the
+                    // location fix that was used to calculate the distance in the first place.
+                    double fenceDistance = state.getDistanceToBoundary(); // MAX_VALUE if unknown
+                    if (fenceDistance < minFenceDistance) {
+                        minFenceDistance = fenceDistance;
+                    }
+                }
+            }
+
+            // Request or cancel location updates if needed.
+            if (needUpdates) {
+                // Request location updates.
+                // Compute a location update interval based on the distance to the nearest fence.
+                long intervalMs;
+                if (location != null && Double.compare(minFenceDistance, Double.MAX_VALUE) != 0) {
+                    intervalMs = (long)Math.min(MAX_INTERVAL_MS, Math.max(MIN_INTERVAL_MS,
+                            minFenceDistance * 1000 / MAX_SPEED_M_S));
+                } else {
+                    intervalMs = MIN_INTERVAL_MS;
+                }
+                if (!mReceivingLocationUpdates || mLocationUpdateInterval != intervalMs) {
+                    mReceivingLocationUpdates = true;
+                    mLocationUpdateInterval = intervalMs;
+                    mLastLocationUpdate = location;
+
+                    LocationRequest request = new LocationRequest();
+                    request.setInterval(intervalMs).setFastestInterval(0);
+                    mLocationManager.requestLocationUpdates(request, this, mHandler.getLooper());
+                }
+            } else {
+                // Cancel location updates.
+                if (mReceivingLocationUpdates) {
+                    mReceivingLocationUpdates = false;
+                    mLocationUpdateInterval = 0;
+                    mLastLocationUpdate = null;
+
+                    mLocationManager.removeUpdates(this);
+                }
+            }
+
+            if (D) {
+                Slog.d(TAG, "updateFences: location=" + location
+                        + ", mFences.size()=" + mFences.size()
+                        + ", mReceivingLocationUpdates=" + mReceivingLocationUpdates
+                        + ", mLocationUpdateInterval=" + mLocationUpdateInterval
+                        + ", mLastLocationUpdate=" + mLastLocationUpdate);
+            }
+        }
+
+        // release lock before sending intents
+        for (PendingIntent intent : exitIntents) {
+            sendIntentExit(intent);
+        }
+        for (PendingIntent intent : enterIntents) {
+            sendIntentEnter(intent);
+        }
+    }
+
+    private void sendIntentEnter(PendingIntent pendingIntent) {
+        if (D) {
+            Slog.d(TAG, "sendIntentEnter: pendingIntent=" + pendingIntent);
+        }
+
+        Intent intent = new Intent();
+        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
+        sendIntent(pendingIntent, intent);
+    }
+
+    private void sendIntentExit(PendingIntent pendingIntent) {
+        if (D) {
+            Slog.d(TAG, "sendIntentExit: pendingIntent=" + pendingIntent);
+        }
+
+        Intent intent = new Intent();
+        intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, false);
+        sendIntent(pendingIntent, intent);
+    }
+
+    private void sendIntent(PendingIntent pendingIntent, Intent intent) {
+        mWakeLock.acquire();
+        try {
+            pendingIntent.send(mContext, 0, intent, this, null,
+                    android.Manifest.permission.ACCESS_FINE_LOCATION);
+        } catch (PendingIntent.CanceledException e) {
+            removeFence(null, pendingIntent);
+            mWakeLock.release();
+        }
+        // ...otherwise, mWakeLock.release() gets called by onSendFinished()
+    }
+
+    // Runs on the handler (which was passed into LocationManager.requestLocationUpdates())
+    @Override
+    public void onLocationChanged(Location location) {
+        synchronized (mLock) {
+            if (mReceivingLocationUpdates) {
+                mLastLocationUpdate = location;
+            }
+
+            // Update the fences immediately before returning in
+            // case the caller is holding a wakelock.
+            if (mPendingUpdate) {
+                mHandler.removeMessages(MSG_UPDATE_FENCES);
+            } else {
+                mPendingUpdate = true;
+            }
+        }
+        updateFences();
+    }
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) { }
+
+    @Override
+    public void onProviderEnabled(String provider) { }
+
+    @Override
+    public void onProviderDisabled(String provider) { }
+
+    @Override
+    public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
+            String resultData, Bundle resultExtras) {
+        mWakeLock.release();
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("  Geofences:");
+
+        for (GeofenceState state : mFences) {
+            pw.append("    ");
+            pw.append(state.mPackageName);
+            pw.append(" ");
+            pw.append(state.mFence.toString());
+            pw.append("\n");
+        }
+    }
+
+    private final class GeofenceHandler extends Handler {
+        public GeofenceHandler() {
+            super(true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_FENCES: {
+                    updateFences();
+                    break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/GeofenceProxy.java b/services/core/java/com/android/server/location/GeofenceProxy.java
new file mode 100644
index 0000000..bbc1f47
--- /dev/null
+++ b/services/core/java/com/android/server/location/GeofenceProxy.java
@@ -0,0 +1,186 @@
+/*
+ * 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.location;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.hardware.location.GeofenceHardwareService;
+import android.hardware.location.IGeofenceHardware;
+import android.location.IGeofenceProvider;
+import android.location.IGpsGeofenceHardware;
+import android.location.IFusedGeofenceHardware;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import com.android.server.ServiceWatcher;
+
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class GeofenceProxy {
+    private static final String TAG = "GeofenceProxy";
+    private static final String SERVICE_ACTION =
+            "com.android.location.service.GeofenceProvider";
+    private final ServiceWatcher mServiceWatcher;
+    private final Context mContext;
+    private final IGpsGeofenceHardware mGpsGeofenceHardware;
+    private final IFusedGeofenceHardware mFusedGeofenceHardware;
+
+    private final Object mLock = new Object();
+
+    // Access to mGeofenceHardware needs to be synchronized by mLock.
+    private IGeofenceHardware mGeofenceHardware;
+
+    private static final int GEOFENCE_PROVIDER_CONNECTED = 1;
+    private static final int GEOFENCE_HARDWARE_CONNECTED = 2;
+    private static final int GEOFENCE_HARDWARE_DISCONNECTED = 3;
+    private static final int GEOFENCE_GPS_HARDWARE_CONNECTED = 4;
+    private static final int GEOFENCE_GPS_HARDWARE_DISCONNECTED = 5;
+
+    private Runnable mRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mHandler.sendEmptyMessage(GEOFENCE_PROVIDER_CONNECTED);
+        }
+    };
+
+    public static GeofenceProxy createAndBind(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence,
+            IFusedGeofenceHardware fusedGeofenceHardware) {
+        GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence,
+            fusedGeofenceHardware);
+        if (proxy.bindGeofenceProvider()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private GeofenceProxy(Context context,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence,
+            IFusedGeofenceHardware fusedGeofenceHardware) {
+        mContext = context;
+        mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId,
+            defaultServicePackageNameResId, initialPackageNamesResId, mRunnable, handler);
+        mGpsGeofenceHardware = gpsGeofence;
+        mFusedGeofenceHardware = fusedGeofenceHardware;
+        bindHardwareGeofence();
+    }
+
+    private boolean bindGeofenceProvider() {
+        return mServiceWatcher.start();
+    }
+
+    private void bindHardwareGeofence() {
+        mContext.bindServiceAsUser(new Intent(mContext, GeofenceHardwareService.class),
+                mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.OWNER);
+    }
+
+    private ServiceConnection mServiceConnection = new ServiceConnection() {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                mGeofenceHardware = IGeofenceHardware.Stub.asInterface(service);
+                mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_CONNECTED);
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                mGeofenceHardware = null;
+                mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_DISCONNECTED);
+            }
+        }
+    };
+
+    private void setGeofenceHardwareInProviderLocked() {
+        try {
+            IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface(
+                      mServiceWatcher.getBinder());
+            if (provider != null) {
+                provider.setGeofenceHardware(mGeofenceHardware);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e);
+        }
+    }
+
+    private void setGpsGeofenceLocked() {
+        try {
+            mGeofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error while connecting to GeofenceHardwareService");
+        }
+    }
+
+    private void setFusedGeofenceLocked() {
+        try {
+            mGeofenceHardware.setFusedGeofenceHardware(mFusedGeofenceHardware);
+        } catch(RemoteException e) {
+            Log.e(TAG, "Error while connecting to GeofenceHardwareService");
+        }
+    }
+
+    // This needs to be reworked, when more services get added,
+    // Might need a state machine or add a framework utility class,
+    private Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case GEOFENCE_PROVIDER_CONNECTED:
+                    synchronized (mLock) {
+                        if (mGeofenceHardware != null) {
+                            setGeofenceHardwareInProviderLocked();
+                        }
+                        // else: the geofence provider will be notified when the connection to
+                        // GeofenceHardwareService is established.
+                    }
+                    break;
+                case GEOFENCE_HARDWARE_CONNECTED:
+                    synchronized (mLock) {
+                        // Theoretically this won't happen because once the GeofenceHardwareService
+                        // is connected to, we won't lose connection to it because it's a system
+                        // service. But this check does make the code more robust.
+                        if (mGeofenceHardware != null) {
+                            setGpsGeofenceLocked();
+                            setFusedGeofenceLocked();
+                            setGeofenceHardwareInProviderLocked();
+                        }
+                    }
+                    break;
+                case GEOFENCE_HARDWARE_DISCONNECTED:
+                    synchronized (mLock) {
+                        if (mGeofenceHardware == null) {
+                            setGeofenceHardwareInProviderLocked();
+                        }
+                    }
+                    break;
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
new file mode 100644
index 0000000..3ebe20a
--- /dev/null
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.location;
+
+import android.app.PendingIntent;
+import android.location.Geofence;
+import android.location.Location;
+
+/**
+ * Represents state associated with a geofence
+ */
+public class GeofenceState {
+    public final static int FLAG_ENTER = 0x01;
+    public final static int FLAG_EXIT = 0x02;
+
+    private static final int STATE_UNKNOWN = 0;
+    private static final int STATE_INSIDE = 1;
+    private static final int STATE_OUTSIDE = 2;
+
+    public final Geofence mFence;
+    private final Location mLocation;
+    public final long mExpireAt;
+    public final int mAllowedResolutionLevel;
+    public final int mUid;
+    public final String mPackageName;
+    public final PendingIntent mIntent;
+
+    int mState;  // current state
+    double mDistanceToCenter;  // current distance to center of fence
+
+    public GeofenceState(Geofence fence, long expireAt,
+            int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
+        mState = STATE_UNKNOWN;
+        mDistanceToCenter = Double.MAX_VALUE;
+
+        mFence = fence;
+        mExpireAt = expireAt;
+        mAllowedResolutionLevel = allowedResolutionLevel;
+        mUid = uid;
+        mPackageName = packageName;
+        mIntent = intent;
+
+        mLocation = new Location("");
+        mLocation.setLatitude(fence.getLatitude());
+        mLocation.setLongitude(fence.getLongitude());
+    }
+
+    /**
+     * Process a new location.
+     * @return FLAG_ENTER or FLAG_EXIT if the fence was crossed, 0 otherwise
+     */
+    public int processLocation(Location location) {
+        mDistanceToCenter = mLocation.distanceTo(location);
+
+        int prevState = mState;
+        //TODO: inside/outside detection could be made more rigorous
+        boolean inside = mDistanceToCenter <= Math.max(mFence.getRadius(), location.getAccuracy());
+        if (inside) {
+            mState = STATE_INSIDE;
+            if (prevState != STATE_INSIDE) {
+                return FLAG_ENTER; // return enter if previously exited or unknown
+            }
+        } else {
+            mState = STATE_OUTSIDE;
+            if (prevState == STATE_INSIDE) {
+                return FLAG_EXIT; // return exit only if previously entered
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Gets the distance from the current location to the fence's boundary.
+     * @return The distance or {@link Double#MAX_VALUE} if unknown.
+     */
+    public double getDistanceToBoundary() {
+        if (Double.compare(mDistanceToCenter, Double.MAX_VALUE) == 0) {
+            return Double.MAX_VALUE;
+        } else {
+            return Math.abs(mFence.getRadius() - mDistanceToCenter);
+        }
+    }
+
+    @Override
+    public String toString() {
+        String state;
+        switch (mState) {
+            case STATE_INSIDE:
+                state = "IN";
+                break;
+            case STATE_OUTSIDE:
+                state = "OUT";
+                break;
+            default:
+                state = "?";
+        }
+        return String.format("%s d=%.0f %s", mFence.toString(), mDistanceToCenter, state);
+    }
+}
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
new file mode 100644
index 0000000..9c76c19
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -0,0 +1,1924 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.app.AlarmManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.hardware.location.GeofenceHardware;
+import android.hardware.location.GeofenceHardwareImpl;
+import android.location.Criteria;
+import android.location.FusedBatchOptions;
+import android.location.IGpsGeofenceHardware;
+import android.location.IGpsStatusListener;
+import android.location.IGpsStatusProvider;
+import android.location.ILocationManager;
+import android.location.INetInitiatedListener;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationRequest;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.provider.Telephony.Carriers;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.SmsMessage;
+import android.telephony.TelephonyManager;
+import android.telephony.gsm.GsmCellLocation;
+import android.util.Log;
+import android.util.NtpTrustedTime;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+/**
+ * A GPS implementation of LocationProvider used by LocationManager.
+ *
+ * {@hide}
+ */
+public class GpsLocationProvider implements LocationProviderInterface {
+
+    private static final String TAG = "GpsLocationProvider";
+
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final ProviderProperties PROPERTIES = new ProviderProperties(
+            true, true, false, false, true, true, true,
+            Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
+
+    // these need to match GpsPositionMode enum in gps.h
+    private static final int GPS_POSITION_MODE_STANDALONE = 0;
+    private static final int GPS_POSITION_MODE_MS_BASED = 1;
+    private static final int GPS_POSITION_MODE_MS_ASSISTED = 2;
+
+    // these need to match GpsPositionRecurrence enum in gps.h
+    private static final int GPS_POSITION_RECURRENCE_PERIODIC = 0;
+    private static final int GPS_POSITION_RECURRENCE_SINGLE = 1;
+
+    // these need to match GpsStatusValue defines in gps.h
+    private static final int GPS_STATUS_NONE = 0;
+    private static final int GPS_STATUS_SESSION_BEGIN = 1;
+    private static final int GPS_STATUS_SESSION_END = 2;
+    private static final int GPS_STATUS_ENGINE_ON = 3;
+    private static final int GPS_STATUS_ENGINE_OFF = 4;
+
+    // these need to match GpsApgsStatusValue defines in gps.h
+    /** AGPS status event values. */
+    private static final int GPS_REQUEST_AGPS_DATA_CONN = 1;
+    private static final int GPS_RELEASE_AGPS_DATA_CONN = 2;
+    private static final int GPS_AGPS_DATA_CONNECTED = 3;
+    private static final int GPS_AGPS_DATA_CONN_DONE = 4;
+    private static final int GPS_AGPS_DATA_CONN_FAILED = 5;
+
+    // these need to match GpsLocationFlags enum in gps.h
+    private static final int LOCATION_INVALID = 0;
+    private static final int LOCATION_HAS_LAT_LONG = 1;
+    private static final int LOCATION_HAS_ALTITUDE = 2;
+    private static final int LOCATION_HAS_SPEED = 4;
+    private static final int LOCATION_HAS_BEARING = 8;
+    private static final int LOCATION_HAS_ACCURACY = 16;
+
+// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h
+    private static final int GPS_DELETE_EPHEMERIS = 0x0001;
+    private static final int GPS_DELETE_ALMANAC = 0x0002;
+    private static final int GPS_DELETE_POSITION = 0x0004;
+    private static final int GPS_DELETE_TIME = 0x0008;
+    private static final int GPS_DELETE_IONO = 0x0010;
+    private static final int GPS_DELETE_UTC = 0x0020;
+    private static final int GPS_DELETE_HEALTH = 0x0040;
+    private static final int GPS_DELETE_SVDIR = 0x0080;
+    private static final int GPS_DELETE_SVSTEER = 0x0100;
+    private static final int GPS_DELETE_SADATA = 0x0200;
+    private static final int GPS_DELETE_RTI = 0x0400;
+    private static final int GPS_DELETE_CELLDB_INFO = 0x8000;
+    private static final int GPS_DELETE_ALL = 0xFFFF;
+
+    // The GPS_CAPABILITY_* flags must match the values in gps.h
+    private static final int GPS_CAPABILITY_SCHEDULING = 0x0000001;
+    private static final int GPS_CAPABILITY_MSB = 0x0000002;
+    private static final int GPS_CAPABILITY_MSA = 0x0000004;
+    private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008;
+    private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010;
+
+    // these need to match AGpsType enum in gps.h
+    private static final int AGPS_TYPE_SUPL = 1;
+    private static final int AGPS_TYPE_C2K = 2;
+
+    // for mAGpsDataConnectionState
+    private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
+    private static final int AGPS_DATA_CONNECTION_OPENING = 1;
+    private static final int AGPS_DATA_CONNECTION_OPEN = 2;
+
+    // Handler messages
+    private static final int CHECK_LOCATION = 1;
+    private static final int ENABLE = 2;
+    private static final int SET_REQUEST = 3;
+    private static final int UPDATE_NETWORK_STATE = 4;
+    private static final int INJECT_NTP_TIME = 5;
+    private static final int DOWNLOAD_XTRA_DATA = 6;
+    private static final int UPDATE_LOCATION = 7;
+    private static final int ADD_LISTENER = 8;
+    private static final int REMOVE_LISTENER = 9;
+    private static final int INJECT_NTP_TIME_FINISHED = 10;
+    private static final int DOWNLOAD_XTRA_DATA_FINISHED = 11;
+
+    // Request setid
+    private static final int AGPS_RIL_REQUEST_SETID_IMSI = 1;
+    private static final int AGPS_RIL_REQUEST_SETID_MSISDN = 2;
+
+    // Request ref location
+    private static final int AGPS_RIL_REQUEST_REFLOC_CELLID = 1;
+    private static final int AGPS_RIL_REQUEST_REFLOC_MAC = 2;
+
+    // ref. location info
+    private static final int AGPS_REF_LOCATION_TYPE_GSM_CELLID = 1;
+    private static final int AGPS_REF_LOCATION_TYPE_UMTS_CELLID = 2;
+    private static final int AGPS_REG_LOCATION_TYPE_MAC        = 3;
+
+    // set id info
+    private static final int AGPS_SETID_TYPE_NONE = 0;
+    private static final int AGPS_SETID_TYPE_IMSI = 1;
+    private static final int AGPS_SETID_TYPE_MSISDN = 2;
+
+    private static final String PROPERTIES_FILE = "/etc/gps.conf";
+
+    private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L;
+    private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L;
+
+    // GPS Geofence errors. Should match gps.h constants.
+    private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0;
+    private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100;
+    private static final int GPS_GEOFENCE_ERROR_ID_EXISTS  = -101;
+    private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102;
+    private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103;
+    private static final int GPS_GEOFENCE_ERROR_GENERIC = -149;
+
+    /** simpler wrapper for ProviderRequest + Worksource */
+    private static class GpsRequest {
+        public ProviderRequest request;
+        public WorkSource source;
+        public GpsRequest(ProviderRequest request, WorkSource source) {
+            this.request = request;
+            this.source = source;
+        }
+    }
+
+    private Object mLock = new Object();
+
+    private int mLocationFlags = LOCATION_INVALID;
+
+    // current status
+    private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
+
+    // time for last status update
+    private long mStatusUpdateTime = SystemClock.elapsedRealtime();
+
+    // turn off GPS fix icon if we haven't received a fix in 10 seconds
+    private static final long RECENT_FIX_TIMEOUT = 10 * 1000;
+
+    // stop trying if we do not receive a fix within 60 seconds
+    private static final int NO_FIX_TIMEOUT = 60 * 1000;
+
+    // if the fix interval is below this we leave GPS on,
+    // if above then we cycle the GPS driver.
+    // Typical hot TTTF is ~5 seconds, so 10 seconds seems sane.
+    private static final int GPS_POLLING_THRESHOLD_INTERVAL = 10 * 1000;
+
+    // how often to request NTP time, in milliseconds
+    // current setting 24 hours
+    private static final long NTP_INTERVAL = 24*60*60*1000;
+    // how long to wait if we have a network error in NTP or XTRA downloading
+    // current setting - 5 minutes
+    private static final long RETRY_INTERVAL = 5*60*1000;
+
+    // true if we are enabled, protected by this
+    private boolean mEnabled;
+
+    // true if we have network connectivity
+    private boolean mNetworkAvailable;
+
+    // states for injecting ntp and downloading xtra data
+    private static final int STATE_PENDING_NETWORK = 0;
+    private static final int STATE_DOWNLOADING = 1;
+    private static final int STATE_IDLE = 2;
+
+    // flags to trigger NTP or XTRA data download when network becomes available
+    // initialized to true so we do NTP and XTRA when the network comes up after booting
+    private int mInjectNtpTimePending = STATE_PENDING_NETWORK;
+    private int mDownloadXtraDataPending = STATE_PENDING_NETWORK;
+
+    // set to true if the GPS engine does not do on-demand NTP time requests
+    private boolean mPeriodicTimeInjection;
+
+    // true if GPS is navigating
+    private boolean mNavigating;
+
+    // true if GPS engine is on
+    private boolean mEngineOn;
+
+    // requested frequency of fixes, in milliseconds
+    private int mFixInterval = 1000;
+
+    // true if we started navigation
+    private boolean mStarted;
+
+    // true if single shot request is in progress
+    private boolean mSingleShot;
+
+    // capabilities of the GPS engine
+    private int mEngineCapabilities;
+
+    // true if XTRA is supported
+    private boolean mSupportsXtra;
+
+    // for calculating time to first fix
+    private long mFixRequestTime = 0;
+    // time to first fix for most recent session
+    private int mTimeToFirstFix = 0;
+    // time we received our last fix
+    private long mLastFixTime;
+
+    private int mPositionMode;
+
+    // properties loaded from PROPERTIES_FILE
+    private Properties mProperties;
+    private String mSuplServerHost;
+    private int mSuplServerPort;
+    private String mC2KServerHost;
+    private int mC2KServerPort;
+
+    private final Context mContext;
+    private final NtpTrustedTime mNtpTime;
+    private final ILocationManager mILocationManager;
+    private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
+    private Bundle mLocationExtras = new Bundle();
+    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
+
+    // Handler for processing events
+    private Handler mHandler;
+
+    private String mAGpsApn;
+    private int mAGpsDataConnectionState;
+    private int mAGpsDataConnectionIpAddr;
+    private final ConnectivityManager mConnMgr;
+    private final GpsNetInitiatedHandler mNIHandler;
+
+    // Wakelocks
+    private final static String WAKELOCK_KEY = "GpsLocationProvider";
+    private final PowerManager.WakeLock mWakeLock;
+
+    // Alarms
+    private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
+    private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
+    private final AlarmManager mAlarmManager;
+    private final PendingIntent mWakeupIntent;
+    private final PendingIntent mTimeoutIntent;
+
+    private final IAppOpsService mAppOpsService;
+    private final IBatteryStats mBatteryStats;
+
+    // only modified on handler thread
+    private WorkSource mClientSource = new WorkSource();
+
+    private GeofenceHardwareImpl mGeofenceHardwareImpl;
+
+    private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
+        @Override
+        public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
+            if (listener == null) {
+                throw new NullPointerException("listener is null in addGpsStatusListener");
+            }
+
+            synchronized (mListeners) {
+                IBinder binder = listener.asBinder();
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener test = mListeners.get(i);
+                    if (binder.equals(test.mListener.asBinder())) {
+                        // listener already added
+                        return;
+                    }
+                }
+
+                Listener l = new Listener(listener);
+                binder.linkToDeath(l, 0);
+                mListeners.add(l);
+            }
+        }
+
+        @Override
+        public void removeGpsStatusListener(IGpsStatusListener listener) {
+            if (listener == null) {
+                throw new NullPointerException("listener is null in addGpsStatusListener");
+            }
+
+            synchronized (mListeners) {
+                IBinder binder = listener.asBinder();
+                Listener l = null;
+                int size = mListeners.size();
+                for (int i = 0; i < size && l == null; i++) {
+                    Listener test = mListeners.get(i);
+                    if (binder.equals(test.mListener.asBinder())) {
+                        l = test;
+                    }
+                }
+
+                if (l != null) {
+                    mListeners.remove(l);
+                    binder.unlinkToDeath(l, 0);
+                }
+            }
+        }
+    };
+
+    public IGpsStatusProvider getGpsStatusProvider() {
+        return mGpsStatusProvider;
+    }
+
+    public IGpsGeofenceHardware getGpsGeofenceProxy() {
+        return mGpsGeofenceBinder;
+    }
+
+    private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (action.equals(ALARM_WAKEUP)) {
+                if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
+                startNavigating(false);
+            } else if (action.equals(ALARM_TIMEOUT)) {
+                if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
+                hibernate();
+            } else if (action.equals(Intents.DATA_SMS_RECEIVED_ACTION)) {
+                checkSmsSuplInit(intent);
+            } else if (action.equals(Intents.WAP_PUSH_RECEIVED_ACTION)) {
+                checkWapSuplInit(intent);
+             } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                 int networkState;
+                 if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
+                     networkState = LocationProvider.TEMPORARILY_UNAVAILABLE;
+                 } else {
+                     networkState = LocationProvider.AVAILABLE;
+                 }
+
+                 // retrieve NetworkInfo result for this UID
+                 NetworkInfo info =
+                         intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
+                 ConnectivityManager connManager = (ConnectivityManager)
+                         mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+                 info = connManager.getNetworkInfo(info.getType());
+
+                 updateNetworkState(networkState, info);
+             }
+        }
+    };
+
+    private void checkSmsSuplInit(Intent intent) {
+        SmsMessage[] messages = Intents.getMessagesFromIntent(intent);
+        for (int i=0; i <messages.length; i++) {
+            byte[] supl_init = messages[i].getUserData();
+            native_agps_ni_message(supl_init,supl_init.length);
+        }
+    }
+
+    private void checkWapSuplInit(Intent intent) {
+        byte[] supl_init = (byte[]) intent.getExtra("data");
+        native_agps_ni_message(supl_init,supl_init.length);
+    }
+
+    public static boolean isSupported() {
+        return native_is_supported();
+    }
+
+    public GpsLocationProvider(Context context, ILocationManager ilocationManager,
+            Looper looper) {
+        mContext = context;
+        mNtpTime = NtpTrustedTime.getInstance(context);
+        mILocationManager = ilocationManager;
+        mNIHandler = new GpsNetInitiatedHandler(context);
+
+        mLocation.setExtras(mLocationExtras);
+
+        // Create a wake lock
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+        mWakeLock.setReferenceCounted(true);
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
+        mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
+
+        mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        // App ops service to keep track of who is accessing the GPS
+        mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(
+                Context.APP_OPS_SERVICE));
+
+        // Battery statistics service to be notified when GPS turns on or off
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
+                BatteryStats.SERVICE_NAME));
+
+        mProperties = new Properties();
+        try {
+            File file = new File(PROPERTIES_FILE);
+            FileInputStream stream = new FileInputStream(file);
+            mProperties.load(stream);
+            stream.close();
+
+            mSuplServerHost = mProperties.getProperty("SUPL_HOST");
+            String portString = mProperties.getProperty("SUPL_PORT");
+            if (mSuplServerHost != null && portString != null) {
+                try {
+                    mSuplServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
+                }
+            }
+
+            mC2KServerHost = mProperties.getProperty("C2K_HOST");
+            portString = mProperties.getProperty("C2K_PORT");
+            if (mC2KServerHost != null && portString != null) {
+                try {
+                    mC2KServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse C2K_PORT: " + portString);
+                }
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+        }
+
+        // construct handler, listen for events
+        mHandler = new ProviderHandler(looper);
+        listenForBroadcasts();
+
+        // also listen for PASSIVE_PROVIDER updates
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                LocationManager locManager =
+                        (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+                final long minTime = 0;
+                final float minDistance = 0;
+                final boolean oneShot = false;
+                LocationRequest request = LocationRequest.createFromDeprecatedProvider(
+                        LocationManager.PASSIVE_PROVIDER,
+                        minTime,
+                        minDistance,
+                        oneShot);
+                // Don't keep track of this request since it's done on behalf of other clients
+                // (which are kept track of separately).
+                request.setHideFromAppOps(true);
+                locManager.requestLocationUpdates(
+                        request,
+                        new NetworkLocationListener(),
+                        mHandler.getLooper());
+            }
+        });
+    }
+
+    private void listenForBroadcasts() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intents.DATA_SMS_RECEIVED_ACTION);
+        intentFilter.addDataScheme("sms");
+        intentFilter.addDataAuthority("localhost","7275");
+        mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler);
+
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(Intents.WAP_PUSH_RECEIVED_ACTION);
+        try {
+            intentFilter.addDataType("application/vnd.omaloc-supl-init");
+        } catch (IntentFilter.MalformedMimeTypeException e) {
+            Log.w(TAG, "Malformed SUPL init mime type");
+        }
+        mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler);
+
+        intentFilter = new IntentFilter();
+        intentFilter.addAction(ALARM_WAKEUP);
+        intentFilter.addAction(ALARM_TIMEOUT);
+        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        mContext.registerReceiver(mBroadcastReciever, intentFilter, null, mHandler);
+    }
+
+    /**
+     * Returns the name of this provider.
+     */
+    @Override
+    public String getName() {
+        return LocationManager.GPS_PROVIDER;
+    }
+
+    @Override
+    public ProviderProperties getProperties() {
+        return PROPERTIES;
+    }
+
+    public void updateNetworkState(int state, NetworkInfo info) {
+        sendMessage(UPDATE_NETWORK_STATE, state, info);
+    }
+
+    private void handleUpdateNetworkState(int state, NetworkInfo info) {
+        mNetworkAvailable = (state == LocationProvider.AVAILABLE);
+
+        if (DEBUG) {
+            Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable")
+                + " info: " + info);
+        }
+
+        if (info != null) {
+            boolean dataEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                                                         Settings.Global.MOBILE_DATA, 1) == 1;
+            boolean networkAvailable = info.isAvailable() && dataEnabled;
+            String defaultApn = getSelectedApn();
+            if (defaultApn == null) {
+                defaultApn = "dummy-apn";
+            }
+
+            native_update_network_state(info.isConnected(), info.getType(),
+                                        info.isRoaming(), networkAvailable,
+                                        info.getExtraInfo(), defaultApn);
+        }
+
+        if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL
+                && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) {
+            String apnName = info.getExtraInfo();
+            if (mNetworkAvailable) {
+                if (apnName == null) {
+                    /* Assign a dummy value in the case of C2K as otherwise we will have a runtime
+                    exception in the following call to native_agps_data_conn_open*/
+                    apnName = "dummy-apn";
+                }
+                mAGpsApn = apnName;
+                if (DEBUG) Log.d(TAG, "mAGpsDataConnectionIpAddr " + mAGpsDataConnectionIpAddr);
+                if (mAGpsDataConnectionIpAddr != 0xffffffff) {
+                    boolean route_result;
+                    if (DEBUG) Log.d(TAG, "call requestRouteToHost");
+                    route_result = mConnMgr.requestRouteToHost(ConnectivityManager.TYPE_MOBILE_SUPL,
+                        mAGpsDataConnectionIpAddr);
+                    if (route_result == false) Log.d(TAG, "call requestRouteToHost failed");
+                }
+                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open");
+                native_agps_data_conn_open(apnName);
+                mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
+            } else {
+                if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed");
+                mAGpsApn = null;
+                mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                native_agps_data_conn_failed();
+            }
+        }
+
+        if (mNetworkAvailable) {
+            if (mInjectNtpTimePending == STATE_PENDING_NETWORK) {
+                sendMessage(INJECT_NTP_TIME, 0, null);
+            }
+            if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) {
+                sendMessage(DOWNLOAD_XTRA_DATA, 0, null);
+            }
+        }
+    }
+
+    private void handleInjectNtpTime() {
+        if (mInjectNtpTimePending == STATE_DOWNLOADING) {
+            // already downloading data
+            return;
+        }
+        if (!mNetworkAvailable) {
+            // try again when network is up
+            mInjectNtpTimePending = STATE_PENDING_NETWORK;
+            return;
+        }
+        mInjectNtpTimePending = STATE_DOWNLOADING;
+
+        // hold wake lock while task runs
+        mWakeLock.acquire();
+        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+            @Override
+            public void run() {
+                long delay;
+
+                // force refresh NTP cache when outdated
+                if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+                    mNtpTime.forceRefresh();
+                }
+
+                // only update when NTP time is fresh
+                if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
+                    long time = mNtpTime.getCachedNtpTime();
+                    long timeReference = mNtpTime.getCachedNtpTimeReference();
+                    long certainty = mNtpTime.getCacheCertainty();
+                    long now = System.currentTimeMillis();
+
+                    Log.d(TAG, "NTP server returned: "
+                            + time + " (" + new Date(time)
+                            + ") reference: " + timeReference
+                            + " certainty: " + certainty
+                            + " system time offset: " + (time - now));
+
+                    native_inject_time(time, timeReference, (int) certainty);
+                    delay = NTP_INTERVAL;
+                } else {
+                    if (DEBUG) Log.d(TAG, "requestTime failed");
+                    delay = RETRY_INTERVAL;
+                }
+
+                sendMessage(INJECT_NTP_TIME_FINISHED, 0, null);
+
+                if (mPeriodicTimeInjection) {
+                    // send delayed message for next NTP injection
+                    // since this is delayed and not urgent we do not hold a wake lock here
+                    mHandler.sendEmptyMessageDelayed(INJECT_NTP_TIME, delay);
+                }
+
+                // release wake lock held by task
+                mWakeLock.release();
+            }
+        });
+    }
+
+    private void handleDownloadXtraData() {
+        if (mDownloadXtraDataPending == STATE_DOWNLOADING) {
+            // already downloading data
+            return;
+        }
+        if (!mNetworkAvailable) {
+            // try again when network is up
+            mDownloadXtraDataPending = STATE_PENDING_NETWORK;
+            return;
+        }
+        mDownloadXtraDataPending = STATE_DOWNLOADING;
+
+        // hold wake lock while task runs
+        mWakeLock.acquire();
+        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+            @Override
+            public void run() {
+                GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
+                byte[] data = xtraDownloader.downloadXtraData();
+                if (data != null) {
+                    if (DEBUG) {
+                        Log.d(TAG, "calling native_inject_xtra_data");
+                    }
+                    native_inject_xtra_data(data, data.length);
+                }
+
+                sendMessage(DOWNLOAD_XTRA_DATA_FINISHED, 0, null);
+
+                if (data == null) {
+                    // try again later
+                    // since this is delayed and not urgent we do not hold a wake lock here
+                    mHandler.sendEmptyMessageDelayed(DOWNLOAD_XTRA_DATA, RETRY_INTERVAL);
+                }
+
+                // release wake lock held by task
+                mWakeLock.release();
+            }
+        });
+    }
+
+    private void handleUpdateLocation(Location location) {
+        if (location.hasAccuracy()) {
+            native_inject_location(location.getLatitude(), location.getLongitude(),
+                    location.getAccuracy());
+        }
+    }
+
+    /**
+     * Enables this provider.  When enabled, calls to getStatus()
+     * must be handled.  Hardware may be started up
+     * when the provider is enabled.
+     */
+    @Override
+    public void enable() {
+        synchronized (mLock) {
+            if (mEnabled) return;
+            mEnabled = true;
+        }
+
+        sendMessage(ENABLE, 1, null);
+    }
+
+    private void handleEnable() {
+        if (DEBUG) Log.d(TAG, "handleEnable");
+
+        boolean enabled = native_init();
+
+        if (enabled) {
+            mSupportsXtra = native_supports_xtra();
+            if (mSuplServerHost != null) {
+                native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort);
+            }
+            if (mC2KServerHost != null) {
+                native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort);
+            }
+        } else {
+            synchronized (mLock) {
+                mEnabled = false;
+            }
+            Log.w(TAG, "Failed to enable location provider");
+        }
+    }
+
+    /**
+     * Disables this provider.  When disabled, calls to getStatus()
+     * need not be handled.  Hardware may be shut
+     * down while the provider is disabled.
+     */
+    @Override
+    public void disable() {
+        synchronized (mLock) {
+            if (!mEnabled) return;
+            mEnabled = false;
+        }
+
+        sendMessage(ENABLE, 0, null);
+    }
+
+    private void handleDisable() {
+        if (DEBUG) Log.d(TAG, "handleDisable");
+
+        stopNavigating();
+        mAlarmManager.cancel(mWakeupIntent);
+        mAlarmManager.cancel(mTimeoutIntent);
+
+        // do this before releasing wakelock
+        native_cleanup();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        synchronized (mLock) {
+            return mEnabled;
+        }
+    }
+
+    @Override
+    public int getStatus(Bundle extras) {
+        if (extras != null) {
+            extras.putInt("satellites", mSvCount);
+        }
+        return mStatus;
+    }
+
+    private void updateStatus(int status, int svCount) {
+        if (status != mStatus || svCount != mSvCount) {
+            mStatus = status;
+            mSvCount = svCount;
+            mLocationExtras.putInt("satellites", svCount);
+            mStatusUpdateTime = SystemClock.elapsedRealtime();
+        }
+    }
+
+    @Override
+    public long getStatusUpdateTime() {
+        return mStatusUpdateTime;
+    }
+
+    @Override
+    public void setRequest(ProviderRequest request, WorkSource source) {
+        sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
+    }
+
+    private void handleSetRequest(ProviderRequest request, WorkSource source) {
+        boolean singleShot = false;
+
+        // see if the request is for a single update
+        if (request.locationRequests != null && request.locationRequests.size() > 0) {
+            // if any request has zero or more than one updates
+            // requested, then this is not single-shot mode
+            singleShot = true;
+
+            for (LocationRequest lr : request.locationRequests) {
+                if (lr.getNumUpdates() != 1) {
+                    singleShot = false;
+                }
+            }
+        }
+
+        if (DEBUG) Log.d(TAG, "setRequest " + request);
+        if (request.reportLocation) {
+            // update client uids
+            updateClientUids(source);
+
+            mFixInterval = (int) request.interval;
+
+            // check for overflow
+            if (mFixInterval != request.interval) {
+                Log.w(TAG, "interval overflow: " + request.interval);
+                mFixInterval = Integer.MAX_VALUE;
+            }
+
+            // apply request to GPS engine
+            if (mStarted && hasCapability(GPS_CAPABILITY_SCHEDULING)) {
+                // change period
+                if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC,
+                        mFixInterval, 0, 0)) {
+                    Log.e(TAG, "set_position_mode failed in setMinTime()");
+                }
+            } else if (!mStarted) {
+                // start GPS
+                startNavigating(singleShot);
+            }
+        } else {
+            updateClientUids(new WorkSource());
+
+            stopNavigating();
+            mAlarmManager.cancel(mWakeupIntent);
+            mAlarmManager.cancel(mTimeoutIntent);
+        }
+    }
+
+    private final class Listener implements IBinder.DeathRecipient {
+        final IGpsStatusListener mListener;
+
+        Listener(IGpsStatusListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) Log.d(TAG, "GPS status listener died");
+
+            synchronized (mListeners) {
+                mListeners.remove(this);
+            }
+            if (mListener != null) {
+                mListener.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+    }
+
+    private void updateClientUids(WorkSource source) {
+        // Update work source.
+        WorkSource[] changes = mClientSource.setReturningDiffs(source);
+        if (changes == null) {
+            return;
+        }
+        WorkSource newWork = changes[0];
+        WorkSource goneWork = changes[1];
+
+        // Update sources that were not previously tracked.
+        if (newWork != null) {
+            int lastuid = -1;
+            for (int i=0; i<newWork.size(); i++) {
+                try {
+                    int uid = newWork.get(i);
+                    mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                            AppOpsManager.OP_GPS, uid, newWork.getName(i));
+                    if (uid != lastuid) {
+                        lastuid = uid;
+                        mBatteryStats.noteStartGps(uid);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException", e);
+                }
+            }
+        }
+
+        // Update sources that are no longer tracked.
+        if (goneWork != null) {
+            int lastuid = -1;
+            for (int i=0; i<goneWork.size(); i++) {
+                try {
+                    int uid = goneWork.get(i);
+                    mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+                            AppOpsManager.OP_GPS, uid, goneWork.getName(i));
+                    if (uid != lastuid) {
+                        lastuid = uid;
+                        mBatteryStats.noteStopGps(uid);
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException", e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean sendExtraCommand(String command, Bundle extras) {
+
+        long identity = Binder.clearCallingIdentity();
+        boolean result = false;
+
+        if ("delete_aiding_data".equals(command)) {
+            result = deleteAidingData(extras);
+        } else if ("force_time_injection".equals(command)) {
+            sendMessage(INJECT_NTP_TIME, 0, null);
+            result = true;
+        } else if ("force_xtra_injection".equals(command)) {
+            if (mSupportsXtra) {
+                xtraDownloadRequest();
+                result = true;
+            }
+        } else {
+            Log.w(TAG, "sendExtraCommand: unknown command " + command);
+        }
+
+        Binder.restoreCallingIdentity(identity);
+        return result;
+    }
+
+    private IGpsGeofenceHardware mGpsGeofenceBinder = new IGpsGeofenceHardware.Stub() {
+        public boolean isHardwareGeofenceSupported() {
+            return native_is_geofence_supported();
+        }
+
+        public boolean addCircularHardwareGeofence(int geofenceId, double latitude,
+                double longitude, double radius, int lastTransition, int monitorTransitions,
+                int notificationResponsiveness, int unknownTimer) {
+            return native_add_geofence(geofenceId, latitude, longitude, radius,
+                    lastTransition, monitorTransitions, notificationResponsiveness, unknownTimer);
+        }
+
+        public boolean removeHardwareGeofence(int geofenceId) {
+            return native_remove_geofence(geofenceId);
+        }
+
+        public boolean pauseHardwareGeofence(int geofenceId) {
+            return native_pause_geofence(geofenceId);
+        }
+
+        public boolean resumeHardwareGeofence(int geofenceId, int monitorTransition) {
+            return native_resume_geofence(geofenceId, monitorTransition);
+        }
+    };
+
+    private boolean deleteAidingData(Bundle extras) {
+        int flags;
+
+        if (extras == null) {
+            flags = GPS_DELETE_ALL;
+        } else {
+            flags = 0;
+            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
+            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
+            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
+            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
+            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
+            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
+            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
+            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
+            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
+            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
+            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
+            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
+            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
+        }
+
+        if (flags != 0) {
+            native_delete_aiding_data(flags);
+            return true;
+        }
+
+        return false;
+    }
+
+    private void startNavigating(boolean singleShot) {
+        if (!mStarted) {
+            if (DEBUG) Log.d(TAG, "startNavigating, singleShot is " + singleShot);
+            mTimeToFirstFix = 0;
+            mLastFixTime = 0;
+            mStarted = true;
+            mSingleShot = singleShot;
+            mPositionMode = GPS_POSITION_MODE_STANDALONE;
+
+             if (Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0) {
+                if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) {
+                    mPositionMode = GPS_POSITION_MODE_MS_ASSISTED;
+                } else if (hasCapability(GPS_CAPABILITY_MSB)) {
+                    mPositionMode = GPS_POSITION_MODE_MS_BASED;
+                }
+            }
+
+            if (DEBUG) {
+                String mode;
+
+                switch(mPositionMode) {
+                    case GPS_POSITION_MODE_STANDALONE:
+                        mode = "standalone";
+                        break;
+                    case GPS_POSITION_MODE_MS_ASSISTED:
+                        mode = "MS_ASSISTED";
+                        break;
+                    case GPS_POSITION_MODE_MS_BASED:
+                        mode = "MS_BASED";
+                        break;
+                    default:
+                        mode = "unknown";
+                        break;
+                }
+                Log.d(TAG, "setting position_mode to " + mode);
+            }
+
+            int interval = (hasCapability(GPS_CAPABILITY_SCHEDULING) ? mFixInterval : 1000);
+            if (!native_set_position_mode(mPositionMode, GPS_POSITION_RECURRENCE_PERIODIC,
+                    interval, 0, 0)) {
+                mStarted = false;
+                Log.e(TAG, "set_position_mode failed in startNavigating()");
+                return;
+            }
+            if (!native_start()) {
+                mStarted = false;
+                Log.e(TAG, "native_start failed in startNavigating()");
+                return;
+            }
+
+            // reset SV count to zero
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+            mFixRequestTime = System.currentTimeMillis();
+            if (!hasCapability(GPS_CAPABILITY_SCHEDULING)) {
+                // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
+                // and our fix interval is not short
+                if (mFixInterval >= NO_FIX_TIMEOUT) {
+                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                            SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, mTimeoutIntent);
+                }
+            }
+        }
+    }
+
+    private void stopNavigating() {
+        if (DEBUG) Log.d(TAG, "stopNavigating");
+        if (mStarted) {
+            mStarted = false;
+            mSingleShot = false;
+            native_stop();
+            mTimeToFirstFix = 0;
+            mLastFixTime = 0;
+            mLocationFlags = LOCATION_INVALID;
+
+            // reset SV count to zero
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+        }
+    }
+
+    private void hibernate() {
+        // stop GPS until our next fix interval arrives
+        stopNavigating();
+        mAlarmManager.cancel(mTimeoutIntent);
+        mAlarmManager.cancel(mWakeupIntent);
+        long now = SystemClock.elapsedRealtime();
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, mWakeupIntent);
+    }
+
+    private boolean hasCapability(int capability) {
+        return ((mEngineCapabilities & capability) != 0);
+    }
+
+
+    /**
+     * called from native code to update our position.
+     */
+    private void reportLocation(int flags, double latitude, double longitude, double altitude,
+            float speed, float bearing, float accuracy, long timestamp) {
+        if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
+                " timestamp: " + timestamp);
+
+        synchronized (mLocation) {
+            mLocationFlags = flags;
+            if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+                mLocation.setLatitude(latitude);
+                mLocation.setLongitude(longitude);
+                mLocation.setTime(timestamp);
+                // It would be nice to push the elapsed real-time timestamp
+                // further down the stack, but this is still useful
+                mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+            }
+            if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
+                mLocation.setAltitude(altitude);
+            } else {
+                mLocation.removeAltitude();
+            }
+            if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+                mLocation.setSpeed(speed);
+            } else {
+                mLocation.removeSpeed();
+            }
+            if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
+                mLocation.setBearing(bearing);
+            } else {
+                mLocation.removeBearing();
+            }
+            if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
+                mLocation.setAccuracy(accuracy);
+            } else {
+                mLocation.removeAccuracy();
+            }
+            mLocation.setExtras(mLocationExtras);
+
+            try {
+                mILocationManager.reportLocation(mLocation, false);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException calling reportLocation");
+            }
+        }
+
+        mLastFixTime = System.currentTimeMillis();
+        // report time to first fix
+        if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+            mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime);
+            if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
+
+            // notify status listeners
+            synchronized (mListeners) {
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener listener = mListeners.get(i);
+                    try {
+                        listener.mListener.onFirstFix(mTimeToFirstFix);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException in stopNavigating");
+                        mListeners.remove(listener);
+                        // adjust for size of list changing
+                        size--;
+                    }
+                }
+            }
+        }
+
+        if (mSingleShot) {
+            stopNavigating();
+        }
+
+        if (mStarted && mStatus != LocationProvider.AVAILABLE) {
+            // we want to time out if we do not receive a fix
+            // within the time out and we are requesting infrequent fixes
+            if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) {
+                mAlarmManager.cancel(mTimeoutIntent);
+            }
+
+            // send an intent to notify that the GPS is receiving fixes.
+            Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+            intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            updateStatus(LocationProvider.AVAILABLE, mSvCount);
+        }
+
+       if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted &&
+               mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
+            if (DEBUG) Log.d(TAG, "got fix, hibernating");
+            hibernate();
+        }
+   }
+
+    /**
+     * called from native code to update our status
+     */
+    private void reportStatus(int status) {
+        if (DEBUG) Log.v(TAG, "reportStatus status: " + status);
+
+        synchronized (mListeners) {
+            boolean wasNavigating = mNavigating;
+
+            switch (status) {
+                case GPS_STATUS_SESSION_BEGIN:
+                    mNavigating = true;
+                    mEngineOn = true;
+                    break;
+                case GPS_STATUS_SESSION_END:
+                    mNavigating = false;
+                    break;
+                case GPS_STATUS_ENGINE_ON:
+                    mEngineOn = true;
+                    break;
+                case GPS_STATUS_ENGINE_OFF:
+                    mEngineOn = false;
+                    mNavigating = false;
+                    break;
+            }
+
+            if (wasNavigating != mNavigating) {
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener listener = mListeners.get(i);
+                    try {
+                        if (mNavigating) {
+                            listener.mListener.onGpsStarted();
+                        } else {
+                            listener.mListener.onGpsStopped();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException in reportStatus");
+                        mListeners.remove(listener);
+                        // adjust for size of list changing
+                        size--;
+                    }
+                }
+
+                // send an intent to notify that the GPS has been enabled or disabled.
+                Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+                intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
+                mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            }
+        }
+    }
+
+    /**
+     * called from native code to update SV info
+     */
+    private void reportSvStatus() {
+
+        int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
+
+        synchronized (mListeners) {
+            int size = mListeners.size();
+            for (int i = 0; i < size; i++) {
+                Listener listener = mListeners.get(i);
+                try {
+                    listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs,
+                            mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK],
+                            mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException in reportSvInfo");
+                    mListeners.remove(listener);
+                    // adjust for size of list changing
+                    size--;
+                }
+            }
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "SV count: " + svCount +
+                    " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) +
+                    " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK]));
+            for (int i = 0; i < svCount; i++) {
+                Log.v(TAG, "sv: " + mSvs[i] +
+                        " snr: " + mSnrs[i]/10 +
+                        " elev: " + mSvElevations[i] +
+                        " azimuth: " + mSvAzimuths[i] +
+                        ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " E") +
+                        ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " A") +
+                        ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U"));
+            }
+        }
+
+        // return number of sets used in fix instead of total
+        updateStatus(mStatus, Integer.bitCount(mSvMasks[USED_FOR_FIX_MASK]));
+
+        if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
+            System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT) {
+            // send an intent to notify that the GPS is no longer receiving fixes.
+            Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+            intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
+        }
+    }
+
+    /**
+     * called from native code to update AGPS status
+     */
+    private void reportAGpsStatus(int type, int status, int ipaddr) {
+        switch (status) {
+            case GPS_REQUEST_AGPS_DATA_CONN:
+                if (DEBUG) Log.d(TAG, "GPS_REQUEST_AGPS_DATA_CONN");
+                // Set mAGpsDataConnectionState before calling startUsingNetworkFeature
+                //  to avoid a race condition with handleUpdateNetworkState()
+                mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
+                int result = mConnMgr.startUsingNetworkFeature(
+                        ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
+                mAGpsDataConnectionIpAddr = ipaddr;
+                if (result == PhoneConstants.APN_ALREADY_ACTIVE) {
+                    if (DEBUG) Log.d(TAG, "PhoneConstants.APN_ALREADY_ACTIVE");
+                    if (mAGpsApn != null) {
+                        Log.d(TAG, "mAGpsDataConnectionIpAddr " + mAGpsDataConnectionIpAddr);
+                        if (mAGpsDataConnectionIpAddr != 0xffffffff) {
+                            boolean route_result;
+                            if (DEBUG) Log.d(TAG, "call requestRouteToHost");
+                            route_result = mConnMgr.requestRouteToHost(
+                                ConnectivityManager.TYPE_MOBILE_SUPL,
+                                mAGpsDataConnectionIpAddr);
+                            if (route_result == false) Log.d(TAG, "call requestRouteToHost failed");
+                        }
+                        native_agps_data_conn_open(mAGpsApn);
+                        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
+                    } else {
+                        Log.e(TAG, "mAGpsApn not set when receiving PhoneConstants.APN_ALREADY_ACTIVE");
+                        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                        native_agps_data_conn_failed();
+                    }
+                } else if (result == PhoneConstants.APN_REQUEST_STARTED) {
+                    if (DEBUG) Log.d(TAG, "PhoneConstants.APN_REQUEST_STARTED");
+                    // Nothing to do here
+                } else {
+                    if (DEBUG) Log.d(TAG, "startUsingNetworkFeature failed, value is " +
+                                     result);
+                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                    native_agps_data_conn_failed();
+                }
+                break;
+            case GPS_RELEASE_AGPS_DATA_CONN:
+                if (DEBUG) Log.d(TAG, "GPS_RELEASE_AGPS_DATA_CONN");
+                if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
+                    mConnMgr.stopUsingNetworkFeature(
+                            ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
+                    native_agps_data_conn_closed();
+                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                }
+                break;
+            case GPS_AGPS_DATA_CONNECTED:
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONNECTED");
+                break;
+            case GPS_AGPS_DATA_CONN_DONE:
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE");
+                break;
+            case GPS_AGPS_DATA_CONN_FAILED:
+                if (DEBUG) Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED");
+                break;
+        }
+    }
+
+    /**
+     * called from native code to report NMEA data received
+     */
+    private void reportNmea(long timestamp) {
+        synchronized (mListeners) {
+            int size = mListeners.size();
+            if (size > 0) {
+                // don't bother creating the String if we have no listeners
+                int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length);
+                String nmea = new String(mNmeaBuffer, 0, length);
+
+                for (int i = 0; i < size; i++) {
+                    Listener listener = mListeners.get(i);
+                    try {
+                        listener.mListener.onNmeaReceived(timestamp, nmea);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException in reportNmea");
+                        mListeners.remove(listener);
+                        // adjust for size of list changing
+                        size--;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * called from native code to inform us what the GPS engine capabilities are
+     */
+    private void setEngineCapabilities(int capabilities) {
+        mEngineCapabilities = capabilities;
+
+        if (!hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME) && !mPeriodicTimeInjection) {
+            mPeriodicTimeInjection = true;
+            requestUtcTime();
+        }
+    }
+
+    /**
+     * called from native code to request XTRA data
+     */
+    private void xtraDownloadRequest() {
+        if (DEBUG) Log.d(TAG, "xtraDownloadRequest");
+        sendMessage(DOWNLOAD_XTRA_DATA, 0, null);
+    }
+
+    /**
+     * Helper method to construct a location object.
+     */
+    private Location buildLocation(
+            int flags,
+            double latitude,
+            double longitude,
+            double altitude,
+            float speed,
+            float bearing,
+            float accuracy,
+            long timestamp) {
+        Location location = new Location(LocationManager.GPS_PROVIDER);
+        if((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+            location.setLatitude(latitude);
+            location.setLongitude(longitude);
+            location.setTime(timestamp);
+            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        }
+        if((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
+            location.setAltitude(altitude);
+        }
+        if((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+            location.setSpeed(speed);
+        }
+        if((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
+            location.setBearing(bearing);
+        }
+        if((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
+            location.setAccuracy(accuracy);
+        }
+        return location;
+    }
+
+    /**
+     * Converts the GPS HAL status to the internal Geofence Hardware status.
+     */
+    private int getGeofenceStatus(int status) {
+        switch(status) {
+            case GPS_GEOFENCE_OPERATION_SUCCESS:
+                return GeofenceHardware.GEOFENCE_SUCCESS;
+            case GPS_GEOFENCE_ERROR_GENERIC:
+                return GeofenceHardware.GEOFENCE_FAILURE;
+            case GPS_GEOFENCE_ERROR_ID_EXISTS:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS;
+            case GPS_GEOFENCE_ERROR_INVALID_TRANSITION:
+                return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION;
+            case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES:
+                return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES;
+            case GPS_GEOFENCE_ERROR_ID_UNKNOWN:
+                return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN;
+            default:
+                return -1;
+        }
+    }
+
+    /**
+     * Called from native to report GPS Geofence transition
+     * All geofence callbacks are called on the same thread
+     */
+    private void reportGeofenceTransition(int geofenceId, int flags, double latitude,
+            double longitude, double altitude, float speed, float bearing, float accuracy,
+            long timestamp, int transition, long transitionTimestamp) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        Location location = buildLocation(
+                flags,
+                latitude,
+                longitude,
+                altitude,
+                speed,
+                bearing,
+                accuracy,
+                timestamp);
+        mGeofenceHardwareImpl.reportGeofenceTransition(
+                geofenceId,
+                location,
+                transition,
+                transitionTimestamp,
+                GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+                FusedBatchOptions.SourceTechnologies.GNSS);
+    }
+
+    /**
+     * called from native code to report GPS status change.
+     */
+    private void reportGeofenceStatus(int status, int flags, double latitude,
+            double longitude, double altitude, float speed, float bearing, float accuracy,
+            long timestamp) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        Location location = buildLocation(
+                flags,
+                latitude,
+                longitude,
+                altitude,
+                speed,
+                bearing,
+                accuracy,
+                timestamp);
+        int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE;
+        if(status == GPS_GEOFENCE_AVAILABLE) {
+            monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE;
+        }
+        mGeofenceHardwareImpl.reportGeofenceMonitorStatus(
+                GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE,
+                monitorStatus,
+                location,
+                FusedBatchOptions.SourceTechnologies.GNSS);
+    }
+
+    /**
+     * called from native code - Geofence Add callback
+     */
+    private void reportGeofenceAddStatus(int geofenceId, int status) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status));
+    }
+
+    /**
+     * called from native code - Geofence Remove callback
+     */
+    private void reportGeofenceRemoveStatus(int geofenceId, int status) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status));
+    }
+
+    /**
+     * called from native code - Geofence Pause callback
+     */
+    private void reportGeofencePauseStatus(int geofenceId, int status) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status));
+    }
+
+    /**
+     * called from native code - Geofence Resume callback
+     */
+    private void reportGeofenceResumeStatus(int geofenceId, int status) {
+        if (mGeofenceHardwareImpl == null) {
+            mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext);
+        }
+        mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status));
+    }
+
+    //=============================================================
+    // NI Client support
+    //=============================================================
+    private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
+        // Sends a response for an NI reqeust to HAL.
+        @Override
+        public boolean sendNiResponse(int notificationId, int userResponse)
+        {
+            // TODO Add Permission check
+
+            if (DEBUG) Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
+                    ", response: " + userResponse);
+            native_send_ni_response(notificationId, userResponse);
+            return true;
+        }
+    };
+
+    public INetInitiatedListener getNetInitiatedListener() {
+        return mNetInitiatedListener;
+    }
+
+    // Called by JNI function to report an NI request.
+    public void reportNiNotification(
+            int notificationId,
+            int niType,
+            int notifyFlags,
+            int timeout,
+            int defaultResponse,
+            String requestorId,
+            String text,
+            int requestorIdEncoding,
+            int textEncoding,
+            String extras  // Encoded extra data
+        )
+    {
+        Log.i(TAG, "reportNiNotification: entered");
+        Log.i(TAG, "notificationId: " + notificationId +
+                ", niType: " + niType +
+                ", notifyFlags: " + notifyFlags +
+                ", timeout: " + timeout +
+                ", defaultResponse: " + defaultResponse);
+
+        Log.i(TAG, "requestorId: " + requestorId +
+                ", text: " + text +
+                ", requestorIdEncoding: " + requestorIdEncoding +
+                ", textEncoding: " + textEncoding);
+
+        GpsNiNotification notification = new GpsNiNotification();
+
+        notification.notificationId = notificationId;
+        notification.niType = niType;
+        notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
+        notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
+        notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
+        notification.timeout = timeout;
+        notification.defaultResponse = defaultResponse;
+        notification.requestorId = requestorId;
+        notification.text = text;
+        notification.requestorIdEncoding = requestorIdEncoding;
+        notification.textEncoding = textEncoding;
+
+        // Process extras, assuming the format is
+        // one of more lines of "key = value"
+        Bundle bundle = new Bundle();
+
+        if (extras == null) extras = "";
+        Properties extraProp = new Properties();
+
+        try {
+            extraProp.load(new StringReader(extras));
+        }
+        catch (IOException e)
+        {
+            Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras);
+        }
+
+        for (Entry<Object, Object> ent : extraProp.entrySet())
+        {
+            bundle.putString((String) ent.getKey(), (String) ent.getValue());
+        }
+
+        notification.extras = bundle;
+
+        mNIHandler.handleNiNotification(notification);
+    }
+
+    /**
+     * Called from native code to request set id info.
+     * We should be careful about receiving null string from the TelephonyManager,
+     * because sending null String to JNI function would cause a crash.
+     */
+
+    private void requestSetID(int flags) {
+        TelephonyManager phone = (TelephonyManager)
+                mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        int    type = AGPS_SETID_TYPE_NONE;
+        String data = "";
+
+        if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) {
+            String data_temp = phone.getSubscriberId();
+            if (data_temp == null) {
+                // This means the framework does not have the SIM card ready.
+            } else {
+                // This means the framework has the SIM card.
+                data = data_temp;
+                type = AGPS_SETID_TYPE_IMSI;
+            }
+        }
+        else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) {
+            String data_temp = phone.getLine1Number();
+            if (data_temp == null) {
+                // This means the framework does not have the SIM card ready.
+            } else {
+                // This means the framework has the SIM card.
+                data = data_temp;
+                type = AGPS_SETID_TYPE_MSISDN;
+            }
+        }
+        native_agps_set_id(type, data);
+    }
+
+    /**
+     * Called from native code to request utc time info
+     */
+
+    private void requestUtcTime() {
+        sendMessage(INJECT_NTP_TIME, 0, null);
+    }
+
+    /**
+     * Called from native code to request reference location info
+     */
+
+    private void requestRefLocation(int flags) {
+        TelephonyManager phone = (TelephonyManager)
+                mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        final int phoneType = phone.getPhoneType();
+        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
+            GsmCellLocation gsm_cell = (GsmCellLocation) phone.getCellLocation();
+            if ((gsm_cell != null) && (phone.getNetworkOperator() != null)
+                    && (phone.getNetworkOperator().length() > 3)) {
+                int type;
+                int mcc = Integer.parseInt(phone.getNetworkOperator().substring(0,3));
+                int mnc = Integer.parseInt(phone.getNetworkOperator().substring(3));
+                int networkType = phone.getNetworkType();
+                if (networkType == TelephonyManager.NETWORK_TYPE_UMTS
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSDPA
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSUPA
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSPA
+                    || networkType == TelephonyManager.NETWORK_TYPE_HSPAP) {
+                    type = AGPS_REF_LOCATION_TYPE_UMTS_CELLID;
+                } else {
+                    type = AGPS_REF_LOCATION_TYPE_GSM_CELLID;
+                }
+                native_agps_set_ref_location_cellid(type, mcc, mnc,
+                        gsm_cell.getLac(), gsm_cell.getCid());
+            } else {
+                Log.e(TAG,"Error getting cell location info.");
+            }
+        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
+            Log.e(TAG, "CDMA not supported.");
+        }
+    }
+
+    private void sendMessage(int message, int arg, Object obj) {
+        // hold a wake lock until this message is delivered
+        // note that this assumes the message will not be removed from the queue before
+        // it is handled (otherwise the wake lock would be leaked).
+        mWakeLock.acquire();
+        mHandler.obtainMessage(message, arg, 1, obj).sendToTarget();
+    }
+
+    private final class ProviderHandler extends Handler {
+        public ProviderHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            int message = msg.what;
+            switch (message) {
+                case ENABLE:
+                    if (msg.arg1 == 1) {
+                        handleEnable();
+                    } else {
+                        handleDisable();
+                    }
+                    break;
+                case SET_REQUEST:
+                    GpsRequest gpsRequest = (GpsRequest) msg.obj;
+                    handleSetRequest(gpsRequest.request, gpsRequest.source);
+                    break;
+                case UPDATE_NETWORK_STATE:
+                    handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj);
+                    break;
+                case INJECT_NTP_TIME:
+                    handleInjectNtpTime();
+                    break;
+                case DOWNLOAD_XTRA_DATA:
+                    if (mSupportsXtra) {
+                        handleDownloadXtraData();
+                    }
+                    break;
+                case INJECT_NTP_TIME_FINISHED:
+                    mInjectNtpTimePending = STATE_IDLE;
+                    break;
+                case DOWNLOAD_XTRA_DATA_FINISHED:
+                    mDownloadXtraDataPending = STATE_IDLE;
+                    break;
+                case UPDATE_LOCATION:
+                    handleUpdateLocation((Location)msg.obj);
+                    break;
+            }
+            if (msg.arg2 == 1) {
+                // wakelock was taken for this message, release it
+                mWakeLock.release();
+            }
+        }
+    };
+
+    private final class NetworkLocationListener implements LocationListener {
+        @Override
+        public void onLocationChanged(Location location) {
+            // this callback happens on mHandler looper
+            if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) {
+                handleUpdateLocation(location);
+            }
+        }
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) { }
+        @Override
+        public void onProviderEnabled(String provider) { }
+        @Override
+        public void onProviderDisabled(String provider) { }
+    }
+
+    private String getSelectedApn() {
+        Uri uri = Uri.parse("content://telephony/carriers/preferapn");
+        String apn = null;
+
+        Cursor cursor = mContext.getContentResolver().query(uri, new String[] {"apn"},
+                null, null, Carriers.DEFAULT_SORT_ORDER);
+
+        if (null != cursor) {
+            try {
+                if (cursor.moveToFirst()) {
+                    apn = cursor.getString(0);
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+        return apn;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        StringBuilder s = new StringBuilder();
+        s.append("  mFixInterval=").append(mFixInterval).append("\n");
+        s.append("  mEngineCapabilities=0x").append(Integer.toHexString(mEngineCapabilities)).append(" (");
+        if (hasCapability(GPS_CAPABILITY_SCHEDULING)) s.append("SCHED ");
+        if (hasCapability(GPS_CAPABILITY_MSB)) s.append("MSB ");
+        if (hasCapability(GPS_CAPABILITY_MSA)) s.append("MSA ");
+        if (hasCapability(GPS_CAPABILITY_SINGLE_SHOT)) s.append("SINGLE_SHOT ");
+        if (hasCapability(GPS_CAPABILITY_ON_DEMAND_TIME)) s.append("ON_DEMAND_TIME ");
+        s.append(")\n");
+
+        s.append(native_get_internal_state());
+        pw.append(s);
+    }
+
+    // for GPS SV statistics
+    private static final int MAX_SVS = 32;
+    private static final int EPHEMERIS_MASK = 0;
+    private static final int ALMANAC_MASK = 1;
+    private static final int USED_FOR_FIX_MASK = 2;
+
+    // preallocated arrays, to avoid memory allocation in reportStatus()
+    private int mSvs[] = new int[MAX_SVS];
+    private float mSnrs[] = new float[MAX_SVS];
+    private float mSvElevations[] = new float[MAX_SVS];
+    private float mSvAzimuths[] = new float[MAX_SVS];
+    private int mSvMasks[] = new int[3];
+    private int mSvCount;
+    // preallocated to avoid memory allocation in reportNmea()
+    private byte[] mNmeaBuffer = new byte[120];
+
+    static { class_init_native(); }
+    private static native void class_init_native();
+    private static native boolean native_is_supported();
+
+    private native boolean native_init();
+    private native void native_cleanup();
+    private native boolean native_set_position_mode(int mode, int recurrence, int min_interval,
+            int preferred_accuracy, int preferred_time);
+    private native boolean native_start();
+    private native boolean native_stop();
+    private native void native_delete_aiding_data(int flags);
+    // returns number of SVs
+    // mask[0] is ephemeris mask and mask[1] is almanac mask
+    private native int native_read_sv_status(int[] svs, float[] snrs,
+            float[] elevations, float[] azimuths, int[] masks);
+    private native int native_read_nmea(byte[] buffer, int bufferSize);
+    private native void native_inject_location(double latitude, double longitude, float accuracy);
+
+    // XTRA Support
+    private native void native_inject_time(long time, long timeReference, int uncertainty);
+    private native boolean native_supports_xtra();
+    private native void native_inject_xtra_data(byte[] data, int length);
+
+    // DEBUG Support
+    private native String native_get_internal_state();
+
+    // AGPS Support
+    private native void native_agps_data_conn_open(String apn);
+    private native void native_agps_data_conn_closed();
+    private native void native_agps_data_conn_failed();
+    private native void native_agps_ni_message(byte [] msg, int length);
+    private native void native_set_agps_server(int type, String hostname, int port);
+
+    // Network-initiated (NI) Support
+    private native void native_send_ni_response(int notificationId, int userResponse);
+
+    // AGPS ril suport
+    private native void native_agps_set_ref_location_cellid(int type, int mcc, int mnc,
+            int lac, int cid);
+    private native void native_agps_set_id(int type, String setid);
+
+    private native void native_update_network_state(boolean connected, int type,
+            boolean roaming, boolean available, String extraInfo, String defaultAPN);
+
+    // Hardware Geofence support.
+    private static native boolean native_is_geofence_supported();
+    private static native boolean native_add_geofence(int geofenceId, double latitude,
+            double longitude, double radius, int lastTransition,int monitorTransitions,
+            int notificationResponsivenes, int unknownTimer);
+    private static native boolean native_remove_geofence(int geofenceId);
+    private static native boolean native_resume_geofence(int geofenceId, int transitions);
+    private static native boolean native_pause_geofence(int geofenceId);
+}
diff --git a/services/core/java/com/android/server/location/GpsXtraDownloader.java b/services/core/java/com/android/server/location/GpsXtraDownloader.java
new file mode 100644
index 0000000..e420073
--- /dev/null
+++ b/services/core/java/com/android/server/location/GpsXtraDownloader.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
+import android.util.Log;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.params.ConnRouteParams;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Random;
+
+/**
+ * A class for downloading GPS XTRA data.
+ *
+ * {@hide}
+ */
+public class GpsXtraDownloader {
+
+    private static final String TAG = "GpsXtraDownloader";
+    static final boolean DEBUG = false;
+    
+    private Context mContext;
+    private String[] mXtraServers;
+    // to load balance our server requests
+    private int mNextServerIndex;
+
+    GpsXtraDownloader(Context context, Properties properties) {
+        mContext = context;
+
+        // read XTRA servers from the Properties object
+        int count = 0;
+        String server1 = properties.getProperty("XTRA_SERVER_1");
+        String server2 = properties.getProperty("XTRA_SERVER_2");
+        String server3 = properties.getProperty("XTRA_SERVER_3");
+        if (server1 != null) count++;
+        if (server2 != null) count++;
+        if (server3 != null) count++;
+        
+        if (count == 0) {
+            Log.e(TAG, "No XTRA servers were specified in the GPS configuration");
+            return;
+        } else {
+            mXtraServers = new String[count];
+            count = 0;
+            if (server1 != null) mXtraServers[count++] = server1;
+            if (server2 != null) mXtraServers[count++] = server2;
+            if (server3 != null) mXtraServers[count++] = server3;
+
+            // randomize first server
+            Random random = new Random();
+            mNextServerIndex = random.nextInt(count);
+        }       
+    }
+
+    byte[] downloadXtraData() {
+        String proxyHost = Proxy.getHost(mContext);
+        int proxyPort = Proxy.getPort(mContext);
+        boolean useProxy = (proxyHost != null && proxyPort != -1);
+        byte[] result = null;
+        int startIndex = mNextServerIndex;
+
+        if (mXtraServers == null) {
+            return null;
+        }
+
+        // load balance our requests among the available servers
+        while (result == null) {
+            result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort);
+            
+            // increment mNextServerIndex and wrap around if necessary
+            mNextServerIndex++;
+            if (mNextServerIndex == mXtraServers.length) {
+                mNextServerIndex = 0;
+            }
+            // break if we have tried all the servers
+            if (mNextServerIndex == startIndex) break;
+        }
+    
+        return result;
+    }
+
+    protected static byte[] doDownload(String url, boolean isProxySet, 
+            String proxyHost, int proxyPort) {
+        if (DEBUG) Log.d(TAG, "Downloading XTRA data from " + url);
+
+        AndroidHttpClient client = null;
+        try {
+            client = AndroidHttpClient.newInstance("Android");
+            HttpUriRequest req = new HttpGet(url);
+
+            if (isProxySet) {
+                HttpHost proxy = new HttpHost(proxyHost, proxyPort);
+                ConnRouteParams.setDefaultProxy(req.getParams(), proxy);
+            }
+
+            req.addHeader(
+                    "Accept",
+                    "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
+
+            req.addHeader(
+                    "x-wap-profile",
+                    "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#");
+
+            HttpResponse response = client.execute(req);
+            StatusLine status = response.getStatusLine();
+            if (status.getStatusCode() != 200) { // HTTP 200 is success.
+                if (DEBUG) Log.d(TAG, "HTTP error: " + status.getReasonPhrase());
+                return null;
+            }
+
+            HttpEntity entity = response.getEntity();
+            byte[] body = null;
+            if (entity != null) {
+                try {
+                    if (entity.getContentLength() > 0) {
+                        body = new byte[(int) entity.getContentLength()];
+                        DataInputStream dis = new DataInputStream(entity.getContent());
+                        try {
+                            dis.readFully(body);
+                        } finally {
+                            try {
+                                dis.close();
+                            } catch (IOException e) {
+                                Log.e(TAG, "Unexpected IOException.", e);
+                            }
+                        }
+                    }
+                } finally {
+                    if (entity != null) {
+                        entity.consumeContent();
+                    }
+                }
+            }
+            return body;
+        } catch (Exception e) {
+            if (DEBUG) Log.d(TAG, "error " + e);
+        } finally {
+            if (client != null) {
+                client.close();
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/services/core/java/com/android/server/location/LocationBasedCountryDetector.java b/services/core/java/com/android/server/location/LocationBasedCountryDetector.java
new file mode 100644
index 0000000..03db621
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationBasedCountryDetector.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import android.content.Context;
+import android.location.Address;
+import android.location.Country;
+import android.location.Geocoder;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This class detects which country the user currently is in through the enabled
+ * location providers and the GeoCoder
+ * <p>
+ * Use {@link #detectCountry} to start querying. If the location can not be
+ * resolved within the given time, the last known location will be used to get
+ * the user country through the GeoCoder. The IllegalStateException will be
+ * thrown if there is a ongoing query.
+ * <p>
+ * The current query can be stopped by {@link #stop()}
+ *
+ * @hide
+ */
+public class LocationBasedCountryDetector extends CountryDetectorBase {
+    private final static String TAG = "LocationBasedCountryDetector";
+    private final static long QUERY_LOCATION_TIMEOUT = 1000 * 60 * 5; // 5 mins
+
+    /**
+     * Used for canceling location query
+     */
+    protected Timer mTimer;
+
+    /**
+     * The thread to query the country from the GeoCoder.
+     */
+    protected Thread mQueryThread;
+    protected List<LocationListener> mLocationListeners;
+
+    private LocationManager mLocationManager;
+    private List<String> mEnabledProviders;
+
+    public LocationBasedCountryDetector(Context ctx) {
+        super(ctx);
+        mLocationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
+    }
+
+    /**
+     * @return the ISO 3166-1 two letters country code from the location
+     */
+    protected String getCountryFromLocation(Location location) {
+        String country = null;
+        Geocoder geoCoder = new Geocoder(mContext);
+        try {
+            List<Address> addresses = geoCoder.getFromLocation(
+                    location.getLatitude(), location.getLongitude(), 1);
+            if (addresses != null && addresses.size() > 0) {
+                country = addresses.get(0).getCountryCode();
+            }
+        } catch (IOException e) {
+            Slog.w(TAG, "Exception occurs when getting country from location");
+        }
+        return country;
+    }
+
+    protected boolean isAcceptableProvider(String provider) {
+        // We don't want to actively initiate a location fix here (with gps or network providers).
+        return LocationManager.PASSIVE_PROVIDER.equals(provider);
+    }
+
+    /**
+     * Register a listener with a provider name
+     */
+    protected void registerListener(String provider, LocationListener listener) {
+        mLocationManager.requestLocationUpdates(provider, 0, 0, listener);
+    }
+
+    /**
+     * Unregister an already registered listener
+     */
+    protected void unregisterListener(LocationListener listener) {
+        mLocationManager.removeUpdates(listener);
+    }
+
+    /**
+     * @return the last known location from all providers
+     */
+    protected Location getLastKnownLocation() {
+        List<String> providers = mLocationManager.getAllProviders();
+        Location bestLocation = null;
+        for (String provider : providers) {
+            Location lastKnownLocation = mLocationManager.getLastKnownLocation(provider);
+            if (lastKnownLocation != null) {
+                if (bestLocation == null ||
+                        bestLocation.getElapsedRealtimeNanos() <
+                        lastKnownLocation.getElapsedRealtimeNanos()) {
+                    bestLocation = lastKnownLocation;
+                }
+            }
+        }
+        return bestLocation;
+    }
+
+    /**
+     * @return the timeout for querying the location.
+     */
+    protected long getQueryLocationTimeout() {
+        return QUERY_LOCATION_TIMEOUT;
+    }
+
+    protected List<String> getEnabledProviders() {
+        if (mEnabledProviders == null) {
+            mEnabledProviders = mLocationManager.getProviders(true);
+        }
+        return mEnabledProviders;
+    }
+
+    /**
+     * Start detecting the country.
+     * <p>
+     * Queries the location from all location providers, then starts a thread to query the
+     * country from GeoCoder.
+     */
+    @Override
+    public synchronized Country detectCountry() {
+        if (mLocationListeners  != null) {
+            throw new IllegalStateException();
+        }
+        // Request the location from all enabled providers.
+        List<String> enabledProviders = getEnabledProviders();
+        int totalProviders = enabledProviders.size();
+        if (totalProviders > 0) {
+            mLocationListeners = new ArrayList<LocationListener>(totalProviders);
+            for (int i = 0; i < totalProviders; i++) {
+                String provider = enabledProviders.get(i);
+                if (isAcceptableProvider(provider)) {
+                    LocationListener listener = new LocationListener () {
+                        @Override
+                        public void onLocationChanged(Location location) {
+                            if (location != null) {
+                                LocationBasedCountryDetector.this.stop();
+                                queryCountryCode(location);
+                            }
+                        }
+                        @Override
+                        public void onProviderDisabled(String provider) {
+                        }
+                        @Override
+                        public void onProviderEnabled(String provider) {
+                        }
+                        @Override
+                        public void onStatusChanged(String provider, int status, Bundle extras) {
+                        }
+                    };
+                    mLocationListeners.add(listener);
+                    registerListener(provider, listener);
+                }
+            }
+
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    mTimer = null;
+                    LocationBasedCountryDetector.this.stop();
+                    // Looks like no provider could provide the location, let's try the last
+                    // known location.
+                    queryCountryCode(getLastKnownLocation());
+                }
+            }, getQueryLocationTimeout());
+        } else {
+            // There is no provider enabled.
+            queryCountryCode(getLastKnownLocation());
+        }
+        return mDetectedCountry;
+    }
+
+    /**
+     * Stop the current query without notifying the listener.
+     */
+    @Override
+    public synchronized void stop() {
+        if (mLocationListeners != null) {
+            for (LocationListener listener : mLocationListeners) {
+                unregisterListener(listener);
+            }
+            mLocationListeners = null;
+        }
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer = null;
+        }
+    }
+
+    /**
+     * Start a new thread to query the country from Geocoder.
+     */
+    private synchronized void queryCountryCode(final Location location) {
+        if (location == null) {
+            notifyListener(null);
+            return;
+        }
+        if (mQueryThread != null) return;
+        mQueryThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                String countryIso = null;
+                if (location != null) {
+                    countryIso = getCountryFromLocation(location);
+                }
+                if (countryIso != null) {
+                    mDetectedCountry = new Country(countryIso, Country.COUNTRY_SOURCE_LOCATION);
+                } else {
+                    mDetectedCountry = null;
+                }
+                notifyListener(mDetectedCountry);
+                mQueryThread = null;
+            }
+        });
+        mQueryThread.start();
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationBlacklist.java b/services/core/java/com/android/server/location/LocationBlacklist.java
new file mode 100644
index 0000000..6f22689
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationBlacklist.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.server.location;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.LocationManagerService;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Allows applications to be blacklisted from location updates at run-time.
+ *
+ * This is a silent blacklist. Applications can still call Location Manager
+ * API's, but they just won't receive any locations.
+ */
+public final class LocationBlacklist extends ContentObserver {
+    private static final String TAG = "LocationBlacklist";
+    private static final boolean D = LocationManagerService.D;
+    private static final String BLACKLIST_CONFIG_NAME = "locationPackagePrefixBlacklist";
+    private static final String WHITELIST_CONFIG_NAME = "locationPackagePrefixWhitelist";
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    // all fields below synchronized on mLock
+    private String[] mWhitelist = new String[0];
+    private String[] mBlacklist = new String[0];
+
+    private int mCurrentUserId = UserHandle.USER_OWNER;
+    
+    public LocationBlacklist(Context context, Handler handler) {
+        super(handler);
+        mContext = context;
+    }
+
+    public void init() {
+        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                BLACKLIST_CONFIG_NAME), false, this, UserHandle.USER_ALL);
+//        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+//                WHITELIST_CONFIG_NAME), false, this, UserHandle.USER_ALL);
+        reloadBlacklist();
+    }
+
+    private void reloadBlacklistLocked() {
+        mWhitelist = getStringArrayLocked(WHITELIST_CONFIG_NAME);
+        if (D) Slog.d(TAG, "whitelist: " + Arrays.toString(mWhitelist));
+        mBlacklist = getStringArrayLocked(BLACKLIST_CONFIG_NAME);
+        if (D) Slog.d(TAG, "blacklist: " + Arrays.toString(mBlacklist));
+    }
+
+    private void reloadBlacklist() {
+        synchronized (mLock) {
+            reloadBlacklistLocked();
+        }
+    }
+
+    /**
+     * Return true if in blacklist
+     * (package name matches blacklist, and does not match whitelist)
+     */
+    public boolean isBlacklisted(String packageName) {
+        synchronized (mLock) {
+            for (String black : mBlacklist) {
+                if (packageName.startsWith(black)) {
+                    if (inWhitelist(packageName)) {
+                        continue;
+                    } else {
+                        if (D) Log.d(TAG, "dropping location (blacklisted): "
+                                + packageName + " matches " + black);
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return true if any of packages are in whitelist
+     */
+    private boolean inWhitelist(String pkg) {
+        synchronized (mLock) {
+            for (String white : mWhitelist) {
+                if (pkg.startsWith(white)) return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void onChange(boolean selfChange) {
+        reloadBlacklist();
+    }
+
+    public void switchUser(int userId) {
+        synchronized(mLock) {
+            mCurrentUserId = userId;
+            reloadBlacklistLocked();
+        }
+    }
+
+    private String[] getStringArrayLocked(String key) {
+        String flatString;
+        synchronized(mLock) {
+            flatString = Settings.Secure.getStringForUser(mContext.getContentResolver(), key,
+                    mCurrentUserId);
+        }
+        if (flatString == null) {
+            return new String[0];
+        }
+        String[] splitStrings = flatString.split(",");
+        ArrayList<String> result = new ArrayList<String>();
+        for (String pkg : splitStrings) {
+            pkg = pkg.trim();
+            if (pkg.isEmpty()) {
+                continue;
+            }
+            result.add(pkg);
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("mWhitelist=" + Arrays.toString(mWhitelist) + " mBlacklist=" +
+                Arrays.toString(mBlacklist));
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationFudger.java b/services/core/java/com/android/server/location/LocationFudger.java
new file mode 100644
index 0000000..2a68743
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationFudger.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.security.SecureRandom;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+
+
+/**
+ * Contains the logic to obfuscate (fudge) locations for coarse applications.
+ *
+ * <p>The goal is just to prevent applications with only
+ * the coarse location permission from receiving a fine location.
+ */
+public class LocationFudger {
+    private static final boolean D = false;
+    private static final String TAG = "LocationFudge";
+
+    /**
+     * Default coarse accuracy in meters.
+     */
+    private static final float DEFAULT_ACCURACY_IN_METERS = 2000.0f;
+
+    /**
+     * Minimum coarse accuracy in meters.
+     */
+    private static final float MINIMUM_ACCURACY_IN_METERS = 200.0f;
+
+    /**
+     * Secure settings key for coarse accuracy.
+     */
+    private static final String COARSE_ACCURACY_CONFIG_NAME = "locationCoarseAccuracy";
+
+    /**
+     * This is the fastest interval that applications can receive coarse
+     * locations.
+     */
+    public static final long FASTEST_INTERVAL_MS = 10 * 60 * 1000;  // 10 minutes
+
+    /**
+     * The duration until we change the random offset.
+     */
+    private static final long CHANGE_INTERVAL_MS = 60 * 60 * 1000;  // 1 hour
+
+    /**
+     * The percentage that we change the random offset at every interval.
+     *
+     * <p>0.0 indicates the random offset doesn't change. 1.0
+     * indicates the random offset is completely replaced every interval.
+     */
+    private static final double CHANGE_PER_INTERVAL = 0.03;  // 3% change
+
+    // Pre-calculated weights used to move the random offset.
+    //
+    // The goal is to iterate on the previous offset, but keep
+    // the resulting standard deviation the same. The variance of
+    // two gaussian distributions summed together is equal to the
+    // sum of the variance of each distribution. So some quick
+    // algebra results in the following sqrt calculation to
+    // weigh in a new offset while keeping the final standard
+    // deviation unchanged.
+    private static final double NEW_WEIGHT = CHANGE_PER_INTERVAL;
+    private static final double PREVIOUS_WEIGHT = Math.sqrt(1 - NEW_WEIGHT * NEW_WEIGHT);
+
+    /**
+     * This number actually varies because the earth is not round, but
+     * 111,000 meters is considered generally acceptable.
+     */
+    private static final int APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR = 111000;
+
+    /**
+     * Maximum latitude.
+     *
+     * <p>We pick a value 1 meter away from 90.0 degrees in order
+     * to keep cosine(MAX_LATITUDE) to a non-zero value, so that we avoid
+     * divide by zero fails.
+     */
+    private static final double MAX_LATITUDE = 90.0 -
+            (1.0 / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR);
+
+    private final Object mLock = new Object();
+    private final SecureRandom mRandom = new SecureRandom();
+
+    /**
+     * Used to monitor coarse accuracy secure setting for changes.
+     */
+    private final ContentObserver mSettingsObserver;
+
+    /**
+     * Used to resolve coarse accuracy setting.
+     */
+    private final Context mContext;
+
+    // all fields below protected by mLock
+    private double mOffsetLatitudeMeters;
+    private double mOffsetLongitudeMeters;
+    private long mNextInterval;
+
+    /**
+     * Best location accuracy allowed for coarse applications.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private float mAccuracyInMeters;
+
+    /**
+     * The distance between grids for snap-to-grid. See {@link #createCoarse}.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private double mGridSizeInMeters;
+
+    /**
+     * Standard deviation of the (normally distributed) random offset applied
+     * to coarse locations. It does not need to be as large as
+     * {@link #COARSE_ACCURACY_METERS} because snap-to-grid is the primary obfuscation
+     * method. See further details in the implementation.
+     * This value should only be set by {@link #setAccuracyInMetersLocked(float)}.
+     */
+    private double mStandardDeviationInMeters;
+
+    public LocationFudger(Context context, Handler handler) {
+        mContext = context;
+        mSettingsObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                setAccuracyInMeters(loadCoarseAccuracy());
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                COARSE_ACCURACY_CONFIG_NAME), false, mSettingsObserver);
+
+        float accuracy = loadCoarseAccuracy();
+        synchronized (mLock) {
+            setAccuracyInMetersLocked(accuracy);
+            mOffsetLatitudeMeters = nextOffsetLocked();
+            mOffsetLongitudeMeters = nextOffsetLocked();
+            mNextInterval = SystemClock.elapsedRealtime() + CHANGE_INTERVAL_MS;
+        }
+    }
+
+    /**
+     * Get the cached coarse location, or generate a new one and cache it.
+     */
+    public Location getOrCreate(Location location) {
+        synchronized (mLock) {
+            Location coarse = location.getExtraLocation(Location.EXTRA_COARSE_LOCATION);
+            if (coarse == null) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            if (coarse.getAccuracy() < mAccuracyInMeters) {
+                return addCoarseLocationExtraLocked(location);
+            }
+            return coarse;
+        }
+    }
+
+    private Location addCoarseLocationExtraLocked(Location location) {
+        Location coarse = createCoarseLocked(location);
+        location.setExtraLocation(Location.EXTRA_COARSE_LOCATION, coarse);
+        return coarse;
+    }
+
+    /**
+     * Create a coarse location.
+     *
+     * <p>Two techniques are used: random offsets and snap-to-grid.
+     *
+     * <p>First we add a random offset. This mitigates against detecting
+     * grid transitions. Without a random offset it is possible to detect
+     * a users position very accurately when they cross a grid boundary.
+     * The random offset changes very slowly over time, to mitigate against
+     * taking many location samples and averaging them out.
+     *
+     * <p>Second we snap-to-grid (quantize). This has the nice property of
+     * producing stable results, and mitigating against taking many samples
+     * to average out a random offset.
+     */
+    private Location createCoarseLocked(Location fine) {
+        Location coarse = new Location(fine);
+
+        // clean all the optional information off the location, because
+        // this can leak detailed location information
+        coarse.removeBearing();
+        coarse.removeSpeed();
+        coarse.removeAltitude();
+        coarse.setExtras(null);
+
+        double lat = coarse.getLatitude();
+        double lon = coarse.getLongitude();
+
+        // wrap
+        lat = wrapLatitude(lat);
+        lon = wrapLongitude(lon);
+
+        // Step 1) apply a random offset
+        //
+        // The goal of the random offset is to prevent the application
+        // from determining that the device is on a grid boundary
+        // when it crosses from one grid to the next.
+        //
+        // We apply the offset even if the location already claims to be
+        // inaccurate, because it may be more accurate than claimed.
+        updateRandomOffsetLocked();
+        // perform lon first whilst lat is still within bounds
+        lon += metersToDegreesLongitude(mOffsetLongitudeMeters, lat);
+        lat += metersToDegreesLatitude(mOffsetLatitudeMeters);
+        if (D) Log.d(TAG, String.format("applied offset of %.0f, %.0f (meters)",
+                mOffsetLongitudeMeters, mOffsetLatitudeMeters));
+
+        // wrap
+        lat = wrapLatitude(lat);
+        lon = wrapLongitude(lon);
+
+        // Step 2) Snap-to-grid (quantize)
+        //
+        // This is the primary means of obfuscation. It gives nice consistent
+        // results and is very effective at hiding the true location
+        // (as long as you are not sitting on a grid boundary, which
+        // step 1 mitigates).
+        //
+        // Note we quantize the latitude first, since the longitude
+        // quantization depends on the latitude value and so leaks information
+        // about the latitude
+        double latGranularity = metersToDegreesLatitude(mGridSizeInMeters);
+        lat = Math.round(lat / latGranularity) * latGranularity;
+        double lonGranularity = metersToDegreesLongitude(mGridSizeInMeters, lat);
+        lon = Math.round(lon / lonGranularity) * lonGranularity;
+
+        // wrap again
+        lat = wrapLatitude(lat);
+        lon = wrapLongitude(lon);
+
+        // apply
+        coarse.setLatitude(lat);
+        coarse.setLongitude(lon);
+        coarse.setAccuracy(Math.max(mAccuracyInMeters, coarse.getAccuracy()));
+
+        if (D) Log.d(TAG, "fudged " + fine + " to " + coarse);
+        return coarse;
+    }
+
+    /**
+     * Update the random offset over time.
+     *
+     * <p>If the random offset was new for every location
+     * fix then an application can more easily average location results
+     * over time,
+     * especially when the location is near a grid boundary. On the
+     * other hand if the random offset is constant then if an application
+     * found a way to reverse engineer the offset they would be able
+     * to detect location at grid boundaries very accurately. So
+     * we choose a random offset and then very slowly move it, to
+     * make both approaches very hard.
+     *
+     * <p>The random offset does not need to be large, because snap-to-grid
+     * is the primary obfuscation mechanism. It just needs to be large
+     * enough to stop information leakage as we cross grid boundaries.
+     */
+    private void updateRandomOffsetLocked() {
+        long now = SystemClock.elapsedRealtime();
+        if (now < mNextInterval) {
+            return;
+        }
+
+        if (D) Log.d(TAG, String.format("old offset: %.0f, %.0f (meters)",
+                mOffsetLongitudeMeters, mOffsetLatitudeMeters));
+
+        // ok, need to update the random offset
+        mNextInterval = now + CHANGE_INTERVAL_MS;
+
+        mOffsetLatitudeMeters *= PREVIOUS_WEIGHT;
+        mOffsetLatitudeMeters += NEW_WEIGHT * nextOffsetLocked();
+        mOffsetLongitudeMeters *= PREVIOUS_WEIGHT;
+        mOffsetLongitudeMeters += NEW_WEIGHT * nextOffsetLocked();
+
+        if (D) Log.d(TAG, String.format("new offset: %.0f, %.0f (meters)",
+                mOffsetLongitudeMeters, mOffsetLatitudeMeters));
+    }
+
+    private double nextOffsetLocked() {
+        return mRandom.nextGaussian() * mStandardDeviationInMeters;
+    }
+
+    private static double wrapLatitude(double lat) {
+         if (lat > MAX_LATITUDE) {
+             lat = MAX_LATITUDE;
+         }
+         if (lat < -MAX_LATITUDE) {
+             lat = -MAX_LATITUDE;
+         }
+         return lat;
+    }
+
+    private static double wrapLongitude(double lon) {
+        lon %= 360.0;  // wraps into range (-360.0, +360.0)
+        if (lon >= 180.0) {
+            lon -= 360.0;
+        }
+        if (lon < -180.0) {
+            lon += 360.0;
+        }
+        return lon;
+    }
+
+    private static double metersToDegreesLatitude(double distance) {
+        return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR;
+    }
+
+    /**
+     * Requires latitude since longitudinal distances change with distance from equator.
+     */
+    private static double metersToDegreesLongitude(double distance, double lat) {
+        return distance / APPROXIMATE_METERS_PER_DEGREE_AT_EQUATOR / Math.cos(Math.toRadians(lat));
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(String.format("offset: %.0f, %.0f (meters)", mOffsetLongitudeMeters,
+                mOffsetLatitudeMeters));
+    }
+
+    /**
+     * This is the main control: call this to set the best location accuracy
+     * allowed for coarse applications and all derived values.
+     */
+    private void setAccuracyInMetersLocked(float accuracyInMeters) {
+        mAccuracyInMeters = Math.max(accuracyInMeters, MINIMUM_ACCURACY_IN_METERS);
+        if (D) {
+            Log.d(TAG, "setAccuracyInMetersLocked: new accuracy = " + mAccuracyInMeters);
+        }
+        mGridSizeInMeters = mAccuracyInMeters;
+        mStandardDeviationInMeters = mGridSizeInMeters / 4.0;
+    }
+
+    /**
+     * Same as setAccuracyInMetersLocked without the pre-lock requirement.
+     */
+    private void setAccuracyInMeters(float accuracyInMeters) {
+        synchronized (mLock) {
+            setAccuracyInMetersLocked(accuracyInMeters);
+        }
+    }
+
+    /**
+     * Loads the coarse accuracy value from secure settings.
+     */
+    private float loadCoarseAccuracy() {
+        String newSetting = Settings.Secure.getString(mContext.getContentResolver(),
+                COARSE_ACCURACY_CONFIG_NAME);
+        if (D) {
+            Log.d(TAG, "loadCoarseAccuracy: newSetting = \"" + newSetting + "\"");
+        }
+        if (newSetting == null) {
+            return DEFAULT_ACCURACY_IN_METERS;
+        }
+        try {
+            return Float.parseFloat(newSetting);
+        } catch (NumberFormatException e) {
+            return DEFAULT_ACCURACY_IN_METERS;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/location/LocationProviderInterface.java b/services/core/java/com/android/server/location/LocationProviderInterface.java
new file mode 100644
index 0000000..6f09232
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationProviderInterface.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+
+
+import android.os.Bundle;
+import android.os.WorkSource;
+
+/**
+ * Location Manager's interface for location providers.
+ * @hide
+ */
+public interface LocationProviderInterface {
+    public String getName();
+
+    public void enable();
+    public void disable();
+    public boolean isEnabled();
+    public void setRequest(ProviderRequest request, WorkSource source);
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args);
+
+    // --- deprecated (but still supported) ---
+    public ProviderProperties getProperties();
+    public int getStatus(Bundle extras);
+    public long getStatusUpdateTime();
+    public boolean sendExtraCommand(String command, Bundle extras);
+}
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
new file mode 100644
index 0000000..14db862
--- /dev/null
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+import android.content.Context;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ILocationProvider;
+import com.android.internal.location.ProviderRequest;
+import com.android.server.LocationManagerService;
+import com.android.server.ServiceWatcher;
+
+/**
+ * Proxy for ILocationProvider implementations.
+ */
+public class LocationProviderProxy implements LocationProviderInterface {
+    private static final String TAG = "LocationProviderProxy";
+    private static final boolean D = LocationManagerService.D;
+
+    private final Context mContext;
+    private final String mName;
+    private final ServiceWatcher mServiceWatcher;
+
+    private Object mLock = new Object();
+
+    // cached values set by the location manager, synchronized on mLock
+    private ProviderProperties mProperties;
+    private boolean mEnabled = false;
+    private ProviderRequest mRequest = null;
+    private WorkSource mWorksource = new WorkSource();
+
+    public static LocationProviderProxy createAndBind(
+            Context context, String name, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
+        LocationProviderProxy proxy = new LocationProviderProxy(context, name, action,
+                overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId,
+                handler);
+        if (proxy.bind()) {
+            return proxy;
+        } else {
+            return null;
+        }
+    }
+
+    private LocationProviderProxy(Context context, String name, String action,
+            int overlaySwitchResId, int defaultServicePackageNameResId,
+            int initialPackageNamesResId, Handler handler) {
+        mContext = context;
+        mName = name;
+        mServiceWatcher = new ServiceWatcher(mContext, TAG + "-" + name, action, overlaySwitchResId,
+                defaultServicePackageNameResId, initialPackageNamesResId,
+                mNewServiceWork, handler);
+    }
+
+    private boolean bind () {
+        return mServiceWatcher.start();
+    }
+
+    private ILocationProvider getService() {
+        return ILocationProvider.Stub.asInterface(mServiceWatcher.getBinder());
+    }
+
+    public String getConnectedPackageName() {
+        return mServiceWatcher.getBestPackageName();
+    }
+
+    /**
+     * Work to apply current state to a newly connected provider.
+     * Remember we can switch the service that implements a providers
+     * at run-time, so need to apply current state.
+     */
+    private Runnable mNewServiceWork = new Runnable() {
+        @Override
+        public void run() {
+            if (D) Log.d(TAG, "applying state to connected service");
+
+            boolean enabled;
+            ProviderProperties properties = null;
+            ProviderRequest request;
+            WorkSource source;
+            ILocationProvider service;
+            synchronized (mLock) {
+                enabled = mEnabled;
+                request = mRequest;
+                source = mWorksource;
+                service = getService();
+            }
+
+            if (service == null) return;
+
+            try {
+                // load properties from provider
+                properties = service.getProperties();
+                if (properties == null) {
+                    Log.e(TAG, mServiceWatcher.getBestPackageName() +
+                            " has invalid locatino provider properties");
+                }
+
+                // apply current state to new service
+                if (enabled) {
+                    service.enable();
+                    if (request != null) {
+                        service.setRequest(request, source);
+                    }
+                }
+            } catch (RemoteException e) {
+                Log.w(TAG, e);
+            } catch (Exception e) {
+                // never let remote service crash system server
+                Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+            }
+
+            synchronized (mLock) {
+                mProperties = properties;
+            }
+        }
+    };
+
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public ProviderProperties getProperties() {
+        synchronized (mLock) {
+            return mProperties;
+        }
+    }
+
+    @Override
+    public void enable() {
+        synchronized (mLock) {
+            mEnabled = true;
+        }
+        ILocationProvider service = getService();
+        if (service == null) return;
+
+        try {
+            service.enable();
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+    }
+
+    @Override
+    public void disable() {
+        synchronized (mLock) {
+            mEnabled = false;
+        }
+        ILocationProvider service = getService();
+        if (service == null) return;
+
+        try {
+            service.disable();
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+    }
+
+    @Override
+    public boolean isEnabled() {
+        synchronized (mLock) {
+            return mEnabled;
+        }
+    }
+
+    @Override
+    public void setRequest(ProviderRequest request, WorkSource source) {
+        synchronized (mLock) {
+            mRequest = request;
+            mWorksource = source;
+        }
+        ILocationProvider service = getService();
+        if (service == null) return;
+
+        try {
+            service.setRequest(request, source);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.append("REMOTE SERVICE");
+        pw.append(" name=").append(mName);
+        pw.append(" pkg=").append(mServiceWatcher.getBestPackageName());
+        pw.append(" version=").append("" + mServiceWatcher.getBestVersion());
+        pw.append('\n');
+
+        ILocationProvider service = getService();
+        if (service == null) {
+            pw.println("service down (null)");
+            return;
+        }
+        pw.flush();
+
+        try {
+            service.asBinder().dump(fd, args);
+        } catch (RemoteException e) {
+            pw.println("service down (RemoteException)");
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            pw.println("service down (Exception)");
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+    }
+
+    @Override
+    public int getStatus(Bundle extras) {
+        ILocationProvider service = getService();
+        if (service == null) return LocationProvider.TEMPORARILY_UNAVAILABLE;
+
+        try {
+            return service.getStatus(extras);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+        return LocationProvider.TEMPORARILY_UNAVAILABLE;
+    }
+
+    @Override
+    public long getStatusUpdateTime() {
+        ILocationProvider service = getService();
+        if (service == null) return 0;
+
+        try {
+            return service.getStatusUpdateTime();
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+        return 0;
+    }
+
+    @Override
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        ILocationProvider service = getService();
+        if (service == null) return false;
+
+        try {
+            return service.sendExtraCommand(command, extras);
+        } catch (RemoteException e) {
+            Log.w(TAG, e);
+        } catch (Exception e) {
+            // never let remote service crash system server
+            Log.e(TAG, "Exception from " + mServiceWatcher.getBestPackageName(), e);
+        }
+        return false;
+    }
+ }
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
new file mode 100644
index 0000000..36c43ff
--- /dev/null
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.location.Criteria;
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+
+/**
+ * A mock location provider used by LocationManagerService to implement test providers.
+ *
+ * {@hide}
+ */
+public class MockProvider implements LocationProviderInterface {
+    private final String mName;
+    private final ProviderProperties mProperties;
+    private final ILocationManager mLocationManager;
+
+    private final Location mLocation;
+    private final Bundle mExtras = new Bundle();
+
+    private int mStatus;
+    private long mStatusUpdateTime;
+    private boolean mHasLocation;
+    private boolean mHasStatus;
+    private boolean mEnabled;
+
+    private static final String TAG = "MockProvider";
+
+    public MockProvider(String name, ILocationManager locationManager,
+            ProviderProperties properties) {
+        if (properties == null) throw new NullPointerException("properties is null");
+
+        mName = name;
+        mLocationManager = locationManager;
+        mProperties = properties;
+        mLocation = new Location(name);
+    }
+
+    @Override
+    public String getName() {
+        return mName;
+    }
+
+    @Override
+    public ProviderProperties getProperties() {
+        return mProperties;
+    }
+
+    @Override
+    public void disable() {
+        mEnabled = false;
+    }
+
+    @Override
+    public void enable() {
+        mEnabled = true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    @Override
+    public int getStatus(Bundle extras) {
+        if (mHasStatus) {
+            extras.clear();
+            extras.putAll(mExtras);
+            return mStatus;
+        } else {
+            return LocationProvider.AVAILABLE;
+        }
+    }
+
+    @Override
+    public long getStatusUpdateTime() {
+        return mStatusUpdateTime;
+    }
+
+    public void setLocation(Location l) {
+        mLocation.set(l);
+        mHasLocation = true;
+        if (mEnabled) {
+            try {
+                mLocationManager.reportLocation(mLocation, false);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException calling reportLocation");
+            }
+        }
+    }
+
+    public void clearLocation() {
+        mHasLocation = false;
+    }
+
+    public void setStatus(int status, Bundle extras, long updateTime) {
+        mStatus = status;
+        mStatusUpdateTime = updateTime;
+        mExtras.clear();
+        if (extras != null) {
+            mExtras.putAll(extras);
+        }
+        mHasStatus = true;
+    }
+
+    public void clearStatus() {
+        mHasStatus = false;
+        mStatusUpdateTime = 0;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        dump(pw, "");
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + mName);
+        pw.println(prefix + "mHasLocation=" + mHasLocation);
+        pw.println(prefix + "mLocation:");
+        mLocation.dump(new PrintWriterPrinter(pw), prefix + "  ");
+        pw.println(prefix + "mHasStatus=" + mHasStatus);
+        pw.println(prefix + "mStatus=" + mStatus);
+        pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime);
+        pw.println(prefix + "mExtras=" + mExtras);
+    }
+
+    @Override
+    public void setRequest(ProviderRequest request, WorkSource source) { }
+
+    @Override
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
new file mode 100644
index 0000000..71bae07
--- /dev/null
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010 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.location;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import com.android.internal.location.ProviderProperties;
+import com.android.internal.location.ProviderRequest;
+
+import android.location.Criteria;
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+
+
+/**
+ * A passive location provider reports locations received from other providers
+ * for clients that want to listen passively without actually triggering
+ * location updates.
+ *
+ * {@hide}
+ */
+public class PassiveProvider implements LocationProviderInterface {
+    private static final String TAG = "PassiveProvider";
+
+    private static final ProviderProperties PROPERTIES = new ProviderProperties(
+            false, false, false, false, false, false, false,
+            Criteria.POWER_LOW, Criteria.ACCURACY_COARSE);
+
+    private final ILocationManager mLocationManager;
+    private boolean mReportLocation;
+
+    public PassiveProvider(ILocationManager locationManager) {
+        mLocationManager = locationManager;
+    }
+
+    @Override
+    public String getName() {
+        return LocationManager.PASSIVE_PROVIDER;
+    }
+
+    @Override
+    public ProviderProperties getProperties() {
+        return PROPERTIES;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    @Override
+    public void enable() {
+    }
+
+    @Override
+    public void disable() {
+    }
+
+    @Override
+    public int getStatus(Bundle extras) {
+        if (mReportLocation) {
+            return LocationProvider.AVAILABLE;
+        } else {
+            return LocationProvider.TEMPORARILY_UNAVAILABLE;
+        }
+    }
+
+    @Override
+    public long getStatusUpdateTime() {
+        return -1;
+    }
+
+    @Override
+    public void setRequest(ProviderRequest request, WorkSource source) {
+        mReportLocation = request.reportLocation;
+    }
+
+    public void updateLocation(Location location) {
+        if (mReportLocation) {
+            try {
+                // pass the location back to the location manager
+                mLocationManager.reportLocation(location, true);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException calling reportLocation");
+            }
+        }
+    }
+
+    @Override
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        return false;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mReportLocation=" + mReportLocation);
+    }
+}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
new file mode 100644
index 0000000..f91ea8c
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -0,0 +1,1423 @@
+/*
+ * 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.media;
+
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioSystem;
+import android.media.IMediaRouterClient;
+import android.media.IMediaRouterService;
+import android.media.MediaRouter;
+import android.media.MediaRouterClientState;
+import android.media.RemoteDisplayState;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides a mechanism for discovering media routes and manages media playback
+ * behalf of applications.
+ * <p>
+ * Currently supports discovering remote displays via remote display provider
+ * services that have been registered by applications.
+ * </p>
+ */
+public final class MediaRouterService extends IMediaRouterService.Stub
+        implements Watchdog.Monitor {
+    private static final String TAG = "MediaRouterService";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Timeout in milliseconds for a selected route to transition from a
+     * disconnected state to a connecting state.  If we don't observe any
+     * progress within this interval, then we will give up and unselect the route.
+     */
+    static final long CONNECTING_TIMEOUT = 5000;
+
+    /**
+     * Timeout in milliseconds for a selected route to transition from a
+     * connecting state to a connected state.  If we don't observe any
+     * progress within this interval, then we will give up and unselect the route.
+     */
+    static final long CONNECTED_TIMEOUT = 60000;
+
+    private final Context mContext;
+
+    // State guarded by mLock.
+    private final Object mLock = new Object();
+    private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+    private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
+            new ArrayMap<IBinder, ClientRecord>();
+    private int mCurrentUserId = -1;
+
+    public MediaRouterService(Context context) {
+        mContext = context;
+        Watchdog.getInstance().addMonitor(this);
+    }
+
+    public void systemRunning() {
+        IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+                    switchUser();
+                }
+            }
+        }, filter);
+
+        switchUser();
+    }
+
+    @Override
+    public void monitor() {
+        synchronized (mLock) { /* check for deadlock */ }
+    }
+
+    // Binder call
+    @Override
+    public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final int uid = Binder.getCallingUid();
+        if (!validatePackageName(uid, packageName)) {
+            throw new SecurityException("packageName must match the calling uid");
+        }
+
+        final int pid = Binder.getCallingPid();
+        final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+                false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+        final boolean trusted = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) ==
+                PackageManager.PERMISSION_GRANTED;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                registerClientLocked(client, pid, packageName, resolvedUserId, trusted);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void unregisterClient(IMediaRouterClient client) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                unregisterClientLocked(client, false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public MediaRouterClientState getState(IMediaRouterClient client) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                return getStateLocked(client);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void setDiscoveryRequest(IMediaRouterClient client,
+            int routeTypes, boolean activeScan) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                setDiscoveryRequestLocked(client, routeTypes, activeScan);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    // A null routeId means that the client wants to unselect its current route.
+    // The explicit flag indicates whether the change was explicitly requested by the
+    // user or the application which may cause changes to propagate out to the rest
+    // of the system.  Should be false when the change is in response to a new globally
+    // selected route or a default selection.
+    @Override
+    public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                setSelectedRouteLocked(client, routeId, explicit);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestSetVolumeLocked(client, routeId, volume);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
+        if (client == null) {
+            throw new IllegalArgumentException("client must not be null");
+        }
+        if (routeId == null) {
+            throw new IllegalArgumentException("routeId must not be null");
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                requestUpdateVolumeLocked(client, routeId, direction);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // Binder call
+    @Override
+    public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump MediaRouterService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
+        pw.println();
+        pw.println("Global state");
+        pw.println("  mCurrentUserId=" + mCurrentUserId);
+
+        synchronized (mLock) {
+            final int count = mUserRecords.size();
+            for (int i = 0; i < count; i++) {
+                UserRecord userRecord = mUserRecords.valueAt(i);
+                pw.println();
+                userRecord.dump(pw, "");
+            }
+        }
+    }
+
+    void switchUser() {
+        synchronized (mLock) {
+            int userId = ActivityManager.getCurrentUser();
+            if (mCurrentUserId != userId) {
+                final int oldUserId = mCurrentUserId;
+                mCurrentUserId = userId; // do this first
+
+                UserRecord oldUser = mUserRecords.get(oldUserId);
+                if (oldUser != null) {
+                    oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+                    disposeUserIfNeededLocked(oldUser); // since no longer current user
+                }
+
+                UserRecord newUser = mUserRecords.get(userId);
+                if (newUser != null) {
+                    newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+                }
+            }
+        }
+    }
+
+    void clientDied(ClientRecord clientRecord) {
+        synchronized (mLock) {
+            unregisterClientLocked(clientRecord.mClient, true);
+        }
+    }
+
+    private void registerClientLocked(IMediaRouterClient client,
+            int pid, String packageName, int userId, boolean trusted) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord == null) {
+            boolean newUser = false;
+            UserRecord userRecord = mUserRecords.get(userId);
+            if (userRecord == null) {
+                userRecord = new UserRecord(userId);
+                newUser = true;
+            }
+            clientRecord = new ClientRecord(userRecord, client, pid, packageName, trusted);
+            try {
+                binder.linkToDeath(clientRecord, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException("Media router client died prematurely.", ex);
+            }
+
+            if (newUser) {
+                mUserRecords.put(userId, userRecord);
+                initializeUserLocked(userRecord);
+            }
+
+            userRecord.mClientRecords.add(clientRecord);
+            mAllClientRecords.put(binder, clientRecord);
+            initializeClientLocked(clientRecord);
+        }
+    }
+
+    private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
+        ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
+        if (clientRecord != null) {
+            UserRecord userRecord = clientRecord.mUserRecord;
+            userRecord.mClientRecords.remove(clientRecord);
+            disposeClientLocked(clientRecord, died);
+            disposeUserIfNeededLocked(userRecord); // since client removed from user
+        }
+    }
+
+    private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
+        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+        if (clientRecord != null) {
+            return clientRecord.getState();
+        }
+        return null;
+    }
+
+    private void setDiscoveryRequestLocked(IMediaRouterClient client,
+            int routeTypes, boolean activeScan) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            // Only let the system discover remote display routes for now.
+            if (!clientRecord.mTrusted) {
+                routeTypes &= ~MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+            }
+
+            if (clientRecord.mRouteTypes != routeTypes
+                    || clientRecord.mActiveScan != activeScan) {
+                if (DEBUG) {
+                    Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
+                            + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
+                }
+                clientRecord.mRouteTypes = routeTypes;
+                clientRecord.mActiveScan = activeScan;
+                clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+                        UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+            }
+        }
+    }
+
+    private void setSelectedRouteLocked(IMediaRouterClient client,
+            String routeId, boolean explicit) {
+        ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+        if (clientRecord != null) {
+            final String oldRouteId = clientRecord.mSelectedRouteId;
+            if (!Objects.equals(routeId, oldRouteId)) {
+                if (DEBUG) {
+                    Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+                            + ", oldRouteId=" + oldRouteId
+                            + ", explicit=" + explicit);
+                }
+
+                clientRecord.mSelectedRouteId = routeId;
+                if (explicit) {
+                    // Any app can disconnect from the globally selected route.
+                    if (oldRouteId != null) {
+                        clientRecord.mUserRecord.mHandler.obtainMessage(
+                                UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
+                    }
+                    // Only let the system connect to new global routes for now.
+                    // A similar check exists in the display manager for wifi display.
+                    if (routeId != null && clientRecord.mTrusted) {
+                        clientRecord.mUserRecord.mHandler.obtainMessage(
+                                UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
+                    }
+                }
+            }
+        }
+    }
+
+    private void requestSetVolumeLocked(IMediaRouterClient client,
+            String routeId, int volume) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.obtainMessage(
+                    UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
+        }
+    }
+
+    private void requestUpdateVolumeLocked(IMediaRouterClient client,
+            String routeId, int direction) {
+        final IBinder binder = client.asBinder();
+        ClientRecord clientRecord = mAllClientRecords.get(binder);
+        if (clientRecord != null) {
+            clientRecord.mUserRecord.mHandler.obtainMessage(
+                    UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
+        }
+    }
+
+    private void initializeUserLocked(UserRecord userRecord) {
+        if (DEBUG) {
+            Slog.d(TAG, userRecord + ": Initialized");
+        }
+        if (userRecord.mUserId == mCurrentUserId) {
+            userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+        }
+    }
+
+    private void disposeUserIfNeededLocked(UserRecord userRecord) {
+        // If there are no records left and the user is no longer current then go ahead
+        // and purge the user record and all of its associated state.  If the user is current
+        // then leave it alone since we might be connected to a route or want to query
+        // the same route information again soon.
+        if (userRecord.mUserId != mCurrentUserId
+                && userRecord.mClientRecords.isEmpty()) {
+            if (DEBUG) {
+                Slog.d(TAG, userRecord + ": Disposed");
+            }
+            mUserRecords.remove(userRecord.mUserId);
+            // Note: User already stopped (by switchUser) so no need to send stop message here.
+        }
+    }
+
+    private void initializeClientLocked(ClientRecord clientRecord) {
+        if (DEBUG) {
+            Slog.d(TAG, clientRecord + ": Registered");
+        }
+    }
+
+    private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
+        if (DEBUG) {
+            if (died) {
+                Slog.d(TAG, clientRecord + ": Died!");
+            } else {
+                Slog.d(TAG, clientRecord + ": Unregistered");
+            }
+        }
+        if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
+            clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+                    UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+        }
+        clientRecord.dispose();
+    }
+
+    private boolean validatePackageName(int uid, String packageName) {
+        if (packageName != null) {
+            String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+            if (packageNames != null) {
+                for (String n : packageNames) {
+                    if (n.equals(packageName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Information about a particular client of the media router.
+     * The contents of this object is guarded by mLock.
+     */
+    final class ClientRecord implements DeathRecipient {
+        public final UserRecord mUserRecord;
+        public final IMediaRouterClient mClient;
+        public final int mPid;
+        public final String mPackageName;
+        public final boolean mTrusted;
+
+        public int mRouteTypes;
+        public boolean mActiveScan;
+        public String mSelectedRouteId;
+
+        public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
+                int pid, String packageName, boolean trusted) {
+            mUserRecord = userRecord;
+            mClient = client;
+            mPid = pid;
+            mPackageName = packageName;
+            mTrusted = trusted;
+        }
+
+        public void dispose() {
+            mClient.asBinder().unlinkToDeath(this, 0);
+        }
+
+        @Override
+        public void binderDied() {
+            clientDied(this);
+        }
+
+        MediaRouterClientState getState() {
+            return mTrusted ? mUserRecord.mTrustedState : mUserRecord.mUntrustedState;
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + this);
+
+            final String indent = prefix + "  ";
+            pw.println(indent + "mTrusted=" + mTrusted);
+            pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
+            pw.println(indent + "mActiveScan=" + mActiveScan);
+            pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
+        }
+
+        @Override
+        public String toString() {
+            return "Client " + mPackageName + " (pid " + mPid + ")";
+        }
+    }
+
+    /**
+     * Information about a particular user.
+     * The contents of this object is guarded by mLock.
+     */
+    final class UserRecord {
+        public final int mUserId;
+        public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
+        public final UserHandler mHandler;
+        public MediaRouterClientState mTrustedState;
+        public MediaRouterClientState mUntrustedState;
+
+        public UserRecord(int userId) {
+            mUserId = userId;
+            mHandler = new UserHandler(MediaRouterService.this, this);
+        }
+
+        public void dump(final PrintWriter pw, String prefix) {
+            pw.println(prefix + this);
+
+            final String indent = prefix + "  ";
+            final int clientCount = mClientRecords.size();
+            if (clientCount != 0) {
+                for (int i = 0; i < clientCount; i++) {
+                    mClientRecords.get(i).dump(pw, indent);
+                }
+            } else {
+                pw.println(indent + "<no clients>");
+            }
+
+            pw.println(indent + "State");
+            pw.println(indent + "mTrustedState=" + mTrustedState);
+            pw.println(indent + "mUntrustedState=" + mUntrustedState);
+
+            if (!mHandler.runWithScissors(new Runnable() {
+                @Override
+                public void run() {
+                    mHandler.dump(pw, indent);
+                }
+            }, 1000)) {
+                pw.println(indent + "<could not dump handler state>");
+            }
+         }
+
+        @Override
+        public String toString() {
+            return "User " + mUserId;
+        }
+    }
+
+    /**
+     * Media router handler
+     * <p>
+     * Since remote display providers are designed to be single-threaded by nature,
+     * this class encapsulates all of the associated functionality and exports state
+     * to the service as it evolves.
+     * </p><p>
+     * One important task of this class is to keep track of the current globally selected
+     * route id for certain routes that have global effects, such as remote displays.
+     * Global route selections override local selections made within apps.  The change
+     * is propagated to all apps so that they are all in sync.  Synchronization works
+     * both ways.  Whenever the globally selected route is explicitly unselected by any
+     * app, then it becomes unselected globally and all apps are informed.
+     * </p><p>
+     * This class is currently hardcoded to work with remote display providers but
+     * it is intended to be eventually extended to support more general route providers
+     * similar to the support library media router.
+     * </p>
+     */
+    static final class UserHandler extends Handler
+            implements RemoteDisplayProviderWatcher.Callback,
+            RemoteDisplayProviderProxy.Callback {
+        public static final int MSG_START = 1;
+        public static final int MSG_STOP = 2;
+        public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
+        public static final int MSG_SELECT_ROUTE = 4;
+        public static final int MSG_UNSELECT_ROUTE = 5;
+        public static final int MSG_REQUEST_SET_VOLUME = 6;
+        public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
+        private static final int MSG_UPDATE_CLIENT_STATE = 8;
+        private static final int MSG_CONNECTION_TIMED_OUT = 9;
+
+        private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
+        private static final int TIMEOUT_REASON_CONNECTION_LOST = 2;
+        private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 3;
+        private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 4;
+
+        // The relative order of these constants is important and expresses progress
+        // through the process of connecting to a route.
+        private static final int PHASE_NOT_AVAILABLE = -1;
+        private static final int PHASE_NOT_CONNECTED = 0;
+        private static final int PHASE_CONNECTING = 1;
+        private static final int PHASE_CONNECTED = 2;
+
+        private final MediaRouterService mService;
+        private final UserRecord mUserRecord;
+        private final RemoteDisplayProviderWatcher mWatcher;
+        private final ArrayList<ProviderRecord> mProviderRecords =
+                new ArrayList<ProviderRecord>();
+        private final ArrayList<IMediaRouterClient> mTempClients =
+                new ArrayList<IMediaRouterClient>();
+
+        private boolean mRunning;
+        private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+        private RouteRecord mGloballySelectedRouteRecord;
+        private int mConnectionPhase = PHASE_NOT_AVAILABLE;
+        private int mConnectionTimeoutReason;
+        private long mConnectionTimeoutStartTime;
+        private boolean mClientStateUpdateScheduled;
+
+        public UserHandler(MediaRouterService service, UserRecord userRecord) {
+            super(Looper.getMainLooper(), null, true);
+            mService = service;
+            mUserRecord = userRecord;
+            mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
+                    this, mUserRecord.mUserId);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START: {
+                    start();
+                    break;
+                }
+                case MSG_STOP: {
+                    stop();
+                    break;
+                }
+                case MSG_UPDATE_DISCOVERY_REQUEST: {
+                    updateDiscoveryRequest();
+                    break;
+                }
+                case MSG_SELECT_ROUTE: {
+                    selectRoute((String)msg.obj);
+                    break;
+                }
+                case MSG_UNSELECT_ROUTE: {
+                    unselectRoute((String)msg.obj);
+                    break;
+                }
+                case MSG_REQUEST_SET_VOLUME: {
+                    requestSetVolume((String)msg.obj, msg.arg1);
+                    break;
+                }
+                case MSG_REQUEST_UPDATE_VOLUME: {
+                    requestUpdateVolume((String)msg.obj, msg.arg1);
+                    break;
+                }
+                case MSG_UPDATE_CLIENT_STATE: {
+                    updateClientState();
+                    break;
+                }
+                case MSG_CONNECTION_TIMED_OUT: {
+                    connectionTimedOut();
+                    break;
+                }
+            }
+        }
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + "Handler");
+
+            final String indent = prefix + "  ";
+            pw.println(indent + "mRunning=" + mRunning);
+            pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
+            pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+            pw.println(indent + "mConnectionPhase=" + mConnectionPhase);
+            pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
+            pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
+                    TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
+
+            mWatcher.dump(pw, prefix);
+
+            final int providerCount = mProviderRecords.size();
+            if (providerCount != 0) {
+                for (int i = 0; i < providerCount; i++) {
+                    mProviderRecords.get(i).dump(pw, prefix);
+                }
+            } else {
+                pw.println(indent + "<no providers>");
+            }
+        }
+
+        private void start() {
+            if (!mRunning) {
+                mRunning = true;
+                mWatcher.start(); // also starts all providers
+            }
+        }
+
+        private void stop() {
+            if (mRunning) {
+                mRunning = false;
+                unselectGloballySelectedRoute();
+                mWatcher.stop(); // also stops all providers
+            }
+        }
+
+        private void updateDiscoveryRequest() {
+            int routeTypes = 0;
+            boolean activeScan = false;
+            synchronized (mService.mLock) {
+                final int count = mUserRecord.mClientRecords.size();
+                for (int i = 0; i < count; i++) {
+                    ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
+                    routeTypes |= clientRecord.mRouteTypes;
+                    activeScan |= clientRecord.mActiveScan;
+                }
+            }
+
+            final int newDiscoveryMode;
+            if ((routeTypes & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
+                if (activeScan) {
+                    newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+                } else {
+                    newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+                }
+            } else {
+                newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+            }
+
+            if (mDiscoveryMode != newDiscoveryMode) {
+                mDiscoveryMode = newDiscoveryMode;
+                final int count = mProviderRecords.size();
+                for (int i = 0; i < count; i++) {
+                    mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
+                }
+            }
+        }
+
+        private void selectRoute(String routeId) {
+            if (routeId != null
+                    && (mGloballySelectedRouteRecord == null
+                            || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+                RouteRecord routeRecord = findRouteRecord(routeId);
+                if (routeRecord != null) {
+                    unselectGloballySelectedRoute();
+
+                    Slog.i(TAG, "Selected global route:" + routeRecord);
+                    mGloballySelectedRouteRecord = routeRecord;
+                    checkGloballySelectedRouteState();
+                    routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
+
+                    scheduleUpdateClientState();
+                }
+            }
+        }
+
+        private void unselectRoute(String routeId) {
+            if (routeId != null
+                    && mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                unselectGloballySelectedRoute();
+            }
+        }
+
+        private void unselectGloballySelectedRoute() {
+            if (mGloballySelectedRouteRecord != null) {
+                Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
+                mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
+                mGloballySelectedRouteRecord = null;
+                checkGloballySelectedRouteState();
+
+                scheduleUpdateClientState();
+            }
+        }
+
+        private void requestSetVolume(String routeId, int volume) {
+            if (mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+            }
+        }
+
+        private void requestUpdateVolume(String routeId, int direction) {
+            if (mGloballySelectedRouteRecord != null
+                    && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+                mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+            }
+        }
+
+        @Override
+        public void addProvider(RemoteDisplayProviderProxy provider) {
+            provider.setCallback(this);
+            provider.setDiscoveryMode(mDiscoveryMode);
+            provider.setSelectedDisplay(null); // just to be safe
+
+            ProviderRecord providerRecord = new ProviderRecord(provider);
+            mProviderRecords.add(providerRecord);
+            providerRecord.updateDescriptor(provider.getDisplayState());
+
+            scheduleUpdateClientState();
+        }
+
+        @Override
+        public void removeProvider(RemoteDisplayProviderProxy provider) {
+            int index = findProviderRecord(provider);
+            if (index >= 0) {
+                ProviderRecord providerRecord = mProviderRecords.remove(index);
+                providerRecord.updateDescriptor(null); // mark routes invalid
+                provider.setCallback(null);
+                provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
+
+                checkGloballySelectedRouteState();
+                scheduleUpdateClientState();
+            }
+        }
+
+        @Override
+        public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
+                RemoteDisplayState state) {
+            updateProvider(provider, state);
+        }
+
+        private void updateProvider(RemoteDisplayProviderProxy provider,
+                RemoteDisplayState state) {
+            int index = findProviderRecord(provider);
+            if (index >= 0) {
+                ProviderRecord providerRecord = mProviderRecords.get(index);
+                if (providerRecord.updateDescriptor(state)) {
+                    checkGloballySelectedRouteState();
+                    scheduleUpdateClientState();
+                }
+            }
+        }
+
+        /**
+         * This function is called whenever the state of the globally selected route
+         * may have changed.  It checks the state and updates timeouts or unselects
+         * the route as appropriate.
+         */
+        private void checkGloballySelectedRouteState() {
+            // Unschedule timeouts when the route is unselected.
+            if (mGloballySelectedRouteRecord == null) {
+                mConnectionPhase = PHASE_NOT_AVAILABLE;
+                updateConnectionTimeout(0);
+                return;
+            }
+
+            // Ensure that the route is still present and enabled.
+            if (!mGloballySelectedRouteRecord.isValid()
+                    || !mGloballySelectedRouteRecord.isEnabled()) {
+                updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+                return;
+            }
+
+            // Make sure we haven't lost our connection.
+            final int oldPhase = mConnectionPhase;
+            mConnectionPhase = getConnectionPhase(mGloballySelectedRouteRecord.getStatus());
+            if (oldPhase >= PHASE_CONNECTING && mConnectionPhase < PHASE_CONNECTING) {
+                updateConnectionTimeout(TIMEOUT_REASON_CONNECTION_LOST);
+                return;
+            }
+
+            // Check the route status.
+            switch (mConnectionPhase) {
+                case PHASE_CONNECTED:
+                    if (oldPhase != PHASE_CONNECTED) {
+                        Slog.i(TAG, "Connected to global route: "
+                                + mGloballySelectedRouteRecord);
+                    }
+                    updateConnectionTimeout(0);
+                    break;
+                case PHASE_CONNECTING:
+                    if (oldPhase != PHASE_CONNECTING) {
+                        Slog.i(TAG, "Connecting to global route: "
+                                + mGloballySelectedRouteRecord);
+                    }
+                    updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
+                    break;
+                case PHASE_NOT_CONNECTED:
+                    updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
+                    break;
+                case PHASE_NOT_AVAILABLE:
+                default:
+                    updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+                    break;
+            }
+        }
+
+        private void updateConnectionTimeout(int reason) {
+            if (reason != mConnectionTimeoutReason) {
+                if (mConnectionTimeoutReason != 0) {
+                    removeMessages(MSG_CONNECTION_TIMED_OUT);
+                }
+                mConnectionTimeoutReason = reason;
+                mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
+                switch (reason) {
+                    case TIMEOUT_REASON_NOT_AVAILABLE:
+                    case TIMEOUT_REASON_CONNECTION_LOST:
+                        // Route became unavailable or connection lost.
+                        // Unselect it immediately.
+                        sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
+                        break;
+                    case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+                        // Waiting for route to start connecting.
+                        sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
+                        break;
+                    case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+                        // Waiting for route to complete connection.
+                        sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
+                        break;
+                }
+            }
+        }
+
+        private void connectionTimedOut() {
+            if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+                // Shouldn't get here.  There must be a bug somewhere.
+                Log.wtf(TAG, "Handled connection timeout for no reason.");
+                return;
+            }
+
+            switch (mConnectionTimeoutReason) {
+                case TIMEOUT_REASON_NOT_AVAILABLE:
+                    Slog.i(TAG, "Global route no longer available: "
+                            + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_CONNECTION_LOST:
+                    Slog.i(TAG, "Global route connection lost: "
+                            + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+                    Slog.i(TAG, "Global route timed out while waiting for "
+                            + "connection attempt to begin after "
+                            + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+                            + " ms: " + mGloballySelectedRouteRecord);
+                    break;
+                case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+                    Slog.i(TAG, "Global route timed out while connecting after "
+                            + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+                            + " ms: " + mGloballySelectedRouteRecord);
+                    break;
+            }
+            mConnectionTimeoutReason = 0;
+
+            unselectGloballySelectedRoute();
+        }
+
+        private void scheduleUpdateClientState() {
+            if (!mClientStateUpdateScheduled) {
+                mClientStateUpdateScheduled = true;
+                sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
+            }
+        }
+
+        private void updateClientState() {
+            mClientStateUpdateScheduled = false;
+
+            final String globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
+                    mGloballySelectedRouteRecord.getUniqueId() : null;
+
+            // Build a new client state for trusted clients.
+            MediaRouterClientState trustedState = new MediaRouterClientState();
+            trustedState.globallySelectedRouteId = globallySelectedRouteId;
+            final int providerCount = mProviderRecords.size();
+            for (int i = 0; i < providerCount; i++) {
+                mProviderRecords.get(i).appendClientState(trustedState);
+            }
+
+            // Build a new client state for untrusted clients that can only see
+            // the currently selected route.
+            MediaRouterClientState untrustedState = new MediaRouterClientState();
+            untrustedState.globallySelectedRouteId = globallySelectedRouteId;
+            if (globallySelectedRouteId != null) {
+                untrustedState.routes.add(trustedState.getRoute(globallySelectedRouteId));
+            }
+
+            try {
+                synchronized (mService.mLock) {
+                    // Update the UserRecord.
+                    mUserRecord.mTrustedState = trustedState;
+                    mUserRecord.mUntrustedState = untrustedState;
+
+                    // Collect all clients.
+                    final int count = mUserRecord.mClientRecords.size();
+                    for (int i = 0; i < count; i++) {
+                        mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
+                    }
+                }
+
+                // Notify all clients (outside of the lock).
+                final int count = mTempClients.size();
+                for (int i = 0; i < count; i++) {
+                    try {
+                        mTempClients.get(i).onStateChanged();
+                    } catch (RemoteException ex) {
+                        // ignore errors, client probably died
+                    }
+                }
+            } finally {
+                // Clear the list in preparation for the next time.
+                mTempClients.clear();
+            }
+        }
+
+        private int findProviderRecord(RemoteDisplayProviderProxy provider) {
+            final int count = mProviderRecords.size();
+            for (int i = 0; i < count; i++) {
+                ProviderRecord record = mProviderRecords.get(i);
+                if (record.getProvider() == provider) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        private RouteRecord findRouteRecord(String uniqueId) {
+            final int count = mProviderRecords.size();
+            for (int i = 0; i < count; i++) {
+                RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
+                if (record != null) {
+                    return record;
+                }
+            }
+            return null;
+        }
+
+        private static int getConnectionPhase(int status) {
+            switch (status) {
+                case MediaRouter.RouteInfo.STATUS_NONE:
+                case MediaRouter.RouteInfo.STATUS_CONNECTED:
+                    return PHASE_CONNECTED;
+                case MediaRouter.RouteInfo.STATUS_CONNECTING:
+                    return PHASE_CONNECTING;
+                case MediaRouter.RouteInfo.STATUS_SCANNING:
+                case MediaRouter.RouteInfo.STATUS_AVAILABLE:
+                    return PHASE_NOT_CONNECTED;
+                case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
+                case MediaRouter.RouteInfo.STATUS_IN_USE:
+                default:
+                    return PHASE_NOT_AVAILABLE;
+            }
+        }
+
+        static final class ProviderRecord {
+            private final RemoteDisplayProviderProxy mProvider;
+            private final String mUniquePrefix;
+            private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
+            private RemoteDisplayState mDescriptor;
+
+            public ProviderRecord(RemoteDisplayProviderProxy provider) {
+                mProvider = provider;
+                mUniquePrefix = provider.getFlattenedComponentName() + ":";
+            }
+
+            public RemoteDisplayProviderProxy getProvider() {
+                return mProvider;
+            }
+
+            public String getUniquePrefix() {
+                return mUniquePrefix;
+            }
+
+            public boolean updateDescriptor(RemoteDisplayState descriptor) {
+                boolean changed = false;
+                if (mDescriptor != descriptor) {
+                    mDescriptor = descriptor;
+
+                    // Update all existing routes and reorder them to match
+                    // the order of their descriptors.
+                    int targetIndex = 0;
+                    if (descriptor != null) {
+                        if (descriptor.isValid()) {
+                            final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
+                            final int routeCount = routeDescriptors.size();
+                            for (int i = 0; i < routeCount; i++) {
+                                final RemoteDisplayInfo routeDescriptor =
+                                        routeDescriptors.get(i);
+                                final String descriptorId = routeDescriptor.id;
+                                final int sourceIndex = findRouteByDescriptorId(descriptorId);
+                                if (sourceIndex < 0) {
+                                    // Add the route to the provider.
+                                    String uniqueId = assignRouteUniqueId(descriptorId);
+                                    RouteRecord route =
+                                            new RouteRecord(this, descriptorId, uniqueId);
+                                    mRoutes.add(targetIndex++, route);
+                                    route.updateDescriptor(routeDescriptor);
+                                    changed = true;
+                                } else if (sourceIndex < targetIndex) {
+                                    // Ignore route with duplicate id.
+                                    Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
+                                            + routeDescriptor);
+                                } else {
+                                    // Reorder existing route within the list.
+                                    RouteRecord route = mRoutes.get(sourceIndex);
+                                    Collections.swap(mRoutes, sourceIndex, targetIndex++);
+                                    changed |= route.updateDescriptor(routeDescriptor);
+                                }
+                            }
+                        } else {
+                            Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
+                                    + mProvider.getFlattenedComponentName());
+                        }
+                    }
+
+                    // Dispose all remaining routes that do not have matching descriptors.
+                    for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
+                        RouteRecord route = mRoutes.remove(i);
+                        route.updateDescriptor(null); // mark route invalid
+                        changed = true;
+                    }
+                }
+                return changed;
+            }
+
+            public void appendClientState(MediaRouterClientState state) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    state.routes.add(mRoutes.get(i).getInfo());
+                }
+            }
+
+            public RouteRecord findRouteByUniqueId(String uniqueId) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    RouteRecord route = mRoutes.get(i);
+                    if (route.getUniqueId().equals(uniqueId)) {
+                        return route;
+                    }
+                }
+                return null;
+            }
+
+            private int findRouteByDescriptorId(String descriptorId) {
+                final int routeCount = mRoutes.size();
+                for (int i = 0; i < routeCount; i++) {
+                    RouteRecord route = mRoutes.get(i);
+                    if (route.getDescriptorId().equals(descriptorId)) {
+                        return i;
+                    }
+                }
+                return -1;
+            }
+
+            public void dump(PrintWriter pw, String prefix) {
+                pw.println(prefix + this);
+
+                final String indent = prefix + "  ";
+                mProvider.dump(pw, indent);
+
+                final int routeCount = mRoutes.size();
+                if (routeCount != 0) {
+                    for (int i = 0; i < routeCount; i++) {
+                        mRoutes.get(i).dump(pw, indent);
+                    }
+                } else {
+                    pw.println(indent + "<no routes>");
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "Provider " + mProvider.getFlattenedComponentName();
+            }
+
+            private String assignRouteUniqueId(String descriptorId) {
+                return mUniquePrefix + descriptorId;
+            }
+        }
+
+        static final class RouteRecord {
+            private final ProviderRecord mProviderRecord;
+            private final String mDescriptorId;
+            private final MediaRouterClientState.RouteInfo mMutableInfo;
+            private MediaRouterClientState.RouteInfo mImmutableInfo;
+            private RemoteDisplayInfo mDescriptor;
+
+            public RouteRecord(ProviderRecord providerRecord,
+                    String descriptorId, String uniqueId) {
+                mProviderRecord = providerRecord;
+                mDescriptorId = descriptorId;
+                mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
+            }
+
+            public RemoteDisplayProviderProxy getProvider() {
+                return mProviderRecord.getProvider();
+            }
+
+            public ProviderRecord getProviderRecord() {
+                return mProviderRecord;
+            }
+
+            public String getDescriptorId() {
+                return mDescriptorId;
+            }
+
+            public String getUniqueId() {
+                return mMutableInfo.id;
+            }
+
+            public MediaRouterClientState.RouteInfo getInfo() {
+                if (mImmutableInfo == null) {
+                    mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
+                }
+                return mImmutableInfo;
+            }
+
+            public boolean isValid() {
+                return mDescriptor != null;
+            }
+
+            public boolean isEnabled() {
+                return mMutableInfo.enabled;
+            }
+
+            public int getStatus() {
+                return mMutableInfo.statusCode;
+            }
+
+            public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
+                boolean changed = false;
+                if (mDescriptor != descriptor) {
+                    mDescriptor = descriptor;
+                    if (descriptor != null) {
+                        final String name = computeName(descriptor);
+                        if (!Objects.equals(mMutableInfo.name, name)) {
+                            mMutableInfo.name = name;
+                            changed = true;
+                        }
+                        final String description = computeDescription(descriptor);
+                        if (!Objects.equals(mMutableInfo.description, description)) {
+                            mMutableInfo.description = description;
+                            changed = true;
+                        }
+                        final int supportedTypes = computeSupportedTypes(descriptor);
+                        if (mMutableInfo.supportedTypes != supportedTypes) {
+                            mMutableInfo.supportedTypes = supportedTypes;
+                            changed = true;
+                        }
+                        final boolean enabled = computeEnabled(descriptor);
+                        if (mMutableInfo.enabled != enabled) {
+                            mMutableInfo.enabled = enabled;
+                            changed = true;
+                        }
+                        final int statusCode = computeStatusCode(descriptor);
+                        if (mMutableInfo.statusCode != statusCode) {
+                            mMutableInfo.statusCode = statusCode;
+                            changed = true;
+                        }
+                        final int playbackType = computePlaybackType(descriptor);
+                        if (mMutableInfo.playbackType != playbackType) {
+                            mMutableInfo.playbackType = playbackType;
+                            changed = true;
+                        }
+                        final int playbackStream = computePlaybackStream(descriptor);
+                        if (mMutableInfo.playbackStream != playbackStream) {
+                            mMutableInfo.playbackStream = playbackStream;
+                            changed = true;
+                        }
+                        final int volume = computeVolume(descriptor);
+                        if (mMutableInfo.volume != volume) {
+                            mMutableInfo.volume = volume;
+                            changed = true;
+                        }
+                        final int volumeMax = computeVolumeMax(descriptor);
+                        if (mMutableInfo.volumeMax != volumeMax) {
+                            mMutableInfo.volumeMax = volumeMax;
+                            changed = true;
+                        }
+                        final int volumeHandling = computeVolumeHandling(descriptor);
+                        if (mMutableInfo.volumeHandling != volumeHandling) {
+                            mMutableInfo.volumeHandling = volumeHandling;
+                            changed = true;
+                        }
+                        final int presentationDisplayId = computePresentationDisplayId(descriptor);
+                        if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+                            mMutableInfo.presentationDisplayId = presentationDisplayId;
+                            changed = true;
+                        }
+                    }
+                }
+                if (changed) {
+                    mImmutableInfo = null;
+                }
+                return changed;
+            }
+
+            public void dump(PrintWriter pw, String prefix) {
+                pw.println(prefix + this);
+
+                final String indent = prefix + "  ";
+                pw.println(indent + "mMutableInfo=" + mMutableInfo);
+                pw.println(indent + "mDescriptorId=" + mDescriptorId);
+                pw.println(indent + "mDescriptor=" + mDescriptor);
+            }
+
+            @Override
+            public String toString() {
+                return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
+            }
+
+            private static String computeName(RemoteDisplayInfo descriptor) {
+                // Note that isValid() already ensures the name is non-empty.
+                return descriptor.name;
+            }
+
+            private static String computeDescription(RemoteDisplayInfo descriptor) {
+                final String description = descriptor.description;
+                return TextUtils.isEmpty(description) ? null : description;
+            }
+
+            private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
+                return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+                        | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+                        | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+            }
+
+            private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
+                switch (descriptor.status) {
+                    case RemoteDisplayInfo.STATUS_CONNECTED:
+                    case RemoteDisplayInfo.STATUS_CONNECTING:
+                    case RemoteDisplayInfo.STATUS_AVAILABLE:
+                        return true;
+                    default:
+                        return false;
+                }
+            }
+
+            private static int computeStatusCode(RemoteDisplayInfo descriptor) {
+                switch (descriptor.status) {
+                    case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
+                        return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
+                    case RemoteDisplayInfo.STATUS_AVAILABLE:
+                        return MediaRouter.RouteInfo.STATUS_AVAILABLE;
+                    case RemoteDisplayInfo.STATUS_IN_USE:
+                        return MediaRouter.RouteInfo.STATUS_IN_USE;
+                    case RemoteDisplayInfo.STATUS_CONNECTING:
+                        return MediaRouter.RouteInfo.STATUS_CONNECTING;
+                    case RemoteDisplayInfo.STATUS_CONNECTED:
+                        return MediaRouter.RouteInfo.STATUS_CONNECTED;
+                    default:
+                        return MediaRouter.RouteInfo.STATUS_NONE;
+                }
+            }
+
+            private static int computePlaybackType(RemoteDisplayInfo descriptor) {
+                return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+            }
+
+            private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
+                return AudioSystem.STREAM_MUSIC;
+            }
+
+            private static int computeVolume(RemoteDisplayInfo descriptor) {
+                final int volume = descriptor.volume;
+                final int volumeMax = descriptor.volumeMax;
+                if (volume < 0) {
+                    return 0;
+                } else if (volume > volumeMax) {
+                    return volumeMax;
+                }
+                return volume;
+            }
+
+            private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
+                final int volumeMax = descriptor.volumeMax;
+                return volumeMax > 0 ? volumeMax : 0;
+            }
+
+            private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
+                final int volumeHandling = descriptor.volumeHandling;
+                switch (volumeHandling) {
+                    case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
+                        return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+                    case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
+                    default:
+                        return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+                }
+            }
+
+            private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
+                // The MediaRouter class validates that the id corresponds to an extant
+                // presentation display.  So all we do here is canonicalize the null case.
+                final int displayId = descriptor.presentationDisplayId;
+                return displayId < 0 ? -1 : displayId;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
new file mode 100644
index 0000000..a5fe9f2
--- /dev/null
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -0,0 +1,442 @@
+/*
+ * 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.media;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IRemoteDisplayCallback;
+import android.media.IRemoteDisplayProvider;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.IBinder.DeathRecipient;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+
+/**
+ * Maintains a connection to a particular remote display provider service.
+ */
+final class RemoteDisplayProviderProxy implements ServiceConnection {
+    private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final ComponentName mComponentName;
+    private final int mUserId;
+    private final Handler mHandler;
+
+    private Callback mDisplayStateCallback;
+
+    // Connection state
+    private boolean mRunning;
+    private boolean mBound;
+    private Connection mActiveConnection;
+    private boolean mConnectionReady;
+
+    // Logical state
+    private int mDiscoveryMode;
+    private String mSelectedDisplayId;
+    private RemoteDisplayState mDisplayState;
+    private boolean mScheduledDisplayStateChangedCallback;
+
+    public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
+            int userId) {
+        mContext = context;
+        mComponentName = componentName;
+        mUserId = userId;
+        mHandler = new Handler();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "Proxy");
+        pw.println(prefix + "  mUserId=" + mUserId);
+        pw.println(prefix + "  mRunning=" + mRunning);
+        pw.println(prefix + "  mBound=" + mBound);
+        pw.println(prefix + "  mActiveConnection=" + mActiveConnection);
+        pw.println(prefix + "  mConnectionReady=" + mConnectionReady);
+        pw.println(prefix + "  mDiscoveryMode=" + mDiscoveryMode);
+        pw.println(prefix + "  mSelectedDisplayId=" + mSelectedDisplayId);
+        pw.println(prefix + "  mDisplayState=" + mDisplayState);
+    }
+
+    public void setCallback(Callback callback) {
+        mDisplayStateCallback = callback;
+    }
+
+    public RemoteDisplayState getDisplayState() {
+        return mDisplayState;
+    }
+
+    public void setDiscoveryMode(int mode) {
+        if (mDiscoveryMode != mode) {
+            mDiscoveryMode = mode;
+            if (mConnectionReady) {
+                mActiveConnection.setDiscoveryMode(mode);
+            }
+            updateBinding();
+        }
+    }
+
+    public void setSelectedDisplay(String id) {
+        if (!Objects.equals(mSelectedDisplayId, id)) {
+            if (mConnectionReady && mSelectedDisplayId != null) {
+                mActiveConnection.disconnect(mSelectedDisplayId);
+            }
+            mSelectedDisplayId = id;
+            if (mConnectionReady && id != null) {
+                mActiveConnection.connect(id);
+            }
+            updateBinding();
+        }
+    }
+
+    public void setDisplayVolume(int volume) {
+        if (mConnectionReady && mSelectedDisplayId != null) {
+            mActiveConnection.setVolume(mSelectedDisplayId, volume);
+        }
+    }
+
+    public void adjustDisplayVolume(int delta) {
+        if (mConnectionReady && mSelectedDisplayId != null) {
+            mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
+        }
+    }
+
+    public boolean hasComponentName(String packageName, String className) {
+        return mComponentName.getPackageName().equals(packageName)
+                && mComponentName.getClassName().equals(className);
+    }
+
+    public String getFlattenedComponentName() {
+        return mComponentName.flattenToShortString();
+    }
+
+    public void start() {
+        if (!mRunning) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Starting");
+            }
+
+            mRunning = true;
+            updateBinding();
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Stopping");
+            }
+
+            mRunning = false;
+            updateBinding();
+        }
+    }
+
+    public void rebindIfDisconnected() {
+        if (mActiveConnection == null && shouldBind()) {
+            unbind();
+            bind();
+        }
+    }
+
+    private void updateBinding() {
+        if (shouldBind()) {
+            bind();
+        } else {
+            unbind();
+        }
+    }
+
+    private boolean shouldBind() {
+        if (mRunning) {
+            // Bind whenever there is a discovery request or selected display.
+            if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
+                    || mSelectedDisplayId != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void bind() {
+        if (!mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Binding");
+            }
+
+            Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+            service.setComponent(mComponentName);
+            try {
+                mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
+                        new UserHandle(mUserId));
+                if (!mBound && DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed");
+                }
+            } catch (SecurityException ex) {
+                if (DEBUG) {
+                    Slog.d(TAG, this + ": Bind failed", ex);
+                }
+            }
+        }
+    }
+
+    private void unbind() {
+        if (mBound) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Unbinding");
+            }
+
+            mBound = false;
+            disconnect();
+            mContext.unbindService(this);
+        }
+    }
+
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+        if (DEBUG) {
+            Slog.d(TAG, this + ": Connected");
+        }
+
+        if (mBound) {
+            disconnect();
+
+            IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
+            if (provider != null) {
+                Connection connection = new Connection(provider);
+                if (connection.register()) {
+                    mActiveConnection = connection;
+                } else {
+                    if (DEBUG) {
+                        Slog.d(TAG, this + ": Registration failed");
+                    }
+                }
+            } else {
+                Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
+            }
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+        if (DEBUG) {
+            Slog.d(TAG, this + ": Service disconnected");
+        }
+        disconnect();
+    }
+
+    private void onConnectionReady(Connection connection) {
+        if (mActiveConnection == connection) {
+            mConnectionReady = true;
+
+            if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
+                mActiveConnection.setDiscoveryMode(mDiscoveryMode);
+            }
+            if (mSelectedDisplayId != null) {
+                mActiveConnection.connect(mSelectedDisplayId);
+            }
+        }
+    }
+
+    private void onConnectionDied(Connection connection) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": Service connection died");
+            }
+            disconnect();
+        }
+    }
+
+    private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
+        if (mActiveConnection == connection) {
+            if (DEBUG) {
+                Slog.d(TAG, this + ": State changed, state=" + state);
+            }
+            setDisplayState(state);
+        }
+    }
+
+    private void disconnect() {
+        if (mActiveConnection != null) {
+            if (mSelectedDisplayId != null) {
+                mActiveConnection.disconnect(mSelectedDisplayId);
+            }
+            mConnectionReady = false;
+            mActiveConnection.dispose();
+            mActiveConnection = null;
+            setDisplayState(null);
+        }
+    }
+
+    private void setDisplayState(RemoteDisplayState state) {
+        if (!Objects.equals(mDisplayState, state)) {
+            mDisplayState = state;
+            if (!mScheduledDisplayStateChangedCallback) {
+                mScheduledDisplayStateChangedCallback = true;
+                mHandler.post(mDisplayStateChanged);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "Service connection " + mComponentName.flattenToShortString();
+    }
+
+    private final Runnable mDisplayStateChanged = new Runnable() {
+        @Override
+        public void run() {
+            mScheduledDisplayStateChangedCallback = false;
+            if (mDisplayStateCallback != null) {
+                mDisplayStateCallback.onDisplayStateChanged(
+                        RemoteDisplayProviderProxy.this, mDisplayState);
+            }
+        }
+    };
+
+    public interface Callback {
+        void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
+    }
+
+    private final class Connection implements DeathRecipient {
+        private final IRemoteDisplayProvider mProvider;
+        private final ProviderCallback mCallback;
+
+        public Connection(IRemoteDisplayProvider provider) {
+            mProvider = provider;
+            mCallback = new ProviderCallback(this);
+        }
+
+        public boolean register() {
+            try {
+                mProvider.asBinder().linkToDeath(this, 0);
+                mProvider.setCallback(mCallback);
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        onConnectionReady(Connection.this);
+                    }
+                });
+                return true;
+            } catch (RemoteException ex) {
+                binderDied();
+            }
+            return false;
+        }
+
+        public void dispose() {
+            mProvider.asBinder().unlinkToDeath(this, 0);
+            mCallback.dispose();
+        }
+
+        public void setDiscoveryMode(int mode) {
+            try {
+                mProvider.setDiscoveryMode(mode);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
+            }
+        }
+
+        public void connect(String id) {
+            try {
+                mProvider.connect(id);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
+            }
+        }
+
+        public void disconnect(String id) {
+            try {
+                mProvider.disconnect(id);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
+            }
+        }
+
+        public void setVolume(String id, int volume) {
+            try {
+                mProvider.setVolume(id, volume);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
+            }
+        }
+
+        public void adjustVolume(String id, int volume) {
+            try {
+                mProvider.adjustVolume(id, volume);
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onConnectionDied(Connection.this);
+                }
+            });
+        }
+
+        void postStateChanged(final RemoteDisplayState state) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    onDisplayStateChanged(Connection.this, state);
+                }
+            });
+        }
+    }
+
+    /**
+     * Receives callbacks from the service.
+     * <p>
+     * This inner class is static and only retains a weak reference to the connection
+     * to prevent the client from being leaked in case the service is holding an
+     * active reference to the client's callback.
+     * </p>
+     */
+    private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
+        private final WeakReference<Connection> mConnectionRef;
+
+        public ProviderCallback(Connection connection) {
+            mConnectionRef = new WeakReference<Connection>(connection);
+        }
+
+        public void dispose() {
+            mConnectionRef.clear();
+        }
+
+        @Override
+        public void onStateChanged(RemoteDisplayState state) throws RemoteException {
+            Connection connection = mConnectionRef.get();
+            if (connection != null) {
+                connection.postStateChanged(state);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
new file mode 100644
index 0000000..6a5f563
--- /dev/null
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -0,0 +1,219 @@
+/*
+ * 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.media;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * Watches for remote display provider services to be installed.
+ * Adds a provider to the media router for each registered service.
+ *
+ * @see RemoteDisplayProviderProxy
+ */
+public final class RemoteDisplayProviderWatcher {
+    private static final String TAG = "RemoteDisplayProvider";  // max. 23 chars
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private final Context mContext;
+    private final Callback mCallback;
+    private final Handler mHandler;
+    private final int mUserId;
+    private final PackageManager mPackageManager;
+
+    private final ArrayList<RemoteDisplayProviderProxy> mProviders =
+            new ArrayList<RemoteDisplayProviderProxy>();
+    private boolean mRunning;
+
+    public RemoteDisplayProviderWatcher(Context context,
+            Callback callback, Handler handler, int userId) {
+        mContext = context;
+        mCallback = callback;
+        mHandler = handler;
+        mUserId = userId;
+        mPackageManager = context.getPackageManager();
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "Watcher");
+        pw.println(prefix + "  mUserId=" + mUserId);
+        pw.println(prefix + "  mRunning=" + mRunning);
+        pw.println(prefix + "  mProviders.size()=" + mProviders.size());
+    }
+
+    public void start() {
+        if (!mRunning) {
+            mRunning = true;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+            filter.addDataScheme("package");
+            mContext.registerReceiverAsUser(mScanPackagesReceiver,
+                    new UserHandle(mUserId), filter, null, mHandler);
+
+            // Scan packages.
+            // Also has the side-effect of restarting providers if needed.
+            mHandler.post(mScanPackagesRunnable);
+        }
+    }
+
+    public void stop() {
+        if (mRunning) {
+            mRunning = false;
+
+            mContext.unregisterReceiver(mScanPackagesReceiver);
+            mHandler.removeCallbacks(mScanPackagesRunnable);
+
+            // Stop all providers.
+            for (int i = mProviders.size() - 1; i >= 0; i--) {
+                mProviders.get(i).stop();
+            }
+        }
+    }
+
+    private void scanPackages() {
+        if (!mRunning) {
+            return;
+        }
+
+        // Add providers for all new services.
+        // Reorder the list so that providers left at the end will be the ones to remove.
+        int targetIndex = 0;
+        Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser(
+                intent, 0, mUserId)) {
+            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) {
+                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
+                if (sourceIndex < 0) {
+                    RemoteDisplayProviderProxy provider =
+                            new RemoteDisplayProviderProxy(mContext,
+                            new ComponentName(serviceInfo.packageName, serviceInfo.name),
+                            mUserId);
+                    provider.start();
+                    mProviders.add(targetIndex++, provider);
+                    mCallback.addProvider(provider);
+                } else if (sourceIndex >= targetIndex) {
+                    RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex);
+                    provider.start(); // restart the provider if needed
+                    provider.rebindIfDisconnected();
+                    Collections.swap(mProviders, sourceIndex, targetIndex++);
+                }
+            }
+        }
+
+        // Remove providers for missing services.
+        if (targetIndex < mProviders.size()) {
+            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
+                RemoteDisplayProviderProxy provider = mProviders.get(i);
+                mCallback.removeProvider(provider);
+                mProviders.remove(provider);
+                provider.stop();
+            }
+        }
+    }
+
+    private boolean verifyServiceTrusted(ServiceInfo serviceInfo) {
+        if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+                Manifest.permission.BIND_REMOTE_DISPLAY)) {
+            // If the service does not require this permission then any app could
+            // potentially bind to it and cause the remote display service to
+            // misbehave.  So we only want to trust providers that require the
+            // correct permissions.
+            Slog.w(TAG, "Ignoring remote display provider service because it did not "
+                    + "require the BIND_REMOTE_DISPLAY permission in its manifest: "
+                    + serviceInfo.packageName + "/" + serviceInfo.name);
+            return false;
+        }
+        if (!hasCaptureVideoPermission(serviceInfo.packageName)) {
+            // If the service does not have permission to capture video then it
+            // isn't going to be terribly useful as a remote display, is it?
+            // Kind of makes you wonder what it's doing there in the first place.
+            Slog.w(TAG, "Ignoring remote display provider service because it does not "
+                    + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT "
+                    + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name);
+            return false;
+        }
+        // Looks good.
+        return true;
+    }
+
+    private boolean hasCaptureVideoPermission(String packageName) {
+        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT,
+                packageName) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT,
+                packageName) == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        return false;
+    }
+
+    private int findProvider(String packageName, String className) {
+        int count = mProviders.size();
+        for (int i = 0; i < count; i++) {
+            RemoteDisplayProviderProxy provider = mProviders.get(i);
+            if (provider.hasComponentName(packageName, className)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) {
+                Slog.d(TAG, "Received package manager broadcast: " + intent);
+            }
+            scanPackages();
+        }
+    };
+
+    private final Runnable mScanPackagesRunnable = new Runnable() {
+        @Override
+        public void run() {
+            scanPackages();
+        }
+    };
+
+    public interface Callback {
+        void addProvider(RemoteDisplayProviderProxy provider);
+        void removeProvider(RemoteDisplayProviderProxy provider);
+    }
+}
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
new file mode 100644
index 0000000..a2e9d676
--- /dev/null
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.LinkProperties;
+import android.net.LinkAddress;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.util.Preconditions;
+import com.android.server.ConnectivityService;
+import com.android.server.EventLogTags;
+import com.android.server.connectivity.Vpn;
+
+import java.util.List;
+
+/**
+ * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be
+ * connected and kicks off VPN connection, managing any required {@code netd}
+ * firewall rules.
+ */
+public class LockdownVpnTracker {
+    private static final String TAG = "LockdownVpnTracker";
+
+    /** Number of VPN attempts before waiting for user intervention. */
+    private static final int MAX_ERROR_COUNT = 4;
+
+    private static final String ACTION_LOCKDOWN_RESET = "com.android.server.action.LOCKDOWN_RESET";
+
+    private static final String ACTION_VPN_SETTINGS = "android.net.vpn.SETTINGS";
+    private static final String EXTRA_PICK_LOCKDOWN = "android.net.vpn.PICK_LOCKDOWN";
+
+    private final Context mContext;
+    private final INetworkManagementService mNetService;
+    private final ConnectivityService mConnService;
+    private final Vpn mVpn;
+    private final VpnProfile mProfile;
+
+    private final Object mStateLock = new Object();
+
+    private final PendingIntent mConfigIntent;
+    private final PendingIntent mResetIntent;
+
+    private String mAcceptedEgressIface;
+    private String mAcceptedIface;
+    private List<LinkAddress> mAcceptedSourceAddr;
+
+    private int mErrorCount;
+
+    public static boolean isEnabled() {
+        return KeyStore.getInstance().contains(Credentials.LOCKDOWN_VPN);
+    }
+
+    public LockdownVpnTracker(Context context, INetworkManagementService netService,
+            ConnectivityService connService, Vpn vpn, VpnProfile profile) {
+        mContext = Preconditions.checkNotNull(context);
+        mNetService = Preconditions.checkNotNull(netService);
+        mConnService = Preconditions.checkNotNull(connService);
+        mVpn = Preconditions.checkNotNull(vpn);
+        mProfile = Preconditions.checkNotNull(profile);
+
+        final Intent configIntent = new Intent(ACTION_VPN_SETTINGS);
+        configIntent.putExtra(EXTRA_PICK_LOCKDOWN, true);
+        mConfigIntent = PendingIntent.getActivity(mContext, 0, configIntent, 0);
+
+        final Intent resetIntent = new Intent(ACTION_LOCKDOWN_RESET);
+        resetIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mResetIntent = PendingIntent.getBroadcast(mContext, 0, resetIntent, 0);
+    }
+
+    private BroadcastReceiver mResetReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reset();
+        }
+    };
+
+    /**
+     * Watch for state changes to both active egress network, kicking off a VPN
+     * connection when ready, or setting firewall rules once VPN is connected.
+     */
+    private void handleStateChangedLocked() {
+        Slog.d(TAG, "handleStateChanged()");
+
+        final NetworkInfo egressInfo = mConnService.getActiveNetworkInfoUnfiltered();
+        final LinkProperties egressProp = mConnService.getActiveLinkProperties();
+
+        final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
+        final VpnConfig vpnConfig = mVpn.getLegacyVpnConfig();
+
+        // Restart VPN when egress network disconnected or changed
+        final boolean egressDisconnected = egressInfo == null
+                || State.DISCONNECTED.equals(egressInfo.getState());
+        final boolean egressChanged = egressProp == null
+                || !TextUtils.equals(mAcceptedEgressIface, egressProp.getInterfaceName());
+        if (egressDisconnected || egressChanged) {
+            clearSourceRulesLocked();
+            mAcceptedEgressIface = null;
+            mVpn.stopLegacyVpn();
+        }
+        if (egressDisconnected) {
+            hideNotification();
+            return;
+        }
+
+        final int egressType = egressInfo.getType();
+        if (vpnInfo.getDetailedState() == DetailedState.FAILED) {
+            EventLogTags.writeLockdownVpnError(egressType);
+        }
+
+        if (mErrorCount > MAX_ERROR_COUNT) {
+            showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+
+        } else if (egressInfo.isConnected() && !vpnInfo.isConnectedOrConnecting()) {
+            if (mProfile.isValidLockdownProfile()) {
+                Slog.d(TAG, "Active network connected; starting VPN");
+                EventLogTags.writeLockdownVpnConnecting(egressType);
+                showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected);
+
+                mAcceptedEgressIface = egressProp.getInterfaceName();
+                try {
+                    mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp);
+                } catch (IllegalStateException e) {
+                    mAcceptedEgressIface = null;
+                    Slog.e(TAG, "Failed to start VPN", e);
+                    showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+                }
+            } else {
+                Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS");
+                showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected);
+            }
+
+        } else if (vpnInfo.isConnected() && vpnConfig != null) {
+            final String iface = vpnConfig.interfaze;
+            final List<LinkAddress> sourceAddrs = vpnConfig.addresses;
+
+            if (TextUtils.equals(iface, mAcceptedIface)
+                  && sourceAddrs.equals(mAcceptedSourceAddr)) {
+                return;
+            }
+
+            Slog.d(TAG, "VPN connected using iface=" + iface +
+                    ", sourceAddr=" + sourceAddrs.toString());
+            EventLogTags.writeLockdownVpnConnected(egressType);
+            showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected);
+
+            try {
+                clearSourceRulesLocked();
+
+                mNetService.setFirewallInterfaceRule(iface, true);
+                for (LinkAddress addr : sourceAddrs) {
+                    mNetService.setFirewallEgressSourceRule(addr.toString(), true);
+                }
+
+                mErrorCount = 0;
+                mAcceptedIface = iface;
+                mAcceptedSourceAddr = sourceAddrs;
+            } catch (RemoteException e) {
+                throw new RuntimeException("Problem setting firewall rules", e);
+            }
+
+            mConnService.sendConnectedBroadcast(augmentNetworkInfo(egressInfo));
+        }
+    }
+
+    public void init() {
+        synchronized (mStateLock) {
+            initLocked();
+        }
+    }
+
+    private void initLocked() {
+        Slog.d(TAG, "initLocked()");
+
+        mVpn.setEnableNotifications(false);
+        mVpn.setEnableTeardown(false);
+
+        final IntentFilter resetFilter = new IntentFilter(ACTION_LOCKDOWN_RESET);
+        mContext.registerReceiver(mResetReceiver, resetFilter, CONNECTIVITY_INTERNAL, null);
+
+        try {
+            // TODO: support non-standard port numbers
+            mNetService.setFirewallEgressDestRule(mProfile.server, 500, true);
+            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, true);
+            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, true);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Problem setting firewall rules", e);
+        }
+
+        synchronized (mStateLock) {
+            handleStateChangedLocked();
+        }
+    }
+
+    public void shutdown() {
+        synchronized (mStateLock) {
+            shutdownLocked();
+        }
+    }
+
+    private void shutdownLocked() {
+        Slog.d(TAG, "shutdownLocked()");
+
+        mAcceptedEgressIface = null;
+        mErrorCount = 0;
+
+        mVpn.stopLegacyVpn();
+        try {
+            mNetService.setFirewallEgressDestRule(mProfile.server, 500, false);
+            mNetService.setFirewallEgressDestRule(mProfile.server, 4500, false);
+            mNetService.setFirewallEgressDestRule(mProfile.server, 1701, false);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Problem setting firewall rules", e);
+        }
+        clearSourceRulesLocked();
+        hideNotification();
+
+        mContext.unregisterReceiver(mResetReceiver);
+        mVpn.setEnableNotifications(true);
+        mVpn.setEnableTeardown(true);
+    }
+
+    public void reset() {
+        synchronized (mStateLock) {
+            // cycle tracker, reset error count, and trigger retry
+            shutdownLocked();
+            initLocked();
+            handleStateChangedLocked();
+        }
+    }
+
+    private void clearSourceRulesLocked() {
+        try {
+            if (mAcceptedIface != null) {
+                mNetService.setFirewallInterfaceRule(mAcceptedIface, false);
+                mAcceptedIface = null;
+            }
+            if (mAcceptedSourceAddr != null) {
+                for (LinkAddress addr : mAcceptedSourceAddr) {
+                    mNetService.setFirewallEgressSourceRule(addr.toString(), false);
+                }
+                mAcceptedSourceAddr = null;
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException("Problem setting firewall rules", e);
+        }
+    }
+
+    public void onNetworkInfoChanged(NetworkInfo info) {
+        synchronized (mStateLock) {
+            handleStateChangedLocked();
+        }
+    }
+
+    public void onVpnStateChanged(NetworkInfo info) {
+        if (info.getDetailedState() == DetailedState.FAILED) {
+            mErrorCount++;
+        }
+        synchronized (mStateLock) {
+            handleStateChangedLocked();
+        }
+    }
+
+    public NetworkInfo augmentNetworkInfo(NetworkInfo info) {
+        if (info.isConnected()) {
+            final NetworkInfo vpnInfo = mVpn.getNetworkInfo();
+            info = new NetworkInfo(info);
+            info.setDetailedState(vpnInfo.getDetailedState(), vpnInfo.getReason(), null);
+        }
+        return info;
+    }
+
+    private void showNotification(int titleRes, int iconRes) {
+        final Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setWhen(0);
+        builder.setSmallIcon(iconRes);
+        builder.setContentTitle(mContext.getString(titleRes));
+        builder.setContentText(mContext.getString(R.string.vpn_lockdown_config));
+        builder.setContentIntent(mConfigIntent);
+        builder.setPriority(Notification.PRIORITY_LOW);
+        builder.setOngoing(true);
+        builder.addAction(
+                R.drawable.ic_menu_refresh, mContext.getString(R.string.reset), mResetIntent);
+
+        NotificationManager.from(mContext).notify(TAG, 0, builder.build());
+    }
+
+    private void hideNotification() {
+        NotificationManager.from(mContext).cancel(TAG, 0);
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java
new file mode 100644
index 0000000..397f9f4
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import android.net.NetworkIdentity;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.HashSet;
+
+/**
+ * Identity of a {@code iface}, defined by the set of {@link NetworkIdentity}
+ * active on that interface.
+ *
+ * @hide
+ */
+public class NetworkIdentitySet extends HashSet<NetworkIdentity> {
+    private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADD_ROAMING = 2;
+    private static final int VERSION_ADD_NETWORK_ID = 3;
+
+    public NetworkIdentitySet() {
+    }
+
+    public NetworkIdentitySet(DataInputStream in) throws IOException {
+        final int version = in.readInt();
+        final int size = in.readInt();
+        for (int i = 0; i < size; i++) {
+            if (version <= VERSION_INIT) {
+                final int ignored = in.readInt();
+            }
+            final int type = in.readInt();
+            final int subType = in.readInt();
+            final String subscriberId = readOptionalString(in);
+            final String networkId;
+            if (version >= VERSION_ADD_NETWORK_ID) {
+                networkId = readOptionalString(in);
+            } else {
+                networkId = null;
+            }
+            final boolean roaming;
+            if (version >= VERSION_ADD_ROAMING) {
+                roaming = in.readBoolean();
+            } else {
+                roaming = false;
+            }
+
+            add(new NetworkIdentity(type, subType, subscriberId, networkId, false));
+        }
+    }
+
+    public void writeToStream(DataOutputStream out) throws IOException {
+        out.writeInt(VERSION_ADD_NETWORK_ID);
+        out.writeInt(size());
+        for (NetworkIdentity ident : this) {
+            out.writeInt(ident.getType());
+            out.writeInt(ident.getSubType());
+            writeOptionalString(out, ident.getSubscriberId());
+            writeOptionalString(out, ident.getNetworkId());
+            out.writeBoolean(ident.getRoaming());
+        }
+    }
+
+    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
+        if (value != null) {
+            out.writeByte(1);
+            out.writeUTF(value);
+        } else {
+            out.writeByte(0);
+        }
+    }
+
+    private static String readOptionalString(DataInputStream in) throws IOException {
+        if (in.readByte() != 0) {
+            return in.readUTF();
+        } else {
+            return null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
new file mode 100644
index 0000000..eb7cc4c
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -0,0 +1,2089 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.ACTION_USER_ADDED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.isNetworkTypeMobile;
+import static android.net.NetworkPolicy.CYCLE_NONE;
+import static android.net.NetworkPolicy.LIMIT_DISABLED;
+import static android.net.NetworkPolicy.SNOOZE_NEVER;
+import static android.net.NetworkPolicy.WARNING_DISABLED;
+import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
+import static android.net.NetworkPolicyManager.POLICY_NONE;
+import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import static android.net.NetworkPolicyManager.computeLastCycleBoundary;
+import static android.net.NetworkPolicyManager.dumpPolicy;
+import static android.net.NetworkPolicyManager.dumpRules;
+import static android.net.NetworkTemplate.MATCH_ETHERNET;
+import static android.net.NetworkTemplate.MATCH_MOBILE_3G_LOWER;
+import static android.net.NetworkTemplate.MATCH_MOBILE_4G;
+import static android.net.NetworkTemplate.MATCH_MOBILE_ALL;
+import static android.net.NetworkTemplate.MATCH_WIFI;
+import static android.net.NetworkTemplate.buildTemplateMobileAll;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.wifi.WifiManager.CHANGE_REASON_ADDED;
+import static android.net.wifi.WifiManager.CHANGE_REASON_REMOVED;
+import static android.net.wifi.WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION;
+import static android.net.wifi.WifiManager.EXTRA_CHANGE_REASON;
+import static android.net.wifi.WifiManager.EXTRA_NETWORK_INFO;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_CONFIGURATION;
+import static android.net.wifi.WifiManager.EXTRA_WIFI_INFO;
+import static android.telephony.TelephonyManager.SIM_STATE_READY;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static com.android.internal.util.ArrayUtils.appendInt;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.app.IActivityManager;
+import android.app.INotificationManager;
+import android.app.IProcessObserver;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.NetworkIdentity;
+import android.net.NetworkInfo;
+import android.net.NetworkPolicy;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
+import android.net.NetworkTemplate;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.MessageQueue.IdleHandler;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.format.Formatter;
+import android.text.format.Time;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+import android.util.TrustedTime;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+import com.google.android.collect.Sets;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import libcore.io.IoUtils;
+
+/**
+ * Service that maintains low-level network policy rules, using
+ * {@link NetworkStatsService} statistics to drive those rules.
+ * <p>
+ * Derives active rules by combining a given policy with other system status,
+ * and delivers to listeners, such as {@link ConnectivityManager}, for
+ * enforcement.
+ */
+public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
+    private static final String TAG = "NetworkPolicy";
+    private static final boolean LOGD = false;
+    private static final boolean LOGV = false;
+
+    private static final int VERSION_INIT = 1;
+    private static final int VERSION_ADDED_SNOOZE = 2;
+    private static final int VERSION_ADDED_RESTRICT_BACKGROUND = 3;
+    private static final int VERSION_ADDED_METERED = 4;
+    private static final int VERSION_SPLIT_SNOOZE = 5;
+    private static final int VERSION_ADDED_TIMEZONE = 6;
+    private static final int VERSION_ADDED_INFERRED = 7;
+    private static final int VERSION_SWITCH_APP_ID = 8;
+    private static final int VERSION_ADDED_NETWORK_ID = 9;
+    private static final int VERSION_SWITCH_UID = 10;
+    private static final int VERSION_LATEST = VERSION_SWITCH_UID;
+
+    @VisibleForTesting
+    public static final int TYPE_WARNING = 0x1;
+    @VisibleForTesting
+    public static final int TYPE_LIMIT = 0x2;
+    @VisibleForTesting
+    public static final int TYPE_LIMIT_SNOOZED = 0x3;
+
+    private static final String TAG_POLICY_LIST = "policy-list";
+    private static final String TAG_NETWORK_POLICY = "network-policy";
+    private static final String TAG_UID_POLICY = "uid-policy";
+    private static final String TAG_APP_POLICY = "app-policy";
+
+    private static final String ATTR_VERSION = "version";
+    private static final String ATTR_RESTRICT_BACKGROUND = "restrictBackground";
+    private static final String ATTR_NETWORK_TEMPLATE = "networkTemplate";
+    private static final String ATTR_SUBSCRIBER_ID = "subscriberId";
+    private static final String ATTR_NETWORK_ID = "networkId";
+    private static final String ATTR_CYCLE_DAY = "cycleDay";
+    private static final String ATTR_CYCLE_TIMEZONE = "cycleTimezone";
+    private static final String ATTR_WARNING_BYTES = "warningBytes";
+    private static final String ATTR_LIMIT_BYTES = "limitBytes";
+    private static final String ATTR_LAST_SNOOZE = "lastSnooze";
+    private static final String ATTR_LAST_WARNING_SNOOZE = "lastWarningSnooze";
+    private static final String ATTR_LAST_LIMIT_SNOOZE = "lastLimitSnooze";
+    private static final String ATTR_METERED = "metered";
+    private static final String ATTR_INFERRED = "inferred";
+    private static final String ATTR_UID = "uid";
+    private static final String ATTR_APP_ID = "appId";
+    private static final String ATTR_POLICY = "policy";
+
+    private static final String TAG_ALLOW_BACKGROUND = TAG + ":allowBackground";
+
+    private static final String ACTION_ALLOW_BACKGROUND =
+            "com.android.server.net.action.ALLOW_BACKGROUND";
+    private static final String ACTION_SNOOZE_WARNING =
+            "com.android.server.net.action.SNOOZE_WARNING";
+
+    private static final long TIME_CACHE_MAX_AGE = DAY_IN_MILLIS;
+
+    private static final int MSG_RULES_CHANGED = 1;
+    private static final int MSG_METERED_IFACES_CHANGED = 2;
+    private static final int MSG_FOREGROUND_ACTIVITIES_CHANGED = 3;
+    private static final int MSG_PROCESS_DIED = 4;
+    private static final int MSG_LIMIT_REACHED = 5;
+    private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6;
+    private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7;
+    private static final int MSG_SCREEN_ON_CHANGED = 8;
+
+    private final Context mContext;
+    private final IActivityManager mActivityManager;
+    private final IPowerManager mPowerManager;
+    private final INetworkStatsService mNetworkStats;
+    private final INetworkManagementService mNetworkManager;
+    private final TrustedTime mTime;
+
+    private IConnectivityManager mConnManager;
+    private INotificationManager mNotifManager;
+
+    private final Object mRulesLock = new Object();
+
+    private volatile boolean mScreenOn;
+    private volatile boolean mRestrictBackground;
+
+    private final boolean mSuppressDefaultPolicy;
+
+    /** Defined network policies. */
+    private HashMap<NetworkTemplate, NetworkPolicy> mNetworkPolicy = Maps.newHashMap();
+    /** Currently active network rules for ifaces. */
+    private HashMap<NetworkPolicy, String[]> mNetworkRules = Maps.newHashMap();
+
+    /** Defined UID policies. */
+    private SparseIntArray mUidPolicy = new SparseIntArray();
+    /** Currently derived rules for each UID. */
+    private SparseIntArray mUidRules = new SparseIntArray();
+
+    /** Set of ifaces that are metered. */
+    private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+    /** Set of over-limit templates that have been notified. */
+    private HashSet<NetworkTemplate> mOverLimitNotified = Sets.newHashSet();
+
+    /** Set of currently active {@link Notification} tags. */
+    private HashSet<String> mActiveNotifs = Sets.newHashSet();
+
+    /** Foreground at both UID and PID granularity. */
+    private SparseBooleanArray mUidForeground = new SparseBooleanArray();
+    private SparseArray<SparseBooleanArray> mUidPidForeground = new SparseArray<
+            SparseBooleanArray>();
+
+    private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList<
+            INetworkPolicyListener>();
+
+    private final Handler mHandler;
+
+    private final AtomicFile mPolicyFile;
+
+    // TODO: keep whitelist of system-critical services that should never have
+    // rules enforced, such as system, phone, and radio UIDs.
+
+    // TODO: migrate notifications to SystemUI
+
+    public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+            IPowerManager powerManager, INetworkStatsService networkStats,
+            INetworkManagementService networkManagement) {
+        this(context, activityManager, powerManager, networkStats, networkManagement,
+                NtpTrustedTime.getInstance(context), getSystemDir(), false);
+    }
+
+    private static File getSystemDir() {
+        return new File(Environment.getDataDirectory(), "system");
+    }
+
+    public NetworkPolicyManagerService(Context context, IActivityManager activityManager,
+            IPowerManager powerManager, INetworkStatsService networkStats,
+            INetworkManagementService networkManagement, TrustedTime time, File systemDir,
+            boolean suppressDefaultPolicy) {
+        mContext = checkNotNull(context, "missing context");
+        mActivityManager = checkNotNull(activityManager, "missing activityManager");
+        mPowerManager = checkNotNull(powerManager, "missing powerManager");
+        mNetworkStats = checkNotNull(networkStats, "missing networkStats");
+        mNetworkManager = checkNotNull(networkManagement, "missing networkManagement");
+        mTime = checkNotNull(time, "missing TrustedTime");
+
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mHandler = new Handler(thread.getLooper(), mHandlerCallback);
+
+        mSuppressDefaultPolicy = suppressDefaultPolicy;
+
+        mPolicyFile = new AtomicFile(new File(systemDir, "netpolicy.xml"));
+    }
+
+    public void bindConnectivityManager(IConnectivityManager connManager) {
+        mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
+    }
+
+    public void bindNotificationManager(INotificationManager notifManager) {
+        mNotifManager = checkNotNull(notifManager, "missing INotificationManager");
+    }
+
+    public void systemReady() {
+        if (!isBandwidthControlEnabled()) {
+            Slog.w(TAG, "bandwidth controls disabled, unable to enforce policy");
+            return;
+        }
+
+        synchronized (mRulesLock) {
+            // read policy from disk
+            readPolicyLocked();
+
+            if (mRestrictBackground) {
+                updateRulesForRestrictBackgroundLocked();
+                updateNotificationsLocked();
+            }
+        }
+
+        updateScreenOn();
+
+        try {
+            mActivityManager.registerProcessObserver(mProcessObserver);
+            mNetworkManager.registerObserver(mAlertObserver);
+        } catch (RemoteException e) {
+            // ignored; both services live in system_server
+        }
+
+        // TODO: traverse existing processes to know foreground state, or have
+        // activitymanager dispatch current state when new observer attached.
+
+        final IntentFilter screenFilter = new IntentFilter();
+        screenFilter.addAction(Intent.ACTION_SCREEN_ON);
+        screenFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(mScreenReceiver, screenFilter);
+
+        // watch for network interfaces to be claimed
+        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
+        mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+        // listen for package changes to update policy
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(ACTION_PACKAGE_ADDED);
+        packageFilter.addDataScheme("package");
+        mContext.registerReceiver(mPackageReceiver, packageFilter, null, mHandler);
+
+        // listen for UID changes to update policy
+        mContext.registerReceiver(
+                mUidRemovedReceiver, new IntentFilter(ACTION_UID_REMOVED), null, mHandler);
+
+        // listen for user changes to update policy
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(ACTION_USER_ADDED);
+        userFilter.addAction(ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+        // listen for stats update events
+        final IntentFilter statsFilter = new IntentFilter(ACTION_NETWORK_STATS_UPDATED);
+        mContext.registerReceiver(
+                mStatsReceiver, statsFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+
+        // listen for restrict background changes from notifications
+        final IntentFilter allowFilter = new IntentFilter(ACTION_ALLOW_BACKGROUND);
+        mContext.registerReceiver(mAllowReceiver, allowFilter, MANAGE_NETWORK_POLICY, mHandler);
+
+        // listen for snooze warning from notifications
+        final IntentFilter snoozeWarningFilter = new IntentFilter(ACTION_SNOOZE_WARNING);
+        mContext.registerReceiver(mSnoozeWarningReceiver, snoozeWarningFilter,
+                MANAGE_NETWORK_POLICY, mHandler);
+
+        // listen for configured wifi networks to be removed
+        final IntentFilter wifiConfigFilter = new IntentFilter(CONFIGURED_NETWORKS_CHANGED_ACTION);
+        mContext.registerReceiver(
+                mWifiConfigReceiver, wifiConfigFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+        // listen for wifi state changes to catch metered hint
+        final IntentFilter wifiStateFilter = new IntentFilter(
+                WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(
+                mWifiStateReceiver, wifiStateFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+    }
+
+    private IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
+        @Override
+        public void onForegroundActivitiesChanged(int pid, int uid, boolean foregroundActivities) {
+            mHandler.obtainMessage(MSG_FOREGROUND_ACTIVITIES_CHANGED,
+                    pid, uid, foregroundActivities).sendToTarget();
+        }
+
+        @Override
+        public void onImportanceChanged(int pid, int uid, int importance) {
+        }
+
+        @Override
+        public void onProcessDied(int pid, int uid) {
+            mHandler.obtainMessage(MSG_PROCESS_DIED, pid, uid).sendToTarget();
+        }
+    };
+
+    private BroadcastReceiver mScreenReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // screen-related broadcasts are protected by system, no need
+            // for permissions check.
+            mHandler.obtainMessage(MSG_SCREEN_ON_CHANGED).sendToTarget();
+        }
+    };
+
+    private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and PACKAGE_ADDED is protected
+
+            final String action = intent.getAction();
+            final int uid = intent.getIntExtra(EXTRA_UID, -1);
+            if (uid == -1) return;
+
+            if (ACTION_PACKAGE_ADDED.equals(action)) {
+                // update rules for UID, since it might be subject to
+                // global background data policy
+                if (LOGV) Slog.v(TAG, "ACTION_PACKAGE_ADDED for uid=" + uid);
+                synchronized (mRulesLock) {
+                    updateRulesForUidLocked(uid);
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mUidRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and UID_REMOVED is protected
+
+            final int uid = intent.getIntExtra(EXTRA_UID, -1);
+            if (uid == -1) return;
+
+            // remove any policy and update rules to clean up
+            if (LOGV) Slog.v(TAG, "ACTION_UID_REMOVED for uid=" + uid);
+            synchronized (mRulesLock) {
+                mUidPolicy.delete(uid);
+                updateRulesForUidLocked(uid);
+                writePolicyLocked();
+            }
+        }
+    };
+
+    private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and USER_ADDED and USER_REMOVED
+            // broadcasts are protected
+
+            final String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+
+            // Remove any policies for given user; both cleaning up after a
+            // USER_REMOVED, and one last sanity check during USER_ADDED
+            removePoliciesForUserLocked(userId);
+
+            // Update global restrict for new user
+            synchronized (mRulesLock) {
+                updateRulesForRestrictBackgroundLocked();
+            }
+        }
+    };
+
+    /**
+     * Receiver that watches for {@link INetworkStatsService} updates, which we
+     * use to check against {@link NetworkPolicy#warningBytes}.
+     */
+    private BroadcastReceiver mStatsReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified
+            // READ_NETWORK_USAGE_HISTORY permission above.
+
+            maybeRefreshTrustedTime();
+            synchronized (mRulesLock) {
+                updateNetworkEnabledLocked();
+                updateNotificationsLocked();
+            }
+        }
+    };
+
+    /**
+     * Receiver that watches for {@link Notification} control of
+     * {@link #mRestrictBackground}.
+     */
+    private BroadcastReceiver mAllowReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified MANAGE_NETWORK_POLICY
+            // permission above.
+
+            setRestrictBackground(false);
+        }
+    };
+
+    /**
+     * Receiver that watches for {@link Notification} control of
+     * {@link NetworkPolicy#lastWarningSnooze}.
+     */
+    private BroadcastReceiver mSnoozeWarningReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified MANAGE_NETWORK_POLICY
+            // permission above.
+
+            final NetworkTemplate template = intent.getParcelableExtra(EXTRA_NETWORK_TEMPLATE);
+            performSnooze(template, TYPE_WARNING);
+        }
+    };
+
+    /**
+     * Receiver that watches for {@link WifiConfiguration} to be changed.
+     */
+    private BroadcastReceiver mWifiConfigReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+
+            final int reason = intent.getIntExtra(EXTRA_CHANGE_REASON, CHANGE_REASON_ADDED);
+            if (reason == CHANGE_REASON_REMOVED) {
+                final WifiConfiguration config = intent.getParcelableExtra(
+                        EXTRA_WIFI_CONFIGURATION);
+                if (config.SSID != null) {
+                    final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(config.SSID);
+                    synchronized (mRulesLock) {
+                        if (mNetworkPolicy.containsKey(template)) {
+                            mNetworkPolicy.remove(template);
+                            writePolicyLocked();
+                        }
+                    }
+                }
+            }
+        }
+    };
+
+    /**
+     * Receiver that watches {@link WifiInfo} state changes to infer metered
+     * state. Ignores hints when policy is user-defined.
+     */
+    private BroadcastReceiver mWifiStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+
+            // ignore when not connected
+            final NetworkInfo netInfo = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
+            if (!netInfo.isConnected()) return;
+
+            final WifiInfo info = intent.getParcelableExtra(EXTRA_WIFI_INFO);
+            final boolean meteredHint = info.getMeteredHint();
+
+            final NetworkTemplate template = NetworkTemplate.buildTemplateWifi(info.getSSID());
+            synchronized (mRulesLock) {
+                NetworkPolicy policy = mNetworkPolicy.get(template);
+                if (policy == null && meteredHint) {
+                    // policy doesn't exist, and AP is hinting that it's
+                    // metered: create an inferred policy.
+                    policy = new NetworkPolicy(template, CYCLE_NONE, Time.TIMEZONE_UTC,
+                            WARNING_DISABLED, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER,
+                            meteredHint, true);
+                    addNetworkPolicyLocked(policy);
+
+                } else if (policy != null && policy.inferred) {
+                    // policy exists, and was inferred: update its current
+                    // metered state.
+                    policy.metered = meteredHint;
+
+                    // since this is inferred for each wifi session, just update
+                    // rules without persisting.
+                    updateNetworkRulesLocked();
+                }
+            }
+        }
+    };
+
+    /**
+     * Observer that watches for {@link INetworkManagementService} alerts.
+     */
+    private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+        @Override
+        public void limitReached(String limitName, String iface) {
+            // only someone like NMS should be calling us
+            mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+            if (!LIMIT_GLOBAL_ALERT.equals(limitName)) {
+                mHandler.obtainMessage(MSG_LIMIT_REACHED, iface).sendToTarget();
+            }
+        }
+    };
+
+    /**
+     * Check {@link NetworkPolicy} against current {@link INetworkStatsService}
+     * to show visible notifications as needed.
+     */
+    private void updateNotificationsLocked() {
+        if (LOGV) Slog.v(TAG, "updateNotificationsLocked()");
+
+        // keep track of previously active notifications
+        final HashSet<String> beforeNotifs = Sets.newHashSet();
+        beforeNotifs.addAll(mActiveNotifs);
+        mActiveNotifs.clear();
+
+        // TODO: when switching to kernel notifications, compute next future
+        // cycle boundary to recompute notifications.
+
+        // examine stats for each active policy
+        final long currentTime = currentTimeMillis();
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            // ignore policies that aren't relevant to user
+            if (!isTemplateRelevant(policy.template)) continue;
+            if (!policy.hasCycle()) continue;
+
+            final long start = computeLastCycleBoundary(currentTime, policy);
+            final long end = currentTime;
+            final long totalBytes = getTotalBytes(policy.template, start, end);
+
+            if (policy.isOverLimit(totalBytes)) {
+                if (policy.lastLimitSnooze >= start) {
+                    enqueueNotification(policy, TYPE_LIMIT_SNOOZED, totalBytes);
+                } else {
+                    enqueueNotification(policy, TYPE_LIMIT, totalBytes);
+                    notifyOverLimitLocked(policy.template);
+                }
+
+            } else {
+                notifyUnderLimitLocked(policy.template);
+
+                if (policy.isOverWarning(totalBytes) && policy.lastWarningSnooze < start) {
+                    enqueueNotification(policy, TYPE_WARNING, totalBytes);
+                }
+            }
+        }
+
+        // ongoing notification when restricting background data
+        if (mRestrictBackground) {
+            enqueueRestrictedNotification(TAG_ALLOW_BACKGROUND);
+        }
+
+        // cancel stale notifications that we didn't renew above
+        for (String tag : beforeNotifs) {
+            if (!mActiveNotifs.contains(tag)) {
+                cancelNotification(tag);
+            }
+        }
+    }
+
+    /**
+     * Test if given {@link NetworkTemplate} is relevant to user based on
+     * current device state, such as when
+     * {@link TelephonyManager#getSubscriberId()} matches. This is regardless of
+     * data connection status.
+     */
+    private boolean isTemplateRelevant(NetworkTemplate template) {
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
+        switch (template.getMatchRule()) {
+            case MATCH_MOBILE_3G_LOWER:
+            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE_ALL:
+                // mobile templates are relevant when SIM is ready and
+                // subscriberId matches.
+                if (tele.getSimState() == SIM_STATE_READY) {
+                    return Objects.equals(tele.getSubscriberId(), template.getSubscriberId());
+                } else {
+                    return false;
+                }
+        }
+        return true;
+    }
+
+    /**
+     * Notify that given {@link NetworkTemplate} is over
+     * {@link NetworkPolicy#limitBytes}, potentially showing dialog to user.
+     */
+    private void notifyOverLimitLocked(NetworkTemplate template) {
+        if (!mOverLimitNotified.contains(template)) {
+            mContext.startActivity(buildNetworkOverLimitIntent(template));
+            mOverLimitNotified.add(template);
+        }
+    }
+
+    private void notifyUnderLimitLocked(NetworkTemplate template) {
+        mOverLimitNotified.remove(template);
+    }
+
+    /**
+     * Build unique tag that identifies an active {@link NetworkPolicy}
+     * notification of a specific type, like {@link #TYPE_LIMIT}.
+     */
+    private String buildNotificationTag(NetworkPolicy policy, int type) {
+        return TAG + ":" + policy.template.hashCode() + ":" + type;
+    }
+
+    /**
+     * Show notification for combined {@link NetworkPolicy} and specific type,
+     * like {@link #TYPE_LIMIT}. Okay to call multiple times.
+     */
+    private void enqueueNotification(NetworkPolicy policy, int type, long totalBytes) {
+        final String tag = buildNotificationTag(policy, type);
+        final Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setOnlyAlertOnce(true);
+        builder.setWhen(0L);
+
+        final Resources res = mContext.getResources();
+        switch (type) {
+            case TYPE_WARNING: {
+                final CharSequence title = res.getText(R.string.data_usage_warning_title);
+                final CharSequence body = res.getString(R.string.data_usage_warning_body);
+
+                builder.setSmallIcon(R.drawable.stat_notify_error);
+                builder.setTicker(title);
+                builder.setContentTitle(title);
+                builder.setContentText(body);
+
+                final Intent snoozeIntent = buildSnoozeWarningIntent(policy.template);
+                builder.setDeleteIntent(PendingIntent.getBroadcast(
+                        mContext, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+                final Intent viewIntent = buildViewDataUsageIntent(policy.template);
+                builder.setContentIntent(PendingIntent.getActivity(
+                        mContext, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+                break;
+            }
+            case TYPE_LIMIT: {
+                final CharSequence body = res.getText(R.string.data_usage_limit_body);
+
+                final CharSequence title;
+                switch (policy.template.getMatchRule()) {
+                    case MATCH_MOBILE_3G_LOWER:
+                        title = res.getText(R.string.data_usage_3g_limit_title);
+                        break;
+                    case MATCH_MOBILE_4G:
+                        title = res.getText(R.string.data_usage_4g_limit_title);
+                        break;
+                    case MATCH_MOBILE_ALL:
+                        title = res.getText(R.string.data_usage_mobile_limit_title);
+                        break;
+                    case MATCH_WIFI:
+                        title = res.getText(R.string.data_usage_wifi_limit_title);
+                        break;
+                    default:
+                        title = null;
+                        break;
+                }
+
+                builder.setOngoing(true);
+                builder.setSmallIcon(R.drawable.stat_notify_disabled);
+                builder.setTicker(title);
+                builder.setContentTitle(title);
+                builder.setContentText(body);
+
+                final Intent intent = buildNetworkOverLimitIntent(policy.template);
+                builder.setContentIntent(PendingIntent.getActivity(
+                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+                break;
+            }
+            case TYPE_LIMIT_SNOOZED: {
+                final long overBytes = totalBytes - policy.limitBytes;
+                final CharSequence body = res.getString(R.string.data_usage_limit_snoozed_body,
+                        Formatter.formatFileSize(mContext, overBytes));
+
+                final CharSequence title;
+                switch (policy.template.getMatchRule()) {
+                    case MATCH_MOBILE_3G_LOWER:
+                        title = res.getText(R.string.data_usage_3g_limit_snoozed_title);
+                        break;
+                    case MATCH_MOBILE_4G:
+                        title = res.getText(R.string.data_usage_4g_limit_snoozed_title);
+                        break;
+                    case MATCH_MOBILE_ALL:
+                        title = res.getText(R.string.data_usage_mobile_limit_snoozed_title);
+                        break;
+                    case MATCH_WIFI:
+                        title = res.getText(R.string.data_usage_wifi_limit_snoozed_title);
+                        break;
+                    default:
+                        title = null;
+                        break;
+                }
+
+                builder.setOngoing(true);
+                builder.setSmallIcon(R.drawable.stat_notify_error);
+                builder.setTicker(title);
+                builder.setContentTitle(title);
+                builder.setContentText(body);
+
+                final Intent intent = buildViewDataUsageIntent(policy.template);
+                builder.setContentIntent(PendingIntent.getActivity(
+                        mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+                break;
+            }
+        }
+
+        // TODO: move to NotificationManager once we can mock it
+        // XXX what to do about multi-user?
+        try {
+            final String packageName = mContext.getPackageName();
+            final int[] idReceived = new int[1];
+            mNotifManager.enqueueNotificationWithTag(
+                    packageName, packageName, tag, 0x0, builder.getNotification(), idReceived,
+                    UserHandle.USER_OWNER);
+            mActiveNotifs.add(tag);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    /**
+     * Show ongoing notification to reflect that {@link #mRestrictBackground}
+     * has been enabled.
+     */
+    private void enqueueRestrictedNotification(String tag) {
+        final Resources res = mContext.getResources();
+        final Notification.Builder builder = new Notification.Builder(mContext);
+
+        final CharSequence title = res.getText(R.string.data_usage_restricted_title);
+        final CharSequence body = res.getString(R.string.data_usage_restricted_body);
+
+        builder.setOnlyAlertOnce(true);
+        builder.setOngoing(true);
+        builder.setSmallIcon(R.drawable.stat_notify_error);
+        builder.setTicker(title);
+        builder.setContentTitle(title);
+        builder.setContentText(body);
+
+        final Intent intent = buildAllowBackgroundDataIntent();
+        builder.setContentIntent(
+                PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+
+        // TODO: move to NotificationManager once we can mock it
+        // XXX what to do about multi-user?
+        try {
+            final String packageName = mContext.getPackageName();
+            final int[] idReceived = new int[1];
+            mNotifManager.enqueueNotificationWithTag(packageName, packageName, tag,
+                    0x0, builder.getNotification(), idReceived, UserHandle.USER_OWNER);
+            mActiveNotifs.add(tag);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private void cancelNotification(String tag) {
+        // TODO: move to NotificationManager once we can mock it
+        // XXX what to do about multi-user?
+        try {
+            final String packageName = mContext.getPackageName();
+            mNotifManager.cancelNotificationWithTag(
+                    packageName, tag, 0x0, UserHandle.USER_OWNER);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    /**
+     * Receiver that watches for {@link IConnectivityManager} to claim network
+     * interfaces. Used to apply {@link NetworkPolicy} to matching networks.
+     */
+    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+
+            maybeRefreshTrustedTime();
+            synchronized (mRulesLock) {
+                ensureActiveMobilePolicyLocked();
+                updateNetworkEnabledLocked();
+                updateNetworkRulesLocked();
+                updateNotificationsLocked();
+            }
+        }
+    };
+
+    /**
+     * Proactively control network data connections when they exceed
+     * {@link NetworkPolicy#limitBytes}.
+     */
+    private void updateNetworkEnabledLocked() {
+        if (LOGV) Slog.v(TAG, "updateNetworkEnabledLocked()");
+
+        // TODO: reset any policy-disabled networks when any policy is removed
+        // completely, which is currently rare case.
+
+        final long currentTime = currentTimeMillis();
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            // shortcut when policy has no limit
+            if (policy.limitBytes == LIMIT_DISABLED || !policy.hasCycle()) {
+                setNetworkTemplateEnabled(policy.template, true);
+                continue;
+            }
+
+            final long start = computeLastCycleBoundary(currentTime, policy);
+            final long end = currentTime;
+            final long totalBytes = getTotalBytes(policy.template, start, end);
+
+            // disable data connection when over limit and not snoozed
+            final boolean overLimitWithoutSnooze = policy.isOverLimit(totalBytes)
+                    && policy.lastLimitSnooze < start;
+            final boolean networkEnabled = !overLimitWithoutSnooze;
+
+            setNetworkTemplateEnabled(policy.template, networkEnabled);
+        }
+    }
+
+    /**
+     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)}
+     * for the given {@link NetworkTemplate}.
+     */
+    private void setNetworkTemplateEnabled(NetworkTemplate template, boolean enabled) {
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
+        switch (template.getMatchRule()) {
+            case MATCH_MOBILE_3G_LOWER:
+            case MATCH_MOBILE_4G:
+            case MATCH_MOBILE_ALL:
+                // TODO: offer more granular control over radio states once
+                // 4965893 is available.
+                if (tele.getSimState() == SIM_STATE_READY
+                        && Objects.equals(tele.getSubscriberId(), template.getSubscriberId())) {
+                    setPolicyDataEnable(TYPE_MOBILE, enabled);
+                    setPolicyDataEnable(TYPE_WIMAX, enabled);
+                }
+                break;
+            case MATCH_WIFI:
+                setPolicyDataEnable(TYPE_WIFI, enabled);
+                break;
+            case MATCH_ETHERNET:
+                setPolicyDataEnable(TYPE_ETHERNET, enabled);
+                break;
+            default:
+                throw new IllegalArgumentException("unexpected template");
+        }
+    }
+
+    /**
+     * Examine all connected {@link NetworkState}, looking for
+     * {@link NetworkPolicy} that need to be enforced. When matches found, set
+     * remaining quota based on usage cycle and historical stats.
+     */
+    private void updateNetworkRulesLocked() {
+        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
+
+        final NetworkState[] states;
+        try {
+            states = mConnManager.getAllNetworkState();
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return;
+        }
+
+        // first, derive identity for all connected networks, which can be used
+        // to match against templates.
+        final HashMap<NetworkIdentity, String> networks = Maps.newHashMap();
+        for (NetworkState state : states) {
+            // stash identity and iface away for later use
+            if (state.networkInfo.isConnected()) {
+                final String iface = state.linkProperties.getInterfaceName();
+                final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+                networks.put(ident, iface);
+            }
+        }
+
+        // build list of rules and ifaces to enforce them against
+        mNetworkRules.clear();
+        final ArrayList<String> ifaceList = Lists.newArrayList();
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+
+            // collect all active ifaces that match this template
+            ifaceList.clear();
+            for (Map.Entry<NetworkIdentity, String> entry : networks.entrySet()) {
+                final NetworkIdentity ident = entry.getKey();
+                if (policy.template.matches(ident)) {
+                    final String iface = entry.getValue();
+                    ifaceList.add(iface);
+                }
+            }
+
+            if (ifaceList.size() > 0) {
+                final String[] ifaces = ifaceList.toArray(new String[ifaceList.size()]);
+                mNetworkRules.put(policy, ifaces);
+            }
+        }
+
+        long lowestRule = Long.MAX_VALUE;
+        final HashSet<String> newMeteredIfaces = Sets.newHashSet();
+
+        // apply each policy that we found ifaces for; compute remaining data
+        // based on current cycle and historical stats, and push to kernel.
+        final long currentTime = currentTimeMillis();
+        for (NetworkPolicy policy : mNetworkRules.keySet()) {
+            final String[] ifaces = mNetworkRules.get(policy);
+
+            final long start;
+            final long totalBytes;
+            if (policy.hasCycle()) {
+                start = computeLastCycleBoundary(currentTime, policy);
+                totalBytes = getTotalBytes(policy.template, start, currentTime);
+            } else {
+                start = Long.MAX_VALUE;
+                totalBytes = 0;
+            }
+
+            if (LOGD) {
+                Slog.d(TAG, "applying policy " + policy.toString() + " to ifaces "
+                        + Arrays.toString(ifaces));
+            }
+
+            final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
+            final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
+            if (hasLimit || policy.metered) {
+                final long quotaBytes;
+                if (!hasLimit) {
+                    // metered network, but no policy limit; we still need to
+                    // restrict apps, so push really high quota.
+                    quotaBytes = Long.MAX_VALUE;
+                } else if (policy.lastLimitSnooze >= start) {
+                    // snoozing past quota, but we still need to restrict apps,
+                    // so push really high quota.
+                    quotaBytes = Long.MAX_VALUE;
+                } else {
+                    // remaining "quota" bytes are based on total usage in
+                    // current cycle. kernel doesn't like 0-byte rules, so we
+                    // set 1-byte quota and disable the radio later.
+                    quotaBytes = Math.max(1, policy.limitBytes - totalBytes);
+                }
+
+                if (ifaces.length > 1) {
+                    // TODO: switch to shared quota once NMS supports
+                    Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
+                }
+
+                for (String iface : ifaces) {
+                    removeInterfaceQuota(iface);
+                    setInterfaceQuota(iface, quotaBytes);
+                    newMeteredIfaces.add(iface);
+                }
+            }
+
+            // keep track of lowest warning or limit of active policies
+            if (hasWarning && policy.warningBytes < lowestRule) {
+                lowestRule = policy.warningBytes;
+            }
+            if (hasLimit && policy.limitBytes < lowestRule) {
+                lowestRule = policy.limitBytes;
+            }
+        }
+
+        mHandler.obtainMessage(MSG_ADVISE_PERSIST_THRESHOLD, lowestRule).sendToTarget();
+
+        // remove quota on any trailing interfaces
+        for (String iface : mMeteredIfaces) {
+            if (!newMeteredIfaces.contains(iface)) {
+                removeInterfaceQuota(iface);
+            }
+        }
+        mMeteredIfaces = newMeteredIfaces;
+
+        final String[] meteredIfaces = mMeteredIfaces.toArray(new String[mMeteredIfaces.size()]);
+        mHandler.obtainMessage(MSG_METERED_IFACES_CHANGED, meteredIfaces).sendToTarget();
+    }
+
+    /**
+     * Once any {@link #mNetworkPolicy} are loaded from disk, ensure that we
+     * have at least a default mobile policy defined.
+     */
+    private void ensureActiveMobilePolicyLocked() {
+        if (LOGV) Slog.v(TAG, "ensureActiveMobilePolicyLocked()");
+        if (mSuppressDefaultPolicy) return;
+
+        final TelephonyManager tele = TelephonyManager.from(mContext);
+
+        // avoid creating policy when SIM isn't ready
+        if (tele.getSimState() != SIM_STATE_READY) return;
+
+        final String subscriberId = tele.getSubscriberId();
+        final NetworkIdentity probeIdent = new NetworkIdentity(
+                TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN, subscriberId, null, false);
+
+        // examine to see if any policy is defined for active mobile
+        boolean mobileDefined = false;
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            if (policy.template.matches(probeIdent)) {
+                mobileDefined = true;
+            }
+        }
+
+        if (!mobileDefined) {
+            Slog.i(TAG, "no policy for active mobile network; generating default policy");
+
+            // build default mobile policy, and assume usage cycle starts today
+            final long warningBytes = mContext.getResources().getInteger(
+                    com.android.internal.R.integer.config_networkPolicyDefaultWarning)
+                    * MB_IN_BYTES;
+
+            final Time time = new Time();
+            time.setToNow();
+
+            final int cycleDay = time.monthDay;
+            final String cycleTimezone = time.timezone;
+
+            final NetworkTemplate template = buildTemplateMobileAll(subscriberId);
+            final NetworkPolicy policy = new NetworkPolicy(template, cycleDay, cycleTimezone,
+                    warningBytes, LIMIT_DISABLED, SNOOZE_NEVER, SNOOZE_NEVER, true, true);
+            addNetworkPolicyLocked(policy);
+        }
+    }
+
+    private void readPolicyLocked() {
+        if (LOGV) Slog.v(TAG, "readPolicyLocked()");
+
+        // clear any existing policy and read from disk
+        mNetworkPolicy.clear();
+        mUidPolicy.clear();
+
+        FileInputStream fis = null;
+        try {
+            fis = mPolicyFile.openRead();
+            final XmlPullParser in = Xml.newPullParser();
+            in.setInput(fis, null);
+
+            int type;
+            int version = VERSION_INIT;
+            while ((type = in.next()) != END_DOCUMENT) {
+                final String tag = in.getName();
+                if (type == START_TAG) {
+                    if (TAG_POLICY_LIST.equals(tag)) {
+                        version = readIntAttribute(in, ATTR_VERSION);
+                        if (version >= VERSION_ADDED_RESTRICT_BACKGROUND) {
+                            mRestrictBackground = readBooleanAttribute(
+                                    in, ATTR_RESTRICT_BACKGROUND);
+                        } else {
+                            mRestrictBackground = false;
+                        }
+
+                    } else if (TAG_NETWORK_POLICY.equals(tag)) {
+                        final int networkTemplate = readIntAttribute(in, ATTR_NETWORK_TEMPLATE);
+                        final String subscriberId = in.getAttributeValue(null, ATTR_SUBSCRIBER_ID);
+                        final String networkId;
+                        if (version >= VERSION_ADDED_NETWORK_ID) {
+                            networkId = in.getAttributeValue(null, ATTR_NETWORK_ID);
+                        } else {
+                            networkId = null;
+                        }
+                        final int cycleDay = readIntAttribute(in, ATTR_CYCLE_DAY);
+                        final String cycleTimezone;
+                        if (version >= VERSION_ADDED_TIMEZONE) {
+                            cycleTimezone = in.getAttributeValue(null, ATTR_CYCLE_TIMEZONE);
+                        } else {
+                            cycleTimezone = Time.TIMEZONE_UTC;
+                        }
+                        final long warningBytes = readLongAttribute(in, ATTR_WARNING_BYTES);
+                        final long limitBytes = readLongAttribute(in, ATTR_LIMIT_BYTES);
+                        final long lastLimitSnooze;
+                        if (version >= VERSION_SPLIT_SNOOZE) {
+                            lastLimitSnooze = readLongAttribute(in, ATTR_LAST_LIMIT_SNOOZE);
+                        } else if (version >= VERSION_ADDED_SNOOZE) {
+                            lastLimitSnooze = readLongAttribute(in, ATTR_LAST_SNOOZE);
+                        } else {
+                            lastLimitSnooze = SNOOZE_NEVER;
+                        }
+                        final boolean metered;
+                        if (version >= VERSION_ADDED_METERED) {
+                            metered = readBooleanAttribute(in, ATTR_METERED);
+                        } else {
+                            switch (networkTemplate) {
+                                case MATCH_MOBILE_3G_LOWER:
+                                case MATCH_MOBILE_4G:
+                                case MATCH_MOBILE_ALL:
+                                    metered = true;
+                                    break;
+                                default:
+                                    metered = false;
+                            }
+                        }
+                        final long lastWarningSnooze;
+                        if (version >= VERSION_SPLIT_SNOOZE) {
+                            lastWarningSnooze = readLongAttribute(in, ATTR_LAST_WARNING_SNOOZE);
+                        } else {
+                            lastWarningSnooze = SNOOZE_NEVER;
+                        }
+                        final boolean inferred;
+                        if (version >= VERSION_ADDED_INFERRED) {
+                            inferred = readBooleanAttribute(in, ATTR_INFERRED);
+                        } else {
+                            inferred = false;
+                        }
+
+                        final NetworkTemplate template = new NetworkTemplate(
+                                networkTemplate, subscriberId, networkId);
+                        mNetworkPolicy.put(template, new NetworkPolicy(template, cycleDay,
+                                cycleTimezone, warningBytes, limitBytes, lastWarningSnooze,
+                                lastLimitSnooze, metered, inferred));
+
+                    } else if (TAG_UID_POLICY.equals(tag)) {
+                        final int uid = readIntAttribute(in, ATTR_UID);
+                        final int policy = readIntAttribute(in, ATTR_POLICY);
+
+                        if (UserHandle.isApp(uid)) {
+                            setUidPolicyUnchecked(uid, policy, false);
+                        } else {
+                            Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
+                        }
+                    } else if (TAG_APP_POLICY.equals(tag)) {
+                        final int appId = readIntAttribute(in, ATTR_APP_ID);
+                        final int policy = readIntAttribute(in, ATTR_POLICY);
+
+                        // TODO: set for other users during upgrade
+                        final int uid = UserHandle.getUid(UserHandle.USER_OWNER, appId);
+                        if (UserHandle.isApp(uid)) {
+                            setUidPolicyUnchecked(uid, policy, false);
+                        } else {
+                            Slog.w(TAG, "unable to apply policy to UID " + uid + "; ignoring");
+                        }
+                    }
+                }
+            }
+
+        } catch (FileNotFoundException e) {
+            // missing policy is okay, probably first boot
+            upgradeLegacyBackgroundData();
+        } catch (IOException e) {
+            Log.wtf(TAG, "problem reading network policy", e);
+        } catch (XmlPullParserException e) {
+            Log.wtf(TAG, "problem reading network policy", e);
+        } finally {
+            IoUtils.closeQuietly(fis);
+        }
+    }
+
+    /**
+     * Upgrade legacy background data flags, notifying listeners of one last
+     * change to always-true.
+     */
+    private void upgradeLegacyBackgroundData() {
+        mRestrictBackground = Settings.Secure.getInt(
+                mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) != 1;
+
+        // kick off one last broadcast if restricted
+        if (mRestrictBackground) {
+            final Intent broadcast = new Intent(
+                    ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
+            mContext.sendBroadcastAsUser(broadcast, UserHandle.ALL);
+        }
+    }
+
+    private void writePolicyLocked() {
+        if (LOGV) Slog.v(TAG, "writePolicyLocked()");
+
+        FileOutputStream fos = null;
+        try {
+            fos = mPolicyFile.startWrite();
+
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+
+            out.startTag(null, TAG_POLICY_LIST);
+            writeIntAttribute(out, ATTR_VERSION, VERSION_LATEST);
+            writeBooleanAttribute(out, ATTR_RESTRICT_BACKGROUND, mRestrictBackground);
+
+            // write all known network policies
+            for (NetworkPolicy policy : mNetworkPolicy.values()) {
+                final NetworkTemplate template = policy.template;
+
+                out.startTag(null, TAG_NETWORK_POLICY);
+                writeIntAttribute(out, ATTR_NETWORK_TEMPLATE, template.getMatchRule());
+                final String subscriberId = template.getSubscriberId();
+                if (subscriberId != null) {
+                    out.attribute(null, ATTR_SUBSCRIBER_ID, subscriberId);
+                }
+                final String networkId = template.getNetworkId();
+                if (networkId != null) {
+                    out.attribute(null, ATTR_NETWORK_ID, networkId);
+                }
+                writeIntAttribute(out, ATTR_CYCLE_DAY, policy.cycleDay);
+                out.attribute(null, ATTR_CYCLE_TIMEZONE, policy.cycleTimezone);
+                writeLongAttribute(out, ATTR_WARNING_BYTES, policy.warningBytes);
+                writeLongAttribute(out, ATTR_LIMIT_BYTES, policy.limitBytes);
+                writeLongAttribute(out, ATTR_LAST_WARNING_SNOOZE, policy.lastWarningSnooze);
+                writeLongAttribute(out, ATTR_LAST_LIMIT_SNOOZE, policy.lastLimitSnooze);
+                writeBooleanAttribute(out, ATTR_METERED, policy.metered);
+                writeBooleanAttribute(out, ATTR_INFERRED, policy.inferred);
+                out.endTag(null, TAG_NETWORK_POLICY);
+            }
+
+            // write all known uid policies
+            for (int i = 0; i < mUidPolicy.size(); i++) {
+                final int uid = mUidPolicy.keyAt(i);
+                final int policy = mUidPolicy.valueAt(i);
+
+                // skip writing empty policies
+                if (policy == POLICY_NONE) continue;
+
+                out.startTag(null, TAG_UID_POLICY);
+                writeIntAttribute(out, ATTR_UID, uid);
+                writeIntAttribute(out, ATTR_POLICY, policy);
+                out.endTag(null, TAG_UID_POLICY);
+            }
+
+            out.endTag(null, TAG_POLICY_LIST);
+            out.endDocument();
+
+            mPolicyFile.finishWrite(fos);
+        } catch (IOException e) {
+            if (fos != null) {
+                mPolicyFile.failWrite(fos);
+            }
+        }
+    }
+
+    @Override
+    public void setUidPolicy(int uid, int policy) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        if (!UserHandle.isApp(uid)) {
+            throw new IllegalArgumentException("cannot apply policy to UID " + uid);
+        }
+
+        setUidPolicyUnchecked(uid, policy, true);
+    }
+
+    private void setUidPolicyUnchecked(int uid, int policy, boolean persist) {
+        final int oldPolicy;
+        synchronized (mRulesLock) {
+            oldPolicy = getUidPolicy(uid);
+            mUidPolicy.put(uid, policy);
+
+            // uid policy changed, recompute rules and persist policy.
+            updateRulesForUidLocked(uid);
+            if (persist) {
+                writePolicyLocked();
+            }
+        }
+    }
+
+    @Override
+    public int getUidPolicy(int uid) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        synchronized (mRulesLock) {
+            return mUidPolicy.get(uid, POLICY_NONE);
+        }
+    }
+
+    @Override
+    public int[] getUidsWithPolicy(int policy) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        int[] uids = new int[0];
+        synchronized (mRulesLock) {
+            for (int i = 0; i < mUidPolicy.size(); i++) {
+                final int uid = mUidPolicy.keyAt(i);
+                final int uidPolicy = mUidPolicy.valueAt(i);
+                if (uidPolicy == policy) {
+                    uids = appendInt(uids, uid);
+                }
+            }
+        }
+        return uids;
+    }
+
+    /**
+     * Remove any policies associated with given {@link UserHandle}, persisting
+     * if any changes are made.
+     */
+    private void removePoliciesForUserLocked(int userId) {
+        if (LOGV) Slog.v(TAG, "removePoliciesForUserLocked()");
+
+        int[] uids = new int[0];
+        for (int i = 0; i < mUidPolicy.size(); i++) {
+            final int uid = mUidPolicy.keyAt(i);
+            if (UserHandle.getUserId(uid) == userId) {
+                uids = appendInt(uids, uid);
+            }
+        }
+
+        if (uids.length > 0) {
+            for (int uid : uids) {
+                mUidPolicy.delete(uid);
+                updateRulesForUidLocked(uid);
+            }
+            writePolicyLocked();
+        }
+    }
+
+    @Override
+    public void registerListener(INetworkPolicyListener listener) {
+        // TODO: create permission for observing network policy
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        mListeners.register(listener);
+
+        // TODO: consider dispatching existing rules to new listeners
+    }
+
+    @Override
+    public void unregisterListener(INetworkPolicyListener listener) {
+        // TODO: create permission for observing network policy
+        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+        mListeners.unregister(listener);
+    }
+
+    @Override
+    public void setNetworkPolicies(NetworkPolicy[] policies) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        maybeRefreshTrustedTime();
+        synchronized (mRulesLock) {
+            mNetworkPolicy.clear();
+            for (NetworkPolicy policy : policies) {
+                mNetworkPolicy.put(policy.template, policy);
+            }
+
+            updateNetworkEnabledLocked();
+            updateNetworkRulesLocked();
+            updateNotificationsLocked();
+            writePolicyLocked();
+        }
+    }
+
+    private void addNetworkPolicyLocked(NetworkPolicy policy) {
+        mNetworkPolicy.put(policy.template, policy);
+
+        updateNetworkEnabledLocked();
+        updateNetworkRulesLocked();
+        updateNotificationsLocked();
+        writePolicyLocked();
+    }
+
+    @Override
+    public NetworkPolicy[] getNetworkPolicies() {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+        mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, TAG);
+
+        synchronized (mRulesLock) {
+            return mNetworkPolicy.values().toArray(new NetworkPolicy[mNetworkPolicy.size()]);
+        }
+    }
+
+    @Override
+    public void snoozeLimit(NetworkTemplate template) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            performSnooze(template, TYPE_LIMIT);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private void performSnooze(NetworkTemplate template, int type) {
+        maybeRefreshTrustedTime();
+        final long currentTime = currentTimeMillis();
+        synchronized (mRulesLock) {
+            // find and snooze local policy that matches
+            final NetworkPolicy policy = mNetworkPolicy.get(template);
+            if (policy == null) {
+                throw new IllegalArgumentException("unable to find policy for " + template);
+            }
+
+            switch (type) {
+                case TYPE_WARNING:
+                    policy.lastWarningSnooze = currentTime;
+                    break;
+                case TYPE_LIMIT:
+                    policy.lastLimitSnooze = currentTime;
+                    break;
+                default:
+                    throw new IllegalArgumentException("unexpected type");
+            }
+
+            updateNetworkEnabledLocked();
+            updateNetworkRulesLocked();
+            updateNotificationsLocked();
+            writePolicyLocked();
+        }
+    }
+
+    @Override
+    public void setRestrictBackground(boolean restrictBackground) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        maybeRefreshTrustedTime();
+        synchronized (mRulesLock) {
+            mRestrictBackground = restrictBackground;
+            updateRulesForRestrictBackgroundLocked();
+            updateNotificationsLocked();
+            writePolicyLocked();
+        }
+
+        mHandler.obtainMessage(MSG_RESTRICT_BACKGROUND_CHANGED, restrictBackground ? 1 : 0, 0)
+                .sendToTarget();
+    }
+
+    @Override
+    public boolean getRestrictBackground() {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        synchronized (mRulesLock) {
+            return mRestrictBackground;
+        }
+    }
+
+    private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) {
+        for (NetworkPolicy policy : mNetworkPolicy.values()) {
+            if (policy.template.matches(ident)) {
+                return policy;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public NetworkQuotaInfo getNetworkQuotaInfo(NetworkState state) {
+        mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+
+        // only returns usage summary, so we don't require caller to have
+        // READ_NETWORK_USAGE_HISTORY.
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return getNetworkQuotaInfoUnchecked(state);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private NetworkQuotaInfo getNetworkQuotaInfoUnchecked(NetworkState state) {
+        final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+
+        final NetworkPolicy policy;
+        synchronized (mRulesLock) {
+            policy = findPolicyForNetworkLocked(ident);
+        }
+
+        if (policy == null || !policy.hasCycle()) {
+            // missing policy means we can't derive useful quota info
+            return null;
+        }
+
+        final long currentTime = currentTimeMillis();
+
+        // find total bytes used under policy
+        final long start = computeLastCycleBoundary(currentTime, policy);
+        final long end = currentTime;
+        final long totalBytes = getTotalBytes(policy.template, start, end);
+
+        // report soft and hard limits under policy
+        final long softLimitBytes = policy.warningBytes != WARNING_DISABLED ? policy.warningBytes
+                : NetworkQuotaInfo.NO_LIMIT;
+        final long hardLimitBytes = policy.limitBytes != LIMIT_DISABLED ? policy.limitBytes
+                : NetworkQuotaInfo.NO_LIMIT;
+
+        return new NetworkQuotaInfo(totalBytes, softLimitBytes, hardLimitBytes);
+    }
+
+    @Override
+    public boolean isNetworkMetered(NetworkState state) {
+        final NetworkIdentity ident = NetworkIdentity.buildNetworkIdentity(mContext, state);
+
+        // roaming networks are always considered metered
+        if (ident.getRoaming()) {
+            return true;
+        }
+
+        final NetworkPolicy policy;
+        synchronized (mRulesLock) {
+            policy = findPolicyForNetworkLocked(ident);
+        }
+
+        if (policy != null) {
+            return policy.metered;
+        } else {
+            final int type = state.networkInfo.getType();
+            if (isNetworkTypeMobile(type) || type == TYPE_WIMAX) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "  ");
+
+        final HashSet<String> argSet = new HashSet<String>();
+        for (String arg : args) {
+            argSet.add(arg);
+        }
+
+        synchronized (mRulesLock) {
+            if (argSet.contains("--unsnooze")) {
+                for (NetworkPolicy policy : mNetworkPolicy.values()) {
+                    policy.clearSnooze();
+                }
+
+                updateNetworkEnabledLocked();
+                updateNetworkRulesLocked();
+                updateNotificationsLocked();
+                writePolicyLocked();
+
+                fout.println("Cleared snooze timestamps");
+                return;
+            }
+
+            fout.print("Restrict background: "); fout.println(mRestrictBackground);
+            fout.println("Network policies:");
+            fout.increaseIndent();
+            for (NetworkPolicy policy : mNetworkPolicy.values()) {
+                fout.println(policy.toString());
+            }
+            fout.decreaseIndent();
+
+            fout.println("Policy for UIDs:");
+            fout.increaseIndent();
+            int size = mUidPolicy.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = mUidPolicy.keyAt(i);
+                final int policy = mUidPolicy.valueAt(i);
+                fout.print("UID=");
+                fout.print(uid);
+                fout.print(" policy=");
+                dumpPolicy(fout, policy);
+                fout.println();
+            }
+            fout.decreaseIndent();
+
+            final SparseBooleanArray knownUids = new SparseBooleanArray();
+            collectKeys(mUidForeground, knownUids);
+            collectKeys(mUidRules, knownUids);
+
+            fout.println("Status for known UIDs:");
+            fout.increaseIndent();
+            size = knownUids.size();
+            for (int i = 0; i < size; i++) {
+                final int uid = knownUids.keyAt(i);
+                fout.print("UID=");
+                fout.print(uid);
+
+                fout.print(" foreground=");
+                final int foregroundIndex = mUidPidForeground.indexOfKey(uid);
+                if (foregroundIndex < 0) {
+                    fout.print("UNKNOWN");
+                } else {
+                    dumpSparseBooleanArray(fout, mUidPidForeground.valueAt(foregroundIndex));
+                }
+
+                fout.print(" rules=");
+                final int rulesIndex = mUidRules.indexOfKey(uid);
+                if (rulesIndex < 0) {
+                    fout.print("UNKNOWN");
+                } else {
+                    dumpRules(fout, mUidRules.valueAt(rulesIndex));
+                }
+
+                fout.println();
+            }
+            fout.decreaseIndent();
+        }
+    }
+
+    @Override
+    public boolean isUidForeground(int uid) {
+        mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+        synchronized (mRulesLock) {
+            // only really in foreground when screen is also on
+            return mUidForeground.get(uid, false) && mScreenOn;
+        }
+    }
+
+    /**
+     * Foreground for PID changed; recompute foreground at UID level. If
+     * changed, will trigger {@link #updateRulesForUidLocked(int)}.
+     */
+    private void computeUidForegroundLocked(int uid) {
+        final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+
+        // current pid is dropping foreground; examine other pids
+        boolean uidForeground = false;
+        final int size = pidForeground.size();
+        for (int i = 0; i < size; i++) {
+            if (pidForeground.valueAt(i)) {
+                uidForeground = true;
+                break;
+            }
+        }
+
+        final boolean oldUidForeground = mUidForeground.get(uid, false);
+        if (oldUidForeground != uidForeground) {
+            // foreground changed, push updated rules
+            mUidForeground.put(uid, uidForeground);
+            updateRulesForUidLocked(uid);
+        }
+    }
+
+    private void updateScreenOn() {
+        synchronized (mRulesLock) {
+            try {
+                mScreenOn = mPowerManager.isScreenOn();
+            } catch (RemoteException e) {
+                // ignored; service lives in system_server
+            }
+            updateRulesForScreenLocked();
+        }
+    }
+
+    /**
+     * Update rules that might be changed by {@link #mScreenOn} value.
+     */
+    private void updateRulesForScreenLocked() {
+        // only update rules for anyone with foreground activities
+        final int size = mUidForeground.size();
+        for (int i = 0; i < size; i++) {
+            if (mUidForeground.valueAt(i)) {
+                final int uid = mUidForeground.keyAt(i);
+                updateRulesForUidLocked(uid);
+            }
+        }
+    }
+
+    /**
+     * Update rules that might be changed by {@link #mRestrictBackground} value.
+     */
+    private void updateRulesForRestrictBackgroundLocked() {
+        final PackageManager pm = mContext.getPackageManager();
+        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+        // update rules for all installed applications
+        final List<UserInfo> users = um.getUsers();
+        final List<ApplicationInfo> apps = pm.getInstalledApplications(
+                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
+
+        for (UserInfo user : users) {
+            for (ApplicationInfo app : apps) {
+                final int uid = UserHandle.getUid(user.id, app.uid);
+                updateRulesForUidLocked(uid);
+            }
+        }
+
+        // limit data usage for some internal system services
+        updateRulesForUidLocked(android.os.Process.MEDIA_UID);
+        updateRulesForUidLocked(android.os.Process.DRM_UID);
+    }
+
+    private static boolean isUidValidForRules(int uid) {
+        // allow rules on specific system services, and any apps
+        if (uid == android.os.Process.MEDIA_UID || uid == android.os.Process.DRM_UID
+                || UserHandle.isApp(uid)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private void updateRulesForUidLocked(int uid) {
+        if (!isUidValidForRules(uid)) return;
+
+        final int uidPolicy = getUidPolicy(uid);
+        final boolean uidForeground = isUidForeground(uid);
+
+        // derive active rules based on policy and active state
+        int uidRules = RULE_ALLOW_ALL;
+        if (!uidForeground && (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0) {
+            // uid in background, and policy says to block metered data
+            uidRules = RULE_REJECT_METERED;
+        }
+        if (!uidForeground && mRestrictBackground) {
+            // uid in background, and global background disabled
+            uidRules = RULE_REJECT_METERED;
+        }
+
+        // TODO: only dispatch when rules actually change
+
+        if (uidRules == RULE_ALLOW_ALL) {
+            mUidRules.delete(uid);
+        } else {
+            mUidRules.put(uid, uidRules);
+        }
+
+        final boolean rejectMetered = (uidRules & RULE_REJECT_METERED) != 0;
+        setUidNetworkRules(uid, rejectMetered);
+
+        // dispatch changed rule to existing listeners
+        mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules).sendToTarget();
+
+        try {
+            // adjust stats accounting based on foreground status
+            mNetworkStats.setUidForeground(uid, uidForeground);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_RULES_CHANGED: {
+                    final int uid = msg.arg1;
+                    final int uidRules = msg.arg2;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        if (listener != null) {
+                            try {
+                                listener.onUidRulesChanged(uid, uidRules);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
+                case MSG_METERED_IFACES_CHANGED: {
+                    final String[] meteredIfaces = (String[]) msg.obj;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        if (listener != null) {
+                            try {
+                                listener.onMeteredIfacesChanged(meteredIfaces);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
+                case MSG_FOREGROUND_ACTIVITIES_CHANGED: {
+                    final int pid = msg.arg1;
+                    final int uid = msg.arg2;
+                    final boolean foregroundActivities = (Boolean) msg.obj;
+
+                    synchronized (mRulesLock) {
+                        // because a uid can have multiple pids running inside, we need to
+                        // remember all pid states and summarize foreground at uid level.
+
+                        // record foreground for this specific pid
+                        SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+                        if (pidForeground == null) {
+                            pidForeground = new SparseBooleanArray(2);
+                            mUidPidForeground.put(uid, pidForeground);
+                        }
+                        pidForeground.put(pid, foregroundActivities);
+                        computeUidForegroundLocked(uid);
+                    }
+                    return true;
+                }
+                case MSG_PROCESS_DIED: {
+                    final int pid = msg.arg1;
+                    final int uid = msg.arg2;
+
+                    synchronized (mRulesLock) {
+                        // clear records and recompute, when they exist
+                        final SparseBooleanArray pidForeground = mUidPidForeground.get(uid);
+                        if (pidForeground != null) {
+                            pidForeground.delete(pid);
+                            computeUidForegroundLocked(uid);
+                        }
+                    }
+                    return true;
+                }
+                case MSG_LIMIT_REACHED: {
+                    final String iface = (String) msg.obj;
+
+                    maybeRefreshTrustedTime();
+                    synchronized (mRulesLock) {
+                        if (mMeteredIfaces.contains(iface)) {
+                            try {
+                                // force stats update to make sure we have
+                                // numbers that caused alert to trigger.
+                                mNetworkStats.forceUpdate();
+                            } catch (RemoteException e) {
+                                // ignored; service lives in system_server
+                            }
+
+                            updateNetworkEnabledLocked();
+                            updateNotificationsLocked();
+                        }
+                    }
+                    return true;
+                }
+                case MSG_RESTRICT_BACKGROUND_CHANGED: {
+                    final boolean restrictBackground = msg.arg1 != 0;
+                    final int length = mListeners.beginBroadcast();
+                    for (int i = 0; i < length; i++) {
+                        final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+                        if (listener != null) {
+                            try {
+                                listener.onRestrictBackgroundChanged(restrictBackground);
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                    mListeners.finishBroadcast();
+                    return true;
+                }
+                case MSG_ADVISE_PERSIST_THRESHOLD: {
+                    final long lowestRule = (Long) msg.obj;
+                    try {
+                        // make sure stats are recorded frequently enough; we aim
+                        // for 2MB threshold for 2GB/month rules.
+                        final long persistThreshold = lowestRule / 1000;
+                        mNetworkStats.advisePersistThreshold(persistThreshold);
+                    } catch (RemoteException e) {
+                        // ignored; service lives in system_server
+                    }
+                    return true;
+                }
+                case MSG_SCREEN_ON_CHANGED: {
+                    updateScreenOn();
+                    return true;
+                }
+                default: {
+                    return false;
+                }
+            }
+        }
+    };
+
+    private void setInterfaceQuota(String iface, long quotaBytes) {
+        try {
+            mNetworkManager.setInterfaceQuota(iface, quotaBytes);
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem setting interface quota", e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private void removeInterfaceQuota(String iface) {
+        try {
+            mNetworkManager.removeInterfaceQuota(iface);
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem removing interface quota", e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private void setUidNetworkRules(int uid, boolean rejectOnQuotaInterfaces) {
+        try {
+            mNetworkManager.setUidNetworkRules(uid, rejectOnQuotaInterfaces);
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem setting uid rules", e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    /**
+     * Control {@link IConnectivityManager#setPolicyDataEnable(int, boolean)}.
+     */
+    private void setPolicyDataEnable(int networkType, boolean enabled) {
+        try {
+            mConnManager.setPolicyDataEnable(networkType, enabled);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private long getTotalBytes(NetworkTemplate template, long start, long end) {
+        try {
+            return mNetworkStats.getNetworkTotalBytes(template, start, end);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "problem reading network stats: " + e);
+            return 0;
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return 0;
+        }
+    }
+
+    private boolean isBandwidthControlEnabled() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mNetworkManager.isBandwidthControlEnabled();
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Try refreshing {@link #mTime} when stale.
+     */
+    private void maybeRefreshTrustedTime() {
+        if (mTime.getCacheAge() > TIME_CACHE_MAX_AGE) {
+            mTime.forceRefresh();
+        }
+    }
+
+    private long currentTimeMillis() {
+        return mTime.hasCache() ? mTime.currentTimeMillis() : System.currentTimeMillis();
+    }
+
+    private static Intent buildAllowBackgroundDataIntent() {
+        return new Intent(ACTION_ALLOW_BACKGROUND);
+    }
+
+    private static Intent buildSnoozeWarningIntent(NetworkTemplate template) {
+        final Intent intent = new Intent(ACTION_SNOOZE_WARNING);
+        intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+        return intent;
+    }
+
+    private static Intent buildNetworkOverLimitIntent(NetworkTemplate template) {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(
+                "com.android.systemui", "com.android.systemui.net.NetworkOverLimitActivity"));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+        return intent;
+    }
+
+    private static Intent buildViewDataUsageIntent(NetworkTemplate template) {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName(
+                "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(EXTRA_NETWORK_TEMPLATE, template);
+        return intent;
+    }
+
+    @VisibleForTesting
+    public void addIdleHandler(IdleHandler handler) {
+        mHandler.getLooper().getQueue().addIdleHandler(handler);
+    }
+
+    private static void collectKeys(SparseIntArray source, SparseBooleanArray target) {
+        final int size = source.size();
+        for (int i = 0; i < size; i++) {
+            target.put(source.keyAt(i), true);
+        }
+    }
+
+    private static void collectKeys(SparseBooleanArray source, SparseBooleanArray target) {
+        final int size = source.size();
+        for (int i = 0; i < size; i++) {
+            target.put(source.keyAt(i), true);
+        }
+    }
+
+    private static void dumpSparseBooleanArray(PrintWriter fout, SparseBooleanArray value) {
+        fout.print("[");
+        final int size = value.size();
+        for (int i = 0; i < size; i++) {
+            fout.print(value.keyAt(i) + "=" + value.valueAt(i));
+            if (i < size - 1) fout.print(",");
+        }
+        fout.print("]");
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
new file mode 100644
index 0000000..475482f
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.TrafficStats.UID_REMOVED;
+
+import android.net.NetworkIdentity;
+import android.net.NetworkStats;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.text.format.DateUtils;
+import android.util.AtomicFile;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ProtocolException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import libcore.io.IoUtils;
+
+/**
+ * Collection of {@link NetworkStatsHistory}, stored based on combined key of
+ * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
+ */
+public class NetworkStatsCollection implements FileRotator.Reader {
+    /** File header magic number: "ANET" */
+    private static final int FILE_MAGIC = 0x414E4554;
+
+    private static final int VERSION_NETWORK_INIT = 1;
+
+    private static final int VERSION_UID_INIT = 1;
+    private static final int VERSION_UID_WITH_IDENT = 2;
+    private static final int VERSION_UID_WITH_TAG = 3;
+    private static final int VERSION_UID_WITH_SET = 4;
+
+    private static final int VERSION_UNIFIED_INIT = 16;
+
+    private HashMap<Key, NetworkStatsHistory> mStats = Maps.newHashMap();
+
+    private final long mBucketDuration;
+
+    private long mStartMillis;
+    private long mEndMillis;
+    private long mTotalBytes;
+    private boolean mDirty;
+
+    public NetworkStatsCollection(long bucketDuration) {
+        mBucketDuration = bucketDuration;
+        reset();
+    }
+
+    public void reset() {
+        mStats.clear();
+        mStartMillis = Long.MAX_VALUE;
+        mEndMillis = Long.MIN_VALUE;
+        mTotalBytes = 0;
+        mDirty = false;
+    }
+
+    public long getStartMillis() {
+        return mStartMillis;
+    }
+
+    /**
+     * Return first atomic bucket in this collection, which is more conservative
+     * than {@link #mStartMillis}.
+     */
+    public long getFirstAtomicBucketMillis() {
+        if (mStartMillis == Long.MAX_VALUE) {
+            return Long.MAX_VALUE;
+        } else {
+            return mStartMillis + mBucketDuration;
+        }
+    }
+
+    public long getEndMillis() {
+        return mEndMillis;
+    }
+
+    public long getTotalBytes() {
+        return mTotalBytes;
+    }
+
+    public boolean isDirty() {
+        return mDirty;
+    }
+
+    public void clearDirty() {
+        mDirty = false;
+    }
+
+    public boolean isEmpty() {
+        return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
+    }
+
+    /**
+     * Combine all {@link NetworkStatsHistory} in this collection which match
+     * the requested parameters.
+     */
+    public NetworkStatsHistory getHistory(
+            NetworkTemplate template, int uid, int set, int tag, int fields) {
+        return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE);
+    }
+
+    /**
+     * Combine all {@link NetworkStatsHistory} in this collection which match
+     * the requested parameters.
+     */
+    public NetworkStatsHistory getHistory(
+            NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
+        final NetworkStatsHistory combined = new NetworkStatsHistory(
+                mBucketDuration, estimateBuckets(), fields);
+        for (Map.Entry<Key, NetworkStatsHistory> entry : mStats.entrySet()) {
+            final Key key = entry.getKey();
+            final boolean setMatches = set == SET_ALL || key.set == set;
+            if (key.uid == uid && setMatches && key.tag == tag
+                    && templateMatches(template, key.ident)) {
+                combined.recordHistory(entry.getValue(), start, end);
+            }
+        }
+        return combined;
+    }
+
+    /**
+     * Summarize all {@link NetworkStatsHistory} in this collection which match
+     * the requested parameters.
+     */
+    public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
+        final long now = System.currentTimeMillis();
+
+        final NetworkStats stats = new NetworkStats(end - start, 24);
+        final NetworkStats.Entry entry = new NetworkStats.Entry();
+        NetworkStatsHistory.Entry historyEntry = null;
+
+        // shortcut when we know stats will be empty
+        if (start == end) return stats;
+
+        for (Map.Entry<Key, NetworkStatsHistory> mapEntry : mStats.entrySet()) {
+            final Key key = mapEntry.getKey();
+            if (templateMatches(template, key.ident)) {
+                final NetworkStatsHistory history = mapEntry.getValue();
+                historyEntry = history.getValues(start, end, now, historyEntry);
+
+                entry.iface = IFACE_ALL;
+                entry.uid = key.uid;
+                entry.set = key.set;
+                entry.tag = key.tag;
+                entry.rxBytes = historyEntry.rxBytes;
+                entry.rxPackets = historyEntry.rxPackets;
+                entry.txBytes = historyEntry.txBytes;
+                entry.txPackets = historyEntry.txPackets;
+                entry.operations = historyEntry.operations;
+
+                if (!entry.isEmpty()) {
+                    stats.combineValues(entry);
+                }
+            }
+        }
+
+        return stats;
+    }
+
+    /**
+     * Record given {@link android.net.NetworkStats.Entry} into this collection.
+     */
+    public void recordData(NetworkIdentitySet ident, int uid, int set, int tag, long start,
+            long end, NetworkStats.Entry entry) {
+        final NetworkStatsHistory history = findOrCreateHistory(ident, uid, set, tag);
+        history.recordData(start, end, entry);
+        noteRecordedHistory(history.getStart(), history.getEnd(), entry.rxBytes + entry.txBytes);
+    }
+
+    /**
+     * Record given {@link NetworkStatsHistory} into this collection.
+     */
+    private void recordHistory(Key key, NetworkStatsHistory history) {
+        if (history.size() == 0) return;
+        noteRecordedHistory(history.getStart(), history.getEnd(), history.getTotalBytes());
+
+        NetworkStatsHistory target = mStats.get(key);
+        if (target == null) {
+            target = new NetworkStatsHistory(history.getBucketDuration());
+            mStats.put(key, target);
+        }
+        target.recordEntireHistory(history);
+    }
+
+    /**
+     * Record all {@link NetworkStatsHistory} contained in the given collection
+     * into this collection.
+     */
+    public void recordCollection(NetworkStatsCollection another) {
+        for (Map.Entry<Key, NetworkStatsHistory> entry : another.mStats.entrySet()) {
+            recordHistory(entry.getKey(), entry.getValue());
+        }
+    }
+
+    private NetworkStatsHistory findOrCreateHistory(
+            NetworkIdentitySet ident, int uid, int set, int tag) {
+        final Key key = new Key(ident, uid, set, tag);
+        final NetworkStatsHistory existing = mStats.get(key);
+
+        // update when no existing, or when bucket duration changed
+        NetworkStatsHistory updated = null;
+        if (existing == null) {
+            updated = new NetworkStatsHistory(mBucketDuration, 10);
+        } else if (existing.getBucketDuration() != mBucketDuration) {
+            updated = new NetworkStatsHistory(existing, mBucketDuration);
+        }
+
+        if (updated != null) {
+            mStats.put(key, updated);
+            return updated;
+        } else {
+            return existing;
+        }
+    }
+
+    @Override
+    public void read(InputStream in) throws IOException {
+        read(new DataInputStream(in));
+    }
+
+    public void read(DataInputStream in) throws IOException {
+        // verify file magic header intact
+        final int magic = in.readInt();
+        if (magic != FILE_MAGIC) {
+            throw new ProtocolException("unexpected magic: " + magic);
+        }
+
+        final int version = in.readInt();
+        switch (version) {
+            case VERSION_UNIFIED_INIT: {
+                // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+                final int identSize = in.readInt();
+                for (int i = 0; i < identSize; i++) {
+                    final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+                    final int size = in.readInt();
+                    for (int j = 0; j < size; j++) {
+                        final int uid = in.readInt();
+                        final int set = in.readInt();
+                        final int tag = in.readInt();
+
+                        final Key key = new Key(ident, uid, set, tag);
+                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
+                        recordHistory(key, history);
+                    }
+                }
+                break;
+            }
+            default: {
+                throw new ProtocolException("unexpected version: " + version);
+            }
+        }
+    }
+
+    public void write(DataOutputStream out) throws IOException {
+        // cluster key lists grouped by ident
+        final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
+        for (Key key : mStats.keySet()) {
+            ArrayList<Key> keys = keysByIdent.get(key.ident);
+            if (keys == null) {
+                keys = Lists.newArrayList();
+                keysByIdent.put(key.ident, keys);
+            }
+            keys.add(key);
+        }
+
+        out.writeInt(FILE_MAGIC);
+        out.writeInt(VERSION_UNIFIED_INIT);
+
+        out.writeInt(keysByIdent.size());
+        for (NetworkIdentitySet ident : keysByIdent.keySet()) {
+            final ArrayList<Key> keys = keysByIdent.get(ident);
+            ident.writeToStream(out);
+
+            out.writeInt(keys.size());
+            for (Key key : keys) {
+                final NetworkStatsHistory history = mStats.get(key);
+                out.writeInt(key.uid);
+                out.writeInt(key.set);
+                out.writeInt(key.tag);
+                history.writeToStream(out);
+            }
+        }
+
+        out.flush();
+    }
+
+    @Deprecated
+    public void readLegacyNetwork(File file) throws IOException {
+        final AtomicFile inputFile = new AtomicFile(file);
+
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+            // verify file magic header intact
+            final int magic = in.readInt();
+            if (magic != FILE_MAGIC) {
+                throw new ProtocolException("unexpected magic: " + magic);
+            }
+
+            final int version = in.readInt();
+            switch (version) {
+                case VERSION_NETWORK_INIT: {
+                    // network := size *(NetworkIdentitySet NetworkStatsHistory)
+                    final int size = in.readInt();
+                    for (int i = 0; i < size; i++) {
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+                        final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+                        final Key key = new Key(ident, UID_ALL, SET_ALL, TAG_NONE);
+                        recordHistory(key, history);
+                    }
+                    break;
+                }
+                default: {
+                    throw new ProtocolException("unexpected version: " + version);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // missing stats is okay, probably first boot
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    @Deprecated
+    public void readLegacyUid(File file, boolean onlyTags) throws IOException {
+        final AtomicFile inputFile = new AtomicFile(file);
+
+        DataInputStream in = null;
+        try {
+            in = new DataInputStream(new BufferedInputStream(inputFile.openRead()));
+
+            // verify file magic header intact
+            final int magic = in.readInt();
+            if (magic != FILE_MAGIC) {
+                throw new ProtocolException("unexpected magic: " + magic);
+            }
+
+            final int version = in.readInt();
+            switch (version) {
+                case VERSION_UID_INIT: {
+                    // uid := size *(UID NetworkStatsHistory)
+
+                    // drop this data version, since we don't have a good
+                    // mapping into NetworkIdentitySet.
+                    break;
+                }
+                case VERSION_UID_WITH_IDENT: {
+                    // uid := size *(NetworkIdentitySet size *(UID NetworkStatsHistory))
+
+                    // drop this data version, since this version only existed
+                    // for a short time.
+                    break;
+                }
+                case VERSION_UID_WITH_TAG:
+                case VERSION_UID_WITH_SET: {
+                    // uid := size *(NetworkIdentitySet size *(uid set tag NetworkStatsHistory))
+                    final int identSize = in.readInt();
+                    for (int i = 0; i < identSize; i++) {
+                        final NetworkIdentitySet ident = new NetworkIdentitySet(in);
+
+                        final int size = in.readInt();
+                        for (int j = 0; j < size; j++) {
+                            final int uid = in.readInt();
+                            final int set = (version >= VERSION_UID_WITH_SET) ? in.readInt()
+                                    : SET_DEFAULT;
+                            final int tag = in.readInt();
+
+                            final Key key = new Key(ident, uid, set, tag);
+                            final NetworkStatsHistory history = new NetworkStatsHistory(in);
+
+                            if ((tag == TAG_NONE) != onlyTags) {
+                                recordHistory(key, history);
+                            }
+                        }
+                    }
+                    break;
+                }
+                default: {
+                    throw new ProtocolException("unexpected version: " + version);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // missing stats is okay, probably first boot
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    /**
+     * Remove any {@link NetworkStatsHistory} attributed to the requested UID,
+     * moving any {@link NetworkStats#TAG_NONE} series to
+     * {@link TrafficStats#UID_REMOVED}.
+     */
+    public void removeUids(int[] uids) {
+        final ArrayList<Key> knownKeys = Lists.newArrayList();
+        knownKeys.addAll(mStats.keySet());
+
+        // migrate all UID stats into special "removed" bucket
+        for (Key key : knownKeys) {
+            if (ArrayUtils.contains(uids, key.uid)) {
+                // only migrate combined TAG_NONE history
+                if (key.tag == TAG_NONE) {
+                    final NetworkStatsHistory uidHistory = mStats.get(key);
+                    final NetworkStatsHistory removedHistory = findOrCreateHistory(
+                            key.ident, UID_REMOVED, SET_DEFAULT, TAG_NONE);
+                    removedHistory.recordEntireHistory(uidHistory);
+                }
+                mStats.remove(key);
+                mDirty = true;
+            }
+        }
+    }
+
+    private void noteRecordedHistory(long startMillis, long endMillis, long totalBytes) {
+        if (startMillis < mStartMillis) mStartMillis = startMillis;
+        if (endMillis > mEndMillis) mEndMillis = endMillis;
+        mTotalBytes += totalBytes;
+        mDirty = true;
+    }
+
+    private int estimateBuckets() {
+        return (int) (Math.min(mEndMillis - mStartMillis, DateUtils.WEEK_IN_MILLIS * 5)
+                / mBucketDuration);
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        final ArrayList<Key> keys = Lists.newArrayList();
+        keys.addAll(mStats.keySet());
+        Collections.sort(keys);
+
+        for (Key key : keys) {
+            pw.print("ident="); pw.print(key.ident.toString());
+            pw.print(" uid="); pw.print(key.uid);
+            pw.print(" set="); pw.print(NetworkStats.setToString(key.set));
+            pw.print(" tag="); pw.println(NetworkStats.tagToString(key.tag));
+
+            final NetworkStatsHistory history = mStats.get(key);
+            pw.increaseIndent();
+            history.dump(pw, true);
+            pw.decreaseIndent();
+        }
+    }
+
+    /**
+     * Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
+     * in the given {@link NetworkIdentitySet}.
+     */
+    private static boolean templateMatches(NetworkTemplate template, NetworkIdentitySet identSet) {
+        for (NetworkIdentity ident : identSet) {
+            if (template.matches(ident)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static class Key implements Comparable<Key> {
+        public final NetworkIdentitySet ident;
+        public final int uid;
+        public final int set;
+        public final int tag;
+
+        private final int hashCode;
+
+        public Key(NetworkIdentitySet ident, int uid, int set, int tag) {
+            this.ident = ident;
+            this.uid = uid;
+            this.set = set;
+            this.tag = tag;
+            hashCode = Objects.hash(ident, uid, set, tag);
+        }
+
+        @Override
+        public int hashCode() {
+            return hashCode;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj instanceof Key) {
+                final Key key = (Key) obj;
+                return uid == key.uid && set == key.set && tag == key.tag
+                        && Objects.equals(ident, key.ident);
+            }
+            return false;
+        }
+
+        @Override
+        public int compareTo(Key another) {
+            return Integer.compare(uid, another.uid);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
new file mode 100644
index 0000000..cea084b
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.KB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.DropBoxManager;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Slog;
+
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Sets;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+
+import libcore.io.IoUtils;
+
+/**
+ * Logic to record deltas between periodic {@link NetworkStats} snapshots into
+ * {@link NetworkStatsHistory} that belong to {@link NetworkStatsCollection}.
+ * Keeps pending changes in memory until they pass a specific threshold, in
+ * bytes. Uses {@link FileRotator} for persistence logic.
+ * <p>
+ * Not inherently thread safe.
+ */
+public class NetworkStatsRecorder {
+    private static final String TAG = "NetworkStatsRecorder";
+    private static final boolean LOGD = false;
+    private static final boolean LOGV = false;
+
+    private static final String TAG_NETSTATS_DUMP = "netstats_dump";
+
+    /** Dump before deleting in {@link #recoverFromWtf()}. */
+    private static final boolean DUMP_BEFORE_DELETE = true;
+
+    private final FileRotator mRotator;
+    private final NonMonotonicObserver<String> mObserver;
+    private final DropBoxManager mDropBox;
+    private final String mCookie;
+
+    private final long mBucketDuration;
+    private final boolean mOnlyTags;
+
+    private long mPersistThresholdBytes = 2 * MB_IN_BYTES;
+    private NetworkStats mLastSnapshot;
+
+    private final NetworkStatsCollection mPending;
+    private final NetworkStatsCollection mSinceBoot;
+
+    private final CombiningRewriter mPendingRewriter;
+
+    private WeakReference<NetworkStatsCollection> mComplete;
+
+    public NetworkStatsRecorder(FileRotator rotator, NonMonotonicObserver<String> observer,
+            DropBoxManager dropBox, String cookie, long bucketDuration, boolean onlyTags) {
+        mRotator = checkNotNull(rotator, "missing FileRotator");
+        mObserver = checkNotNull(observer, "missing NonMonotonicObserver");
+        mDropBox = checkNotNull(dropBox, "missing DropBoxManager");
+        mCookie = cookie;
+
+        mBucketDuration = bucketDuration;
+        mOnlyTags = onlyTags;
+
+        mPending = new NetworkStatsCollection(bucketDuration);
+        mSinceBoot = new NetworkStatsCollection(bucketDuration);
+
+        mPendingRewriter = new CombiningRewriter(mPending);
+    }
+
+    public void setPersistThreshold(long thresholdBytes) {
+        if (LOGV) Slog.v(TAG, "setPersistThreshold() with " + thresholdBytes);
+        mPersistThresholdBytes = MathUtils.constrain(
+                thresholdBytes, 1 * KB_IN_BYTES, 100 * MB_IN_BYTES);
+    }
+
+    public void resetLocked() {
+        mLastSnapshot = null;
+        mPending.reset();
+        mSinceBoot.reset();
+        mComplete.clear();
+    }
+
+    public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
+        return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
+    }
+
+    /**
+     * Load complete history represented by {@link FileRotator}. Caches
+     * internally as a {@link WeakReference}, and updated with future
+     * {@link #recordSnapshotLocked(NetworkStats, Map, long)} snapshots as long
+     * as reference is valid.
+     */
+    public NetworkStatsCollection getOrLoadCompleteLocked() {
+        NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+        if (complete == null) {
+            if (LOGD) Slog.d(TAG, "getOrLoadCompleteLocked() reading from disk for " + mCookie);
+            try {
+                complete = new NetworkStatsCollection(mBucketDuration);
+                mRotator.readMatching(complete, Long.MIN_VALUE, Long.MAX_VALUE);
+                complete.recordCollection(mPending);
+                mComplete = new WeakReference<NetworkStatsCollection>(complete);
+            } catch (IOException e) {
+                Log.wtf(TAG, "problem completely reading network stats", e);
+                recoverFromWtf();
+            } catch (OutOfMemoryError e) {
+                Log.wtf(TAG, "problem completely reading network stats", e);
+                recoverFromWtf();
+            }
+        }
+        return complete;
+    }
+
+    /**
+     * Record any delta that occurred since last {@link NetworkStats} snapshot,
+     * using the given {@link Map} to identify network interfaces. First
+     * snapshot is considered bootstrap, and is not counted as delta.
+     */
+    public void recordSnapshotLocked(NetworkStats snapshot,
+            Map<String, NetworkIdentitySet> ifaceIdent, long currentTimeMillis) {
+        final HashSet<String> unknownIfaces = Sets.newHashSet();
+
+        // skip recording when snapshot missing
+        if (snapshot == null) return;
+
+        // assume first snapshot is bootstrap and don't record
+        if (mLastSnapshot == null) {
+            mLastSnapshot = snapshot;
+            return;
+        }
+
+        final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+
+        final NetworkStats delta = NetworkStats.subtract(
+                snapshot, mLastSnapshot, mObserver, mCookie);
+        final long end = currentTimeMillis;
+        final long start = end - delta.getElapsedRealtime();
+
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < delta.size(); i++) {
+            entry = delta.getValues(i, entry);
+            final NetworkIdentitySet ident = ifaceIdent.get(entry.iface);
+            if (ident == null) {
+                unknownIfaces.add(entry.iface);
+                continue;
+            }
+
+            // skip when no delta occurred
+            if (entry.isEmpty()) continue;
+
+            // only record tag data when requested
+            if ((entry.tag == TAG_NONE) != mOnlyTags) {
+                mPending.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+
+                // also record against boot stats when present
+                if (mSinceBoot != null) {
+                    mSinceBoot.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+                }
+
+                // also record against complete dataset when present
+                if (complete != null) {
+                    complete.recordData(ident, entry.uid, entry.set, entry.tag, start, end, entry);
+                }
+            }
+        }
+
+        mLastSnapshot = snapshot;
+
+        if (LOGV && unknownIfaces.size() > 0) {
+            Slog.w(TAG, "unknown interfaces " + unknownIfaces + ", ignoring those stats");
+        }
+    }
+
+    /**
+     * Consider persisting any pending deltas, if they are beyond
+     * {@link #mPersistThresholdBytes}.
+     */
+    public void maybePersistLocked(long currentTimeMillis) {
+        final long pendingBytes = mPending.getTotalBytes();
+        if (pendingBytes >= mPersistThresholdBytes) {
+            forcePersistLocked(currentTimeMillis);
+        } else {
+            mRotator.maybeRotate(currentTimeMillis);
+        }
+    }
+
+    /**
+     * Force persisting any pending deltas.
+     */
+    public void forcePersistLocked(long currentTimeMillis) {
+        if (mPending.isDirty()) {
+            if (LOGD) Slog.d(TAG, "forcePersistLocked() writing for " + mCookie);
+            try {
+                mRotator.rewriteActive(mPendingRewriter, currentTimeMillis);
+                mRotator.maybeRotate(currentTimeMillis);
+                mPending.reset();
+            } catch (IOException e) {
+                Log.wtf(TAG, "problem persisting pending stats", e);
+                recoverFromWtf();
+            } catch (OutOfMemoryError e) {
+                Log.wtf(TAG, "problem persisting pending stats", e);
+                recoverFromWtf();
+            }
+        }
+    }
+
+    /**
+     * Remove the given UID from all {@link FileRotator} history, migrating it
+     * to {@link TrafficStats#UID_REMOVED}.
+     */
+    public void removeUidsLocked(int[] uids) {
+        try {
+            // Rewrite all persisted data to migrate UID stats
+            mRotator.rewriteAll(new RemoveUidRewriter(mBucketDuration, uids));
+        } catch (IOException e) {
+            Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+            recoverFromWtf();
+        } catch (OutOfMemoryError e) {
+            Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e);
+            recoverFromWtf();
+        }
+
+        // Remove any pending stats
+        mPending.removeUids(uids);
+        mSinceBoot.removeUids(uids);
+
+        // Clear UID from current stats snapshot
+        if (mLastSnapshot != null) {
+            mLastSnapshot = mLastSnapshot.withoutUids(uids);
+        }
+
+        final NetworkStatsCollection complete = mComplete != null ? mComplete.get() : null;
+        if (complete != null) {
+            complete.removeUids(uids);
+        }
+    }
+
+    /**
+     * Rewriter that will combine current {@link NetworkStatsCollection} values
+     * with anything read from disk, and write combined set to disk. Clears the
+     * original {@link NetworkStatsCollection} when finished writing.
+     */
+    private static class CombiningRewriter implements FileRotator.Rewriter {
+        private final NetworkStatsCollection mCollection;
+
+        public CombiningRewriter(NetworkStatsCollection collection) {
+            mCollection = checkNotNull(collection, "missing NetworkStatsCollection");
+        }
+
+        @Override
+        public void reset() {
+            // ignored
+        }
+
+        @Override
+        public void read(InputStream in) throws IOException {
+            mCollection.read(in);
+        }
+
+        @Override
+        public boolean shouldWrite() {
+            return true;
+        }
+
+        @Override
+        public void write(OutputStream out) throws IOException {
+            mCollection.write(new DataOutputStream(out));
+            mCollection.reset();
+        }
+    }
+
+    /**
+     * Rewriter that will remove any {@link NetworkStatsHistory} attributed to
+     * the requested UID, only writing data back when modified.
+     */
+    public static class RemoveUidRewriter implements FileRotator.Rewriter {
+        private final NetworkStatsCollection mTemp;
+        private final int[] mUids;
+
+        public RemoveUidRewriter(long bucketDuration, int[] uids) {
+            mTemp = new NetworkStatsCollection(bucketDuration);
+            mUids = uids;
+        }
+
+        @Override
+        public void reset() {
+            mTemp.reset();
+        }
+
+        @Override
+        public void read(InputStream in) throws IOException {
+            mTemp.read(in);
+            mTemp.clearDirty();
+            mTemp.removeUids(mUids);
+        }
+
+        @Override
+        public boolean shouldWrite() {
+            return mTemp.isDirty();
+        }
+
+        @Override
+        public void write(OutputStream out) throws IOException {
+            mTemp.write(new DataOutputStream(out));
+        }
+    }
+
+    public void importLegacyNetworkLocked(File file) throws IOException {
+        // legacy file still exists; start empty to avoid double importing
+        mRotator.deleteAll();
+
+        final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+        collection.readLegacyNetwork(file);
+
+        final long startMillis = collection.getStartMillis();
+        final long endMillis = collection.getEndMillis();
+
+        if (!collection.isEmpty()) {
+            // process legacy data, creating active file at starting time, then
+            // using end time to possibly trigger rotation.
+            mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+            mRotator.maybeRotate(endMillis);
+        }
+    }
+
+    public void importLegacyUidLocked(File file) throws IOException {
+        // legacy file still exists; start empty to avoid double importing
+        mRotator.deleteAll();
+
+        final NetworkStatsCollection collection = new NetworkStatsCollection(mBucketDuration);
+        collection.readLegacyUid(file, mOnlyTags);
+
+        final long startMillis = collection.getStartMillis();
+        final long endMillis = collection.getEndMillis();
+
+        if (!collection.isEmpty()) {
+            // process legacy data, creating active file at starting time, then
+            // using end time to possibly trigger rotation.
+            mRotator.rewriteActive(new CombiningRewriter(collection), startMillis);
+            mRotator.maybeRotate(endMillis);
+        }
+    }
+
+    public void dumpLocked(IndentingPrintWriter pw, boolean fullHistory) {
+        pw.print("Pending bytes: "); pw.println(mPending.getTotalBytes());
+        if (fullHistory) {
+            pw.println("Complete history:");
+            getOrLoadCompleteLocked().dump(pw);
+        } else {
+            pw.println("History since boot:");
+            mSinceBoot.dump(pw);
+        }
+    }
+
+    /**
+     * Recover from {@link FileRotator} failure by dumping state to
+     * {@link DropBoxManager} and deleting contents.
+     */
+    private void recoverFromWtf() {
+        if (DUMP_BEFORE_DELETE) {
+            final ByteArrayOutputStream os = new ByteArrayOutputStream();
+            try {
+                mRotator.dumpAll(os);
+            } catch (IOException e) {
+                // ignore partial contents
+                os.reset();
+            } finally {
+                IoUtils.closeQuietly(os);
+            }
+            mDropBox.addData(TAG_NETSTATS_DUMP, os.toByteArray(), 0);
+        }
+
+        mRotator.deleteAll();
+    }
+}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
new file mode 100644
index 0000000..5d6adc2
--- /dev/null
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -0,0 +1,1340 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net;
+
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
+import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MODIFY_NETWORK_ACCOUNTING;
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.content.Intent.ACTION_SHUTDOWN;
+import static android.content.Intent.ACTION_UID_REMOVED;
+import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_UID;
+import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.isNetworkTypeMobile;
+import static android.net.NetworkIdentity.COMBINE_SUBTYPE_ENABLED;
+import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.SET_ALL;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkTemplate.buildTemplateMobileWildcard;
+import static android.net.NetworkTemplate.buildTemplateWifiWildcard;
+import static android.net.TrafficStats.KB_IN_BYTES;
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.provider.Settings.Global.NETSTATS_DEV_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_DEV_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_DEV_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_DEV_ROTATE_AGE;
+import static android.provider.Settings.Global.NETSTATS_GLOBAL_ALERT_BYTES;
+import static android.provider.Settings.Global.NETSTATS_POLL_INTERVAL;
+import static android.provider.Settings.Global.NETSTATS_REPORT_XT_OVER_DEV;
+import static android.provider.Settings.Global.NETSTATS_SAMPLE_ENABLED;
+import static android.provider.Settings.Global.NETSTATS_TIME_CACHE_MAX_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_UID_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_UID_ROTATE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_BUCKET_DURATION;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_DELETE_AGE;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_PERSIST_BYTES;
+import static android.provider.Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE;
+import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
+import static android.telephony.PhoneStateListener.LISTEN_NONE;
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.internal.util.ArrayUtils.appendElement;
+import static com.android.internal.util.ArrayUtils.contains;
+import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
+import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
+
+import android.app.AlarmManager;
+import android.app.IAlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkStatsService;
+import android.net.INetworkStatsSession;
+import android.net.LinkProperties;
+import android.net.NetworkIdentity;
+import android.net.NetworkInfo;
+import android.net.NetworkState;
+import android.net.NetworkStats;
+import android.net.NetworkStats.NonMonotonicObserver;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.net.TrafficStats;
+import android.os.Binder;
+import android.os.DropBoxManager;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.NtpTrustedTime;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TrustedTime;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FileRotator;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.EventLogTags;
+import com.android.server.connectivity.Tethering;
+import com.google.android.collect.Maps;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Collect and persist detailed network statistics, and provide this data to
+ * other system services.
+ */
+public class NetworkStatsService extends INetworkStatsService.Stub {
+    private static final String TAG = "NetworkStats";
+    private static final boolean LOGV = false;
+
+    private static final int MSG_PERFORM_POLL = 1;
+    private static final int MSG_UPDATE_IFACES = 2;
+    private static final int MSG_REGISTER_GLOBAL_ALERT = 3;
+
+    /** Flags to control detail level of poll event. */
+    private static final int FLAG_PERSIST_NETWORK = 0x1;
+    private static final int FLAG_PERSIST_UID = 0x2;
+    private static final int FLAG_PERSIST_ALL = FLAG_PERSIST_NETWORK | FLAG_PERSIST_UID;
+    private static final int FLAG_PERSIST_FORCE = 0x100;
+
+    private static final String TAG_NETSTATS_ERROR = "netstats_error";
+
+    private final Context mContext;
+    private final INetworkManagementService mNetworkManager;
+    private final AlarmManager mAlarmManager;
+    private final TrustedTime mTime;
+    private final TelephonyManager mTeleManager;
+    private final NetworkStatsSettings mSettings;
+
+    private final File mSystemDir;
+    private final File mBaseDir;
+
+    private final PowerManager.WakeLock mWakeLock;
+
+    private IConnectivityManager mConnManager;
+
+    @VisibleForTesting
+    public static final String ACTION_NETWORK_STATS_POLL =
+            "com.android.server.action.NETWORK_STATS_POLL";
+    public static final String ACTION_NETWORK_STATS_UPDATED =
+            "com.android.server.action.NETWORK_STATS_UPDATED";
+
+    private PendingIntent mPollIntent;
+
+    private static final String PREFIX_DEV = "dev";
+    private static final String PREFIX_XT = "xt";
+    private static final String PREFIX_UID = "uid";
+    private static final String PREFIX_UID_TAG = "uid_tag";
+
+    /**
+     * Settings that can be changed externally.
+     */
+    public interface NetworkStatsSettings {
+        public long getPollInterval();
+        public long getTimeCacheMaxAge();
+        public boolean getSampleEnabled();
+        public boolean getReportXtOverDev();
+
+        public static class Config {
+            public final long bucketDuration;
+            public final long rotateAgeMillis;
+            public final long deleteAgeMillis;
+
+            public Config(long bucketDuration, long rotateAgeMillis, long deleteAgeMillis) {
+                this.bucketDuration = bucketDuration;
+                this.rotateAgeMillis = rotateAgeMillis;
+                this.deleteAgeMillis = deleteAgeMillis;
+            }
+        }
+
+        public Config getDevConfig();
+        public Config getXtConfig();
+        public Config getUidConfig();
+        public Config getUidTagConfig();
+
+        public long getGlobalAlertBytes(long def);
+        public long getDevPersistBytes(long def);
+        public long getXtPersistBytes(long def);
+        public long getUidPersistBytes(long def);
+        public long getUidTagPersistBytes(long def);
+    }
+
+    private final Object mStatsLock = new Object();
+
+    /** Set of currently active ifaces. */
+    private HashMap<String, NetworkIdentitySet> mActiveIfaces = Maps.newHashMap();
+    /** Current default active iface. */
+    private String mActiveIface;
+    /** Set of any ifaces associated with mobile networks since boot. */
+    private String[] mMobileIfaces = new String[0];
+
+    private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
+            new DropBoxNonMonotonicObserver();
+
+    private NetworkStatsRecorder mDevRecorder;
+    private NetworkStatsRecorder mXtRecorder;
+    private NetworkStatsRecorder mUidRecorder;
+    private NetworkStatsRecorder mUidTagRecorder;
+
+    /** Cached {@link #mDevRecorder} stats. */
+    private NetworkStatsCollection mDevStatsCached;
+    /** Cached {@link #mXtRecorder} stats. */
+    private NetworkStatsCollection mXtStatsCached;
+
+    /** Current counter sets for each UID. */
+    private SparseIntArray mActiveUidCounterSet = new SparseIntArray();
+
+    /** Data layer operation counters for splicing into other structures. */
+    private NetworkStats mUidOperations = new NetworkStats(0L, 10);
+
+    private final Handler mHandler;
+
+    private boolean mSystemReady;
+    private long mPersistThreshold = 2 * MB_IN_BYTES;
+    private long mGlobalAlertBytes;
+
+    public NetworkStatsService(
+            Context context, INetworkManagementService networkManager, IAlarmManager alarmManager) {
+        this(context, networkManager, alarmManager, NtpTrustedTime.getInstance(context),
+                getDefaultSystemDir(), new DefaultNetworkStatsSettings(context));
+    }
+
+    private static File getDefaultSystemDir() {
+        return new File(Environment.getDataDirectory(), "system");
+    }
+
+    public NetworkStatsService(Context context, INetworkManagementService networkManager,
+            IAlarmManager alarmManager, TrustedTime time, File systemDir,
+            NetworkStatsSettings settings) {
+        mContext = checkNotNull(context, "missing Context");
+        mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService");
+        mTime = checkNotNull(time, "missing TrustedTime");
+        mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager");
+        mSettings = checkNotNull(settings, "missing NetworkStatsSettings");
+        mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+
+        final PowerManager powerManager = (PowerManager) context.getSystemService(
+                Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mHandler = new Handler(thread.getLooper(), mHandlerCallback);
+
+        mSystemDir = checkNotNull(systemDir);
+        mBaseDir = new File(systemDir, "netstats");
+        mBaseDir.mkdirs();
+    }
+
+    public void bindConnectivityManager(IConnectivityManager connManager) {
+        mConnManager = checkNotNull(connManager, "missing IConnectivityManager");
+    }
+
+    public void systemReady() {
+        mSystemReady = true;
+
+        if (!isBandwidthControlEnabled()) {
+            Slog.w(TAG, "bandwidth controls disabled, unable to track stats");
+            return;
+        }
+
+        // create data recorders along with historical rotators
+        mDevRecorder = buildRecorder(PREFIX_DEV, mSettings.getDevConfig(), false);
+        mXtRecorder = buildRecorder(PREFIX_XT, mSettings.getXtConfig(), false);
+        mUidRecorder = buildRecorder(PREFIX_UID, mSettings.getUidConfig(), false);
+        mUidTagRecorder = buildRecorder(PREFIX_UID_TAG, mSettings.getUidTagConfig(), true);
+
+        updatePersistThresholds();
+
+        synchronized (mStatsLock) {
+            // upgrade any legacy stats, migrating them to rotated files
+            maybeUpgradeLegacyStatsLocked();
+
+            // read historical network stats from disk, since policy service
+            // might need them right away.
+            mDevStatsCached = mDevRecorder.getOrLoadCompleteLocked();
+            mXtStatsCached = mXtRecorder.getOrLoadCompleteLocked();
+
+            // bootstrap initial stats to prevent double-counting later
+            bootstrapStatsLocked();
+        }
+
+        // watch for network interfaces to be claimed
+        final IntentFilter connFilter = new IntentFilter(CONNECTIVITY_ACTION_IMMEDIATE);
+        mContext.registerReceiver(mConnReceiver, connFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+        // watch for tethering changes
+        final IntentFilter tetherFilter = new IntentFilter(ACTION_TETHER_STATE_CHANGED);
+        mContext.registerReceiver(mTetherReceiver, tetherFilter, CONNECTIVITY_INTERNAL, mHandler);
+
+        // listen for periodic polling events
+        final IntentFilter pollFilter = new IntentFilter(ACTION_NETWORK_STATS_POLL);
+        mContext.registerReceiver(mPollReceiver, pollFilter, READ_NETWORK_USAGE_HISTORY, mHandler);
+
+        // listen for uid removal to clean stats
+        final IntentFilter removedFilter = new IntentFilter(ACTION_UID_REMOVED);
+        mContext.registerReceiver(mRemovedReceiver, removedFilter, null, mHandler);
+
+        // listen for user changes to clean stats
+        final IntentFilter userFilter = new IntentFilter(ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+        // persist stats during clean shutdown
+        final IntentFilter shutdownFilter = new IntentFilter(ACTION_SHUTDOWN);
+        mContext.registerReceiver(mShutdownReceiver, shutdownFilter);
+
+        try {
+            mNetworkManager.registerObserver(mAlertObserver);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+
+        // watch for networkType changes that aren't broadcast through
+        // CONNECTIVITY_ACTION_IMMEDIATE above.
+        if (!COMBINE_SUBTYPE_ENABLED) {
+            mTeleManager.listen(mPhoneListener, LISTEN_DATA_CONNECTION_STATE);
+        }
+
+        registerPollAlarmLocked();
+        registerGlobalAlert();
+    }
+
+    private NetworkStatsRecorder buildRecorder(
+            String prefix, NetworkStatsSettings.Config config, boolean includeTags) {
+        final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
+                Context.DROPBOX_SERVICE);
+        return new NetworkStatsRecorder(new FileRotator(
+                mBaseDir, prefix, config.rotateAgeMillis, config.deleteAgeMillis),
+                mNonMonotonicObserver, dropBox, prefix, config.bucketDuration, includeTags);
+    }
+
+    private void shutdownLocked() {
+        mContext.unregisterReceiver(mConnReceiver);
+        mContext.unregisterReceiver(mTetherReceiver);
+        mContext.unregisterReceiver(mPollReceiver);
+        mContext.unregisterReceiver(mRemovedReceiver);
+        mContext.unregisterReceiver(mShutdownReceiver);
+
+        if (!COMBINE_SUBTYPE_ENABLED) {
+            mTeleManager.listen(mPhoneListener, LISTEN_NONE);
+        }
+
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+
+        // persist any pending stats
+        mDevRecorder.forcePersistLocked(currentTime);
+        mXtRecorder.forcePersistLocked(currentTime);
+        mUidRecorder.forcePersistLocked(currentTime);
+        mUidTagRecorder.forcePersistLocked(currentTime);
+
+        mDevRecorder = null;
+        mXtRecorder = null;
+        mUidRecorder = null;
+        mUidTagRecorder = null;
+
+        mDevStatsCached = null;
+        mXtStatsCached = null;
+
+        mSystemReady = false;
+    }
+
+    private void maybeUpgradeLegacyStatsLocked() {
+        File file;
+        try {
+            file = new File(mSystemDir, "netstats.bin");
+            if (file.exists()) {
+                mDevRecorder.importLegacyNetworkLocked(file);
+                file.delete();
+            }
+
+            file = new File(mSystemDir, "netstats_xt.bin");
+            if (file.exists()) {
+                file.delete();
+            }
+
+            file = new File(mSystemDir, "netstats_uid.bin");
+            if (file.exists()) {
+                mUidRecorder.importLegacyUidLocked(file);
+                mUidTagRecorder.importLegacyUidLocked(file);
+                file.delete();
+            }
+        } catch (IOException e) {
+            Log.wtf(TAG, "problem during legacy upgrade", e);
+        } catch (OutOfMemoryError e) {
+            Log.wtf(TAG, "problem during legacy upgrade", e);
+        }
+    }
+
+    /**
+     * Clear any existing {@link #ACTION_NETWORK_STATS_POLL} alarms, and
+     * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}.
+     */
+    private void registerPollAlarmLocked() {
+        if (mPollIntent != null) {
+            mAlarmManager.cancel(mPollIntent);
+        }
+
+        mPollIntent = PendingIntent.getBroadcast(
+                mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0);
+
+        final long currentRealtime = SystemClock.elapsedRealtime();
+        mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime,
+                mSettings.getPollInterval(), mPollIntent);
+    }
+
+    /**
+     * Register for a global alert that is delivered through
+     * {@link INetworkManagementEventObserver} once a threshold amount of data
+     * has been transferred.
+     */
+    private void registerGlobalAlert() {
+        try {
+            mNetworkManager.setGlobalAlert(mGlobalAlertBytes);
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "problem registering for global alert: " + e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    @Override
+    public INetworkStatsSession openSession() {
+        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        assertBandwidthControlEnabled();
+
+        // return an IBinder which holds strong references to any loaded stats
+        // for its lifetime; when caller closes only weak references remain.
+
+        return new INetworkStatsSession.Stub() {
+            private NetworkStatsCollection mUidComplete;
+            private NetworkStatsCollection mUidTagComplete;
+
+            private NetworkStatsCollection getUidComplete() {
+                if (mUidComplete == null) {
+                    synchronized (mStatsLock) {
+                        mUidComplete = mUidRecorder.getOrLoadCompleteLocked();
+                    }
+                }
+                return mUidComplete;
+            }
+
+            private NetworkStatsCollection getUidTagComplete() {
+                if (mUidTagComplete == null) {
+                    synchronized (mStatsLock) {
+                        mUidTagComplete = mUidTagRecorder.getOrLoadCompleteLocked();
+                    }
+                }
+                return mUidTagComplete;
+            }
+
+            @Override
+            public NetworkStats getSummaryForNetwork(
+                    NetworkTemplate template, long start, long end) {
+                return internalGetSummaryForNetwork(template, start, end);
+            }
+
+            @Override
+            public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
+                return internalGetHistoryForNetwork(template, fields);
+            }
+
+            @Override
+            public NetworkStats getSummaryForAllUid(
+                    NetworkTemplate template, long start, long end, boolean includeTags) {
+                final NetworkStats stats = getUidComplete().getSummary(template, start, end);
+                if (includeTags) {
+                    final NetworkStats tagStats = getUidTagComplete()
+                            .getSummary(template, start, end);
+                    stats.combineAllValues(tagStats);
+                }
+                return stats;
+            }
+
+            @Override
+            public NetworkStatsHistory getHistoryForUid(
+                    NetworkTemplate template, int uid, int set, int tag, int fields) {
+                if (tag == TAG_NONE) {
+                    return getUidComplete().getHistory(template, uid, set, tag, fields);
+                } else {
+                    return getUidTagComplete().getHistory(template, uid, set, tag, fields);
+                }
+            }
+
+            @Override
+            public void close() {
+                mUidComplete = null;
+                mUidTagComplete = null;
+            }
+        };
+    }
+
+    /**
+     * Return network summary, splicing between {@link #mDevStatsCached}
+     * and {@link #mXtStatsCached} when appropriate.
+     */
+    private NetworkStats internalGetSummaryForNetwork(
+            NetworkTemplate template, long start, long end) {
+        if (!mSettings.getReportXtOverDev()) {
+            // shortcut when XT reporting disabled
+            return mDevStatsCached.getSummary(template, start, end);
+        }
+
+        // splice stats between DEV and XT, switching over from DEV to XT at
+        // first atomic bucket.
+        final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis();
+        final NetworkStats dev = mDevStatsCached.getSummary(
+                template, Math.min(start, firstAtomicBucket), Math.min(end, firstAtomicBucket));
+        final NetworkStats xt = mXtStatsCached.getSummary(
+                template, Math.max(start, firstAtomicBucket), Math.max(end, firstAtomicBucket));
+
+        xt.combineAllValues(dev);
+        return xt;
+    }
+
+    /**
+     * Return network history, splicing between {@link #mDevStatsCached}
+     * and {@link #mXtStatsCached} when appropriate.
+     */
+    private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields) {
+        if (!mSettings.getReportXtOverDev()) {
+            // shortcut when XT reporting disabled
+            return mDevStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
+        }
+
+        // splice stats between DEV and XT, switching over from DEV to XT at
+        // first atomic bucket.
+        final long firstAtomicBucket = mXtStatsCached.getFirstAtomicBucketMillis();
+        final NetworkStatsHistory dev = mDevStatsCached.getHistory(
+                template, UID_ALL, SET_ALL, TAG_NONE, fields, Long.MIN_VALUE, firstAtomicBucket);
+        final NetworkStatsHistory xt = mXtStatsCached.getHistory(
+                template, UID_ALL, SET_ALL, TAG_NONE, fields, firstAtomicBucket, Long.MAX_VALUE);
+
+        xt.recordEntireHistory(dev);
+        return xt;
+    }
+
+    @Override
+    public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
+        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        assertBandwidthControlEnabled();
+        return internalGetSummaryForNetwork(template, start, end).getTotalBytes();
+    }
+
+    @Override
+    public NetworkStats getDataLayerSnapshotForUid(int uid) throws RemoteException {
+        if (Binder.getCallingUid() != uid) {
+            mContext.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, TAG);
+        }
+        assertBandwidthControlEnabled();
+
+        // TODO: switch to data layer stats once kernel exports
+        // for now, read network layer stats and flatten across all ifaces
+        final long token = Binder.clearCallingIdentity();
+        final NetworkStats networkLayer;
+        try {
+            networkLayer = mNetworkManager.getNetworkStatsUidDetail(uid);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+
+        // splice in operation counts
+        networkLayer.spliceOperationsFrom(mUidOperations);
+
+        final NetworkStats dataLayer = new NetworkStats(
+                networkLayer.getElapsedRealtime(), networkLayer.size());
+
+        NetworkStats.Entry entry = null;
+        for (int i = 0; i < networkLayer.size(); i++) {
+            entry = networkLayer.getValues(i, entry);
+            entry.iface = IFACE_ALL;
+            dataLayer.combineValues(entry);
+        }
+
+        return dataLayer;
+    }
+
+    @Override
+    public String[] getMobileIfaces() {
+        return mMobileIfaces;
+    }
+
+    @Override
+    public void incrementOperationCount(int uid, int tag, int operationCount) {
+        if (Binder.getCallingUid() != uid) {
+            mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
+        }
+
+        if (operationCount < 0) {
+            throw new IllegalArgumentException("operation count can only be incremented");
+        }
+        if (tag == TAG_NONE) {
+            throw new IllegalArgumentException("operation count must have specific tag");
+        }
+
+        synchronized (mStatsLock) {
+            final int set = mActiveUidCounterSet.get(uid, SET_DEFAULT);
+            mUidOperations.combineValues(
+                    mActiveIface, uid, set, tag, 0L, 0L, 0L, 0L, operationCount);
+            mUidOperations.combineValues(
+                    mActiveIface, uid, set, TAG_NONE, 0L, 0L, 0L, 0L, operationCount);
+        }
+    }
+
+    @Override
+    public void setUidForeground(int uid, boolean uidForeground) {
+        mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
+
+        synchronized (mStatsLock) {
+            final int set = uidForeground ? SET_FOREGROUND : SET_DEFAULT;
+            final int oldSet = mActiveUidCounterSet.get(uid, SET_DEFAULT);
+            if (oldSet != set) {
+                mActiveUidCounterSet.put(uid, set);
+                setKernelCounterSet(uid, set);
+            }
+        }
+    }
+
+    @Override
+    public void forceUpdate() {
+        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        assertBandwidthControlEnabled();
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            performPoll(FLAG_PERSIST_ALL);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    @Override
+    public void advisePersistThreshold(long thresholdBytes) {
+        mContext.enforceCallingOrSelfPermission(MODIFY_NETWORK_ACCOUNTING, TAG);
+        assertBandwidthControlEnabled();
+
+        // clamp threshold into safe range
+        mPersistThreshold = MathUtils.constrain(thresholdBytes, 128 * KB_IN_BYTES, 2 * MB_IN_BYTES);
+        if (LOGV) {
+            Slog.v(TAG, "advisePersistThreshold() given " + thresholdBytes + ", clamped to "
+                    + mPersistThreshold);
+        }
+
+        // update and persist if beyond new thresholds
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+        synchronized (mStatsLock) {
+            if (!mSystemReady) return;
+
+            updatePersistThresholds();
+
+            mDevRecorder.maybePersistLocked(currentTime);
+            mXtRecorder.maybePersistLocked(currentTime);
+            mUidRecorder.maybePersistLocked(currentTime);
+            mUidTagRecorder.maybePersistLocked(currentTime);
+        }
+
+        // re-arm global alert
+        registerGlobalAlert();
+    }
+
+    /**
+     * Update {@link NetworkStatsRecorder} and {@link #mGlobalAlertBytes} to
+     * reflect current {@link #mPersistThreshold} value. Always defers to
+     * {@link Global} values when defined.
+     */
+    private void updatePersistThresholds() {
+        mDevRecorder.setPersistThreshold(mSettings.getDevPersistBytes(mPersistThreshold));
+        mXtRecorder.setPersistThreshold(mSettings.getXtPersistBytes(mPersistThreshold));
+        mUidRecorder.setPersistThreshold(mSettings.getUidPersistBytes(mPersistThreshold));
+        mUidTagRecorder.setPersistThreshold(mSettings.getUidTagPersistBytes(mPersistThreshold));
+        mGlobalAlertBytes = mSettings.getGlobalAlertBytes(mPersistThreshold);
+    }
+
+    /**
+     * Receiver that watches for {@link IConnectivityManager} to claim network
+     * interfaces. Used to associate {@link TelephonyManager#getSubscriberId()}
+     * with mobile interfaces.
+     */
+    private BroadcastReceiver mConnReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+            updateIfaces();
+        }
+    };
+
+    /**
+     * Receiver that watches for {@link Tethering} to claim interface pairs.
+     */
+    private BroadcastReceiver mTetherReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified CONNECTIVITY_INTERNAL
+            // permission above.
+            performPoll(FLAG_PERSIST_NETWORK);
+        }
+    };
+
+    private BroadcastReceiver mPollReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and verified UPDATE_DEVICE_STATS
+            // permission above.
+            performPoll(FLAG_PERSIST_ALL);
+
+            // verify that we're watching global alert
+            registerGlobalAlert();
+        }
+    };
+
+    private BroadcastReceiver mRemovedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // on background handler thread, and UID_REMOVED is protected
+            // broadcast.
+
+            final int uid = intent.getIntExtra(EXTRA_UID, -1);
+            if (uid == -1) return;
+
+            synchronized (mStatsLock) {
+                mWakeLock.acquire();
+                try {
+                    removeUidsLocked(uid);
+                } finally {
+                    mWakeLock.release();
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // On background handler thread, and USER_REMOVED is protected
+            // broadcast.
+
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+
+            synchronized (mStatsLock) {
+                mWakeLock.acquire();
+                try {
+                    removeUserLocked(userId);
+                } finally {
+                    mWakeLock.release();
+                }
+            }
+        }
+    };
+
+    private BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // SHUTDOWN is protected broadcast.
+            synchronized (mStatsLock) {
+                shutdownLocked();
+            }
+        }
+    };
+
+    /**
+     * Observer that watches for {@link INetworkManagementService} alerts.
+     */
+    private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+        @Override
+        public void limitReached(String limitName, String iface) {
+            // only someone like NMS should be calling us
+            mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+            if (LIMIT_GLOBAL_ALERT.equals(limitName)) {
+                // kick off background poll to collect network stats; UID stats
+                // are handled during normal polling interval.
+                final int flags = FLAG_PERSIST_NETWORK;
+                mHandler.obtainMessage(MSG_PERFORM_POLL, flags, 0).sendToTarget();
+
+                // re-arm global alert for next update
+                mHandler.obtainMessage(MSG_REGISTER_GLOBAL_ALERT).sendToTarget();
+            }
+        }
+    };
+
+    private int mLastPhoneState = TelephonyManager.DATA_UNKNOWN;
+    private int mLastPhoneNetworkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+
+    /**
+     * Receiver that watches for {@link TelephonyManager} changes, such as
+     * transitioning between network types.
+     */
+    private PhoneStateListener mPhoneListener = new PhoneStateListener() {
+        @Override
+        public void onDataConnectionStateChanged(int state, int networkType) {
+            final boolean stateChanged = state != mLastPhoneState;
+            final boolean networkTypeChanged = networkType != mLastPhoneNetworkType;
+
+            if (networkTypeChanged && !stateChanged) {
+                // networkType changed without a state change, which means we
+                // need to roll our own update. delay long enough for
+                // ConnectivityManager to process.
+                // TODO: add direct event to ConnectivityService instead of
+                // relying on this delay.
+                if (LOGV) Slog.v(TAG, "triggering delayed updateIfaces()");
+                mHandler.sendMessageDelayed(
+                        mHandler.obtainMessage(MSG_UPDATE_IFACES), SECOND_IN_MILLIS);
+            }
+
+            mLastPhoneState = state;
+            mLastPhoneNetworkType = networkType;
+        }
+    };
+
+    private void updateIfaces() {
+        synchronized (mStatsLock) {
+            mWakeLock.acquire();
+            try {
+                updateIfacesLocked();
+            } finally {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    /**
+     * Inspect all current {@link NetworkState} to derive mapping from {@code
+     * iface} to {@link NetworkStatsHistory}. When multiple {@link NetworkInfo}
+     * are active on a single {@code iface}, they are combined under a single
+     * {@link NetworkIdentitySet}.
+     */
+    private void updateIfacesLocked() {
+        if (!mSystemReady) return;
+        if (LOGV) Slog.v(TAG, "updateIfacesLocked()");
+
+        // take one last stats snapshot before updating iface mapping. this
+        // isn't perfect, since the kernel may already be counting traffic from
+        // the updated network.
+
+        // poll, but only persist network stats to keep codepath fast. UID stats
+        // will be persisted during next alarm poll event.
+        performPollLocked(FLAG_PERSIST_NETWORK);
+
+        final NetworkState[] states;
+        final LinkProperties activeLink;
+        try {
+            states = mConnManager.getAllNetworkState();
+            activeLink = mConnManager.getActiveLinkProperties();
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return;
+        }
+
+        mActiveIface = activeLink != null ? activeLink.getInterfaceName() : null;
+
+        // rebuild active interfaces based on connected networks
+        mActiveIfaces.clear();
+
+        for (NetworkState state : states) {
+            if (state.networkInfo.isConnected()) {
+                // collect networks under their parent interfaces
+                final String iface = state.linkProperties.getInterfaceName();
+
+                NetworkIdentitySet ident = mActiveIfaces.get(iface);
+                if (ident == null) {
+                    ident = new NetworkIdentitySet();
+                    mActiveIfaces.put(iface, ident);
+                }
+
+                ident.add(NetworkIdentity.buildNetworkIdentity(mContext, state));
+
+                // remember any ifaces associated with mobile networks
+                if (isNetworkTypeMobile(state.networkInfo.getType()) && iface != null) {
+                    if (!contains(mMobileIfaces, iface)) {
+                        mMobileIfaces = appendElement(String.class, mMobileIfaces, iface);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Bootstrap initial stats snapshot, usually during {@link #systemReady()}
+     * so we have baseline values without double-counting.
+     */
+    private void bootstrapStatsLocked() {
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+
+        try {
+            // snapshot and record current counters; read UID stats first to
+            // avoid overcounting dev stats.
+            final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+            final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
+            final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
+
+            mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+            mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
+            mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+            mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "problem reading network stats: " + e);
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+        }
+    }
+
+    private void performPoll(int flags) {
+        // try refreshing time source when stale
+        if (mTime.getCacheAge() > mSettings.getTimeCacheMaxAge()) {
+            mTime.forceRefresh();
+        }
+
+        synchronized (mStatsLock) {
+            mWakeLock.acquire();
+
+            try {
+                performPollLocked(flags);
+            } finally {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    /**
+     * Periodic poll operation, reading current statistics and recording into
+     * {@link NetworkStatsHistory}.
+     */
+    private void performPollLocked(int flags) {
+        if (!mSystemReady) return;
+        if (LOGV) Slog.v(TAG, "performPollLocked(flags=0x" + Integer.toHexString(flags) + ")");
+
+        final long startRealtime = SystemClock.elapsedRealtime();
+
+        final boolean persistNetwork = (flags & FLAG_PERSIST_NETWORK) != 0;
+        final boolean persistUid = (flags & FLAG_PERSIST_UID) != 0;
+        final boolean persistForce = (flags & FLAG_PERSIST_FORCE) != 0;
+
+        // TODO: consider marking "untrusted" times in historical stats
+        final long currentTime = mTime.hasCache() ? mTime.currentTimeMillis()
+                : System.currentTimeMillis();
+
+        try {
+            // snapshot and record current counters; read UID stats first to
+            // avoid overcounting dev stats.
+            final NetworkStats uidSnapshot = getNetworkStatsUidDetail();
+            final NetworkStats xtSnapshot = mNetworkManager.getNetworkStatsSummaryXt();
+            final NetworkStats devSnapshot = mNetworkManager.getNetworkStatsSummaryDev();
+
+            mDevRecorder.recordSnapshotLocked(devSnapshot, mActiveIfaces, currentTime);
+            mXtRecorder.recordSnapshotLocked(xtSnapshot, mActiveIfaces, currentTime);
+            mUidRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+            mUidTagRecorder.recordSnapshotLocked(uidSnapshot, mActiveIfaces, currentTime);
+
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem reading network stats", e);
+            return;
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return;
+        }
+
+        // persist any pending data depending on requested flags
+        if (persistForce) {
+            mDevRecorder.forcePersistLocked(currentTime);
+            mXtRecorder.forcePersistLocked(currentTime);
+            mUidRecorder.forcePersistLocked(currentTime);
+            mUidTagRecorder.forcePersistLocked(currentTime);
+        } else {
+            if (persistNetwork) {
+                mDevRecorder.maybePersistLocked(currentTime);
+                mXtRecorder.maybePersistLocked(currentTime);
+            }
+            if (persistUid) {
+                mUidRecorder.maybePersistLocked(currentTime);
+                mUidTagRecorder.maybePersistLocked(currentTime);
+            }
+        }
+
+        if (LOGV) {
+            final long duration = SystemClock.elapsedRealtime() - startRealtime;
+            Slog.v(TAG, "performPollLocked() took " + duration + "ms");
+        }
+
+        if (mSettings.getSampleEnabled()) {
+            // sample stats after each full poll
+            performSampleLocked();
+        }
+
+        // finally, dispatch updated event to any listeners
+        final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
+        updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(updatedIntent, UserHandle.ALL,
+                READ_NETWORK_USAGE_HISTORY);
+    }
+
+    /**
+     * Sample recent statistics summary into {@link EventLog}.
+     */
+    private void performSampleLocked() {
+        // TODO: migrate trustedtime fixes to separate binary log events
+        final long trustedTime = mTime.hasCache() ? mTime.currentTimeMillis() : -1;
+
+        NetworkTemplate template;
+        NetworkStats.Entry devTotal;
+        NetworkStats.Entry xtTotal;
+        NetworkStats.Entry uidTotal;
+
+        // collect mobile sample
+        template = buildTemplateMobileWildcard();
+        devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+        xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
+        uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
+        EventLogTags.writeNetstatsMobileSample(
+                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                trustedTime);
+
+        // collect wifi sample
+        template = buildTemplateWifiWildcard();
+        devTotal = mDevRecorder.getTotalSinceBootLocked(template);
+        xtTotal = mXtRecorder.getTotalSinceBootLocked(template);
+        uidTotal = mUidRecorder.getTotalSinceBootLocked(template);
+
+        EventLogTags.writeNetstatsWifiSample(
+                devTotal.rxBytes, devTotal.rxPackets, devTotal.txBytes, devTotal.txPackets,
+                xtTotal.rxBytes, xtTotal.rxPackets, xtTotal.txBytes, xtTotal.txPackets,
+                uidTotal.rxBytes, uidTotal.rxPackets, uidTotal.txBytes, uidTotal.txPackets,
+                trustedTime);
+    }
+
+    /**
+     * Clean up {@link #mUidRecorder} after UID is removed.
+     */
+    private void removeUidsLocked(int... uids) {
+        if (LOGV) Slog.v(TAG, "removeUidsLocked() for UIDs " + Arrays.toString(uids));
+
+        // Perform one last poll before removing
+        performPollLocked(FLAG_PERSIST_ALL);
+
+        mUidRecorder.removeUidsLocked(uids);
+        mUidTagRecorder.removeUidsLocked(uids);
+
+        // Clear kernel stats associated with UID
+        for (int uid : uids) {
+            resetKernelUidStats(uid);
+        }
+    }
+
+    /**
+     * Clean up {@link #mUidRecorder} after user is removed.
+     */
+    private void removeUserLocked(int userId) {
+        if (LOGV) Slog.v(TAG, "removeUserLocked() for userId=" + userId);
+
+        // Build list of UIDs that we should clean up
+        int[] uids = new int[0];
+        final List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
+        for (ApplicationInfo app : apps) {
+            final int uid = UserHandle.getUid(userId, app.uid);
+            uids = ArrayUtils.appendInt(uids, uid);
+        }
+
+        removeUidsLocked(uids);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(DUMP, TAG);
+
+        final HashSet<String> argSet = new HashSet<String>();
+        for (String arg : args) {
+            argSet.add(arg);
+        }
+
+        // usage: dumpsys netstats --full --uid --tag --poll --checkin
+        final boolean poll = argSet.contains("--poll") || argSet.contains("poll");
+        final boolean checkin = argSet.contains("--checkin");
+        final boolean fullHistory = argSet.contains("--full") || argSet.contains("full");
+        final boolean includeUid = argSet.contains("--uid") || argSet.contains("detail");
+        final boolean includeTag = argSet.contains("--tag") || argSet.contains("detail");
+
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+
+        synchronized (mStatsLock) {
+            if (poll) {
+                performPollLocked(FLAG_PERSIST_ALL | FLAG_PERSIST_FORCE);
+                pw.println("Forced poll");
+                return;
+            }
+
+            if (checkin) {
+                // list current stats files to verify rotation
+                pw.println("Current files:");
+                pw.increaseIndent();
+                for (String file : mBaseDir.list()) {
+                    pw.println(file);
+                }
+                pw.decreaseIndent();
+                return;
+            }
+
+            pw.println("Active interfaces:");
+            pw.increaseIndent();
+            for (String iface : mActiveIfaces.keySet()) {
+                final NetworkIdentitySet ident = mActiveIfaces.get(iface);
+                pw.print("iface="); pw.print(iface);
+                pw.print(" ident="); pw.println(ident.toString());
+            }
+            pw.decreaseIndent();
+
+            pw.println("Dev stats:");
+            pw.increaseIndent();
+            mDevRecorder.dumpLocked(pw, fullHistory);
+            pw.decreaseIndent();
+
+            pw.println("Xt stats:");
+            pw.increaseIndent();
+            mXtRecorder.dumpLocked(pw, fullHistory);
+            pw.decreaseIndent();
+
+            if (includeUid) {
+                pw.println("UID stats:");
+                pw.increaseIndent();
+                mUidRecorder.dumpLocked(pw, fullHistory);
+                pw.decreaseIndent();
+            }
+
+            if (includeTag) {
+                pw.println("UID tag stats:");
+                pw.increaseIndent();
+                mUidTagRecorder.dumpLocked(pw, fullHistory);
+                pw.decreaseIndent();
+            }
+        }
+    }
+
+    /**
+     * Return snapshot of current UID statistics, including any
+     * {@link TrafficStats#UID_TETHERING} and {@link #mUidOperations} values.
+     */
+    private NetworkStats getNetworkStatsUidDetail() throws RemoteException {
+        final NetworkStats uidSnapshot = mNetworkManager.getNetworkStatsUidDetail(UID_ALL);
+
+        // fold tethering stats and operations into uid snapshot
+        final NetworkStats tetherSnapshot = getNetworkStatsTethering();
+        uidSnapshot.combineAllValues(tetherSnapshot);
+        uidSnapshot.combineAllValues(mUidOperations);
+
+        return uidSnapshot;
+    }
+
+    /**
+     * Return snapshot of current tethering statistics. Will return empty
+     * {@link NetworkStats} if any problems are encountered.
+     */
+    private NetworkStats getNetworkStatsTethering() throws RemoteException {
+        try {
+            return mNetworkManager.getNetworkStatsTethering();
+        } catch (IllegalStateException e) {
+            Log.wtf(TAG, "problem reading network stats", e);
+            return new NetworkStats(0L, 10);
+        }
+    }
+
+    private Handler.Callback mHandlerCallback = new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PERFORM_POLL: {
+                    final int flags = msg.arg1;
+                    performPoll(flags);
+                    return true;
+                }
+                case MSG_UPDATE_IFACES: {
+                    updateIfaces();
+                    return true;
+                }
+                case MSG_REGISTER_GLOBAL_ALERT: {
+                    registerGlobalAlert();
+                    return true;
+                }
+                default: {
+                    return false;
+                }
+            }
+        }
+    };
+
+    private void assertBandwidthControlEnabled() {
+        if (!isBandwidthControlEnabled()) {
+            throw new IllegalStateException("Bandwidth module disabled");
+        }
+    }
+
+    private boolean isBandwidthControlEnabled() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            return mNetworkManager.isBandwidthControlEnabled();
+        } catch (RemoteException e) {
+            // ignored; service lives in system_server
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    private class DropBoxNonMonotonicObserver implements NonMonotonicObserver<String> {
+        @Override
+        public void foundNonMonotonic(NetworkStats left, int leftIndex, NetworkStats right,
+                int rightIndex, String cookie) {
+            Log.w(TAG, "found non-monotonic values; saving to dropbox");
+
+            // record error for debugging
+            final StringBuilder builder = new StringBuilder();
+            builder.append("found non-monotonic " + cookie + " values at left[" + leftIndex
+                    + "] - right[" + rightIndex + "]\n");
+            builder.append("left=").append(left).append('\n');
+            builder.append("right=").append(right).append('\n');
+
+            final DropBoxManager dropBox = (DropBoxManager) mContext.getSystemService(
+                    Context.DROPBOX_SERVICE);
+            dropBox.addText(TAG_NETSTATS_ERROR, builder.toString());
+        }
+    }
+
+    /**
+     * Default external settings that read from
+     * {@link android.provider.Settings.Global}.
+     */
+    private static class DefaultNetworkStatsSettings implements NetworkStatsSettings {
+        private final ContentResolver mResolver;
+
+        public DefaultNetworkStatsSettings(Context context) {
+            mResolver = checkNotNull(context.getContentResolver());
+            // TODO: adjust these timings for production builds
+        }
+
+        private long getGlobalLong(String name, long def) {
+            return Settings.Global.getLong(mResolver, name, def);
+        }
+        private boolean getGlobalBoolean(String name, boolean def) {
+            final int defInt = def ? 1 : 0;
+            return Settings.Global.getInt(mResolver, name, defInt) != 0;
+        }
+
+        @Override
+        public long getPollInterval() {
+            return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
+        }
+        @Override
+        public long getTimeCacheMaxAge() {
+            return getGlobalLong(NETSTATS_TIME_CACHE_MAX_AGE, DAY_IN_MILLIS);
+        }
+        @Override
+        public long getGlobalAlertBytes(long def) {
+            return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
+        }
+        @Override
+        public boolean getSampleEnabled() {
+            return getGlobalBoolean(NETSTATS_SAMPLE_ENABLED, true);
+        }
+        @Override
+        public boolean getReportXtOverDev() {
+            return getGlobalBoolean(NETSTATS_REPORT_XT_OVER_DEV, true);
+        }
+        @Override
+        public Config getDevConfig() {
+            return new Config(getGlobalLong(NETSTATS_DEV_BUCKET_DURATION, HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_DEV_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_DEV_DELETE_AGE, 90 * DAY_IN_MILLIS));
+        }
+        @Override
+        public Config getXtConfig() {
+            return getDevConfig();
+        }
+        @Override
+        public Config getUidConfig() {
+            return new Config(getGlobalLong(NETSTATS_UID_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_ROTATE_AGE, 15 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_DELETE_AGE, 90 * DAY_IN_MILLIS));
+        }
+        @Override
+        public Config getUidTagConfig() {
+            return new Config(getGlobalLong(NETSTATS_UID_TAG_BUCKET_DURATION, 2 * HOUR_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_TAG_ROTATE_AGE, 5 * DAY_IN_MILLIS),
+                    getGlobalLong(NETSTATS_UID_TAG_DELETE_AGE, 15 * DAY_IN_MILLIS));
+        }
+        @Override
+        public long getDevPersistBytes(long def) {
+            return getGlobalLong(NETSTATS_DEV_PERSIST_BYTES, def);
+        }
+        @Override
+        public long getXtPersistBytes(long def) {
+            return getDevPersistBytes(def);
+        }
+        @Override
+        public long getUidPersistBytes(long def) {
+            return getGlobalLong(NETSTATS_UID_PERSIST_BYTES, def);
+        }
+        @Override
+        public long getUidTagPersistBytes(long def) {
+            return getGlobalLong(NETSTATS_UID_TAG_PERSIST_BYTES, def);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
new file mode 100644
index 0000000..df2aaca
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
new file mode 100644
index 0000000..92ffdcc
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
new file mode 100644
index 0000000..db4cf31d
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -0,0 +1,2427 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
+import android.app.IActivityManager;
+import android.app.INotificationManager;
+import android.app.ITransientNotification;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.IRingtonePlayer;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.service.notification.INotificationListener;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.AtomicFile;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Toast;
+
+import com.android.internal.R;
+
+import com.android.internal.notification.NotificationScorer;
+import com.android.server.EventLogTags;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.SystemService;
+import com.android.server.lights.Light;
+import com.android.server.lights.LightsManager;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Array;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import libcore.io.IoUtils;
+
+/** {@hide} */
+public class NotificationManagerService extends SystemService {
+    static final String TAG = "NotificationService";
+    static final boolean DBG = false;
+
+    static final int MAX_PACKAGE_NOTIFICATIONS = 50;
+
+    // message codes
+    static final int MESSAGE_TIMEOUT = 2;
+
+    static final int LONG_DELAY = 3500; // 3.5 seconds
+    static final int SHORT_DELAY = 2000; // 2 seconds
+
+    static final long[] DEFAULT_VIBRATE_PATTERN = {0, 250, 250, 250};
+    static final int VIBRATE_PATTERN_MAXLEN = 8 * 2 + 1; // up to eight bumps
+
+    static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_NOTIFICATION;
+    static final boolean SCORE_ONGOING_HIGHER = false;
+
+    static final int JUNK_SCORE = -1000;
+    static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10;
+    static final int SCORE_DISPLAY_THRESHOLD = Notification.PRIORITY_MIN * NOTIFICATION_PRIORITY_MULTIPLIER;
+
+    // Notifications with scores below this will not interrupt the user, either via LED or
+    // sound or vibration
+    static final int SCORE_INTERRUPTION_THRESHOLD =
+            Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
+
+    static final boolean ENABLE_BLOCKED_NOTIFICATIONS = true;
+    static final boolean ENABLE_BLOCKED_TOASTS = true;
+
+    static final String ENABLED_NOTIFICATION_LISTENERS_SEPARATOR = ":";
+
+    private IActivityManager mAm;
+    AudioManager mAudioManager;
+    StatusBarManagerInternal mStatusBar;
+    Vibrator mVibrator;
+
+    final IBinder mForegroundToken = new Binder();
+    private WorkerHandler mHandler;
+
+    private Light mNotificationLight;
+    Light mAttentionLight;
+    private int mDefaultNotificationColor;
+    private int mDefaultNotificationLedOn;
+
+    private int mDefaultNotificationLedOff;
+    private long[] mDefaultVibrationPattern;
+
+    private long[] mFallbackVibrationPattern;
+    boolean mSystemReady;
+
+    int mDisabledNotifications;
+    NotificationRecord mSoundNotification;
+    NotificationRecord mVibrateNotification;
+
+    // for enabling and disabling notification pulse behavior
+    private boolean mScreenOn = true;
+    private boolean mInCall = false;
+    private boolean mNotificationPulseEnabled;
+
+    // used as a mutex for access to all active notifications & listeners
+    final ArrayList<NotificationRecord> mNotificationList =
+            new ArrayList<NotificationRecord>();
+
+    final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
+
+    ArrayList<NotificationRecord> mLights = new ArrayList<NotificationRecord>();
+    NotificationRecord mLedNotification;
+
+    private AppOpsManager mAppOps;
+
+    // contains connections to all connected listeners, including app services
+    // and system listeners
+    private ArrayList<NotificationListenerInfo> mListeners
+            = new ArrayList<NotificationListenerInfo>();
+    // things that will be put into mListeners as soon as they're ready
+    private ArrayList<String> mServicesBinding = new ArrayList<String>();
+    // lists the component names of all enabled (and therefore connected) listener
+    // app services for the current user only
+    private HashSet<ComponentName> mEnabledListenersForCurrentUser
+            = new HashSet<ComponentName>();
+    // Just the packages from mEnabledListenersForCurrentUser
+    private HashSet<String> mEnabledListenerPackageNames = new HashSet<String>();
+
+    // Notification control database. For now just contains disabled packages.
+    private AtomicFile mPolicyFile;
+    private HashSet<String> mBlockedPackages = new HashSet<String>();
+
+    private static final int DB_VERSION = 1;
+
+    private static final String TAG_BODY = "notification-policy";
+    private static final String ATTR_VERSION = "version";
+
+    private static final String TAG_BLOCKED_PKGS = "blocked-packages";
+    private static final String TAG_PACKAGE = "package";
+    private static final String ATTR_NAME = "name";
+
+    final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
+
+    private class NotificationListenerInfo implements IBinder.DeathRecipient {
+        INotificationListener listener;
+        ComponentName component;
+        int userid;
+        boolean isSystem;
+        ServiceConnection connection;
+
+        public NotificationListenerInfo(INotificationListener listener, ComponentName component,
+                int userid, boolean isSystem) {
+            this.listener = listener;
+            this.component = component;
+            this.userid = userid;
+            this.isSystem = isSystem;
+            this.connection = null;
+        }
+
+        public NotificationListenerInfo(INotificationListener listener, ComponentName component,
+                int userid, ServiceConnection connection) {
+            this.listener = listener;
+            this.component = component;
+            this.userid = userid;
+            this.isSystem = false;
+            this.connection = connection;
+        }
+
+        boolean enabledAndUserMatches(StatusBarNotification sbn) {
+            final int nid = sbn.getUserId();
+            if (!isEnabledForCurrentUser()) {
+                return false;
+            }
+            if (this.userid == UserHandle.USER_ALL) return true;
+            return (nid == UserHandle.USER_ALL || nid == this.userid);
+        }
+
+        public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
+            if (!enabledAndUserMatches(sbn)) {
+                return;
+            }
+            try {
+                listener.onNotificationPosted(sbn);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
+            }
+        }
+
+        public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
+            if (!enabledAndUserMatches(sbn)) return;
+            try {
+                listener.onNotificationRemoved(sbn);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
+            }
+        }
+
+        @Override
+        public void binderDied() {
+            if (connection == null) {
+                // This is not a service; it won't be recreated. We can give up this connection.
+                unregisterListenerImpl(this.listener, this.userid);
+            }
+        }
+
+        /** convenience method for looking in mEnabledListenersForCurrentUser */
+        public boolean isEnabledForCurrentUser() {
+            if (this.isSystem) return true;
+            if (this.connection == null) return false;
+            return mEnabledListenersForCurrentUser.contains(this.component);
+        }
+    }
+
+    private static class Archive {
+        static final int BUFFER_SIZE = 250;
+        ArrayDeque<StatusBarNotification> mBuffer = new ArrayDeque<StatusBarNotification>(BUFFER_SIZE);
+
+        public Archive() {
+        }
+
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            final int N = mBuffer.size();
+            sb.append("Archive (");
+            sb.append(N);
+            sb.append(" notification");
+            sb.append((N==1)?")":"s)");
+            return sb.toString();
+        }
+
+        public void record(StatusBarNotification nr) {
+            if (mBuffer.size() == BUFFER_SIZE) {
+                mBuffer.removeFirst();
+            }
+
+            // We don't want to store the heavy bits of the notification in the archive,
+            // but other clients in the system process might be using the object, so we
+            // store a (lightened) copy.
+            mBuffer.addLast(nr.cloneLight());
+        }
+
+
+        public void clear() {
+            mBuffer.clear();
+        }
+
+        public Iterator<StatusBarNotification> descendingIterator() {
+            return mBuffer.descendingIterator();
+        }
+        public Iterator<StatusBarNotification> ascendingIterator() {
+            return mBuffer.iterator();
+        }
+        public Iterator<StatusBarNotification> filter(
+                final Iterator<StatusBarNotification> iter, final String pkg, final int userId) {
+            return new Iterator<StatusBarNotification>() {
+                StatusBarNotification mNext = findNext();
+
+                private StatusBarNotification findNext() {
+                    while (iter.hasNext()) {
+                        StatusBarNotification nr = iter.next();
+                        if ((pkg == null || nr.getPackageName() == pkg)
+                                && (userId == UserHandle.USER_ALL || nr.getUserId() == userId)) {
+                            return nr;
+                        }
+                    }
+                    return null;
+                }
+
+                @Override
+                public boolean hasNext() {
+                    return mNext == null;
+                }
+
+                @Override
+                public StatusBarNotification next() {
+                    StatusBarNotification next = mNext;
+                    if (next == null) {
+                        throw new NoSuchElementException();
+                    }
+                    mNext = findNext();
+                    return next;
+                }
+
+                @Override
+                public void remove() {
+                    iter.remove();
+                }
+            };
+        }
+
+        public StatusBarNotification[] getArray(int count) {
+            if (count == 0) count = Archive.BUFFER_SIZE;
+            final StatusBarNotification[] a
+                    = new StatusBarNotification[Math.min(count, mBuffer.size())];
+            Iterator<StatusBarNotification> iter = descendingIterator();
+            int i=0;
+            while (iter.hasNext() && i < count) {
+                a[i++] = iter.next();
+            }
+            return a;
+        }
+
+        public StatusBarNotification[] getArray(int count, String pkg, int userId) {
+            if (count == 0) count = Archive.BUFFER_SIZE;
+            final StatusBarNotification[] a
+                    = new StatusBarNotification[Math.min(count, mBuffer.size())];
+            Iterator<StatusBarNotification> iter = filter(descendingIterator(), pkg, userId);
+            int i=0;
+            while (iter.hasNext() && i < count) {
+                a[i++] = iter.next();
+            }
+            return a;
+        }
+
+    }
+
+    Archive mArchive = new Archive();
+
+    private void loadBlockDb() {
+        synchronized(mBlockedPackages) {
+            if (mPolicyFile == null) {
+                File dir = new File("/data/system");
+                mPolicyFile = new AtomicFile(new File(dir, "notification_policy.xml"));
+
+                mBlockedPackages.clear();
+
+                FileInputStream infile = null;
+                try {
+                    infile = mPolicyFile.openRead();
+                    final XmlPullParser parser = Xml.newPullParser();
+                    parser.setInput(infile, null);
+
+                    int type;
+                    String tag;
+                    int version = DB_VERSION;
+                    while ((type = parser.next()) != END_DOCUMENT) {
+                        tag = parser.getName();
+                        if (type == START_TAG) {
+                            if (TAG_BODY.equals(tag)) {
+                                version = Integer.parseInt(
+                                        parser.getAttributeValue(null, ATTR_VERSION));
+                            } else if (TAG_BLOCKED_PKGS.equals(tag)) {
+                                while ((type = parser.next()) != END_DOCUMENT) {
+                                    tag = parser.getName();
+                                    if (TAG_PACKAGE.equals(tag)) {
+                                        mBlockedPackages.add(
+                                                parser.getAttributeValue(null, ATTR_NAME));
+                                    } else if (TAG_BLOCKED_PKGS.equals(tag) && type == END_TAG) {
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } catch (FileNotFoundException e) {
+                    // No data yet
+                } catch (IOException e) {
+                    Log.wtf(TAG, "Unable to read blocked notifications database", e);
+                } catch (NumberFormatException e) {
+                    Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+                } catch (XmlPullParserException e) {
+                    Log.wtf(TAG, "Unable to parse blocked notifications database", e);
+                } finally {
+                    IoUtils.closeQuietly(infile);
+                }
+            }
+        }
+    }
+
+    /** Use this when you actually want to post a notification or toast.
+     *
+     * Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
+     */
+    private boolean noteNotificationOp(String pkg, int uid) {
+        if (mAppOps.noteOpNoThrow(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg)
+                != AppOpsManager.MODE_ALLOWED) {
+            Slog.v(TAG, "notifications are disabled by AppOps for " + pkg);
+            return false;
+        }
+        return true;
+    }
+
+    private static String idDebugString(Context baseContext, String packageName, int id) {
+        Context c = null;
+
+        if (packageName != null) {
+            try {
+                c = baseContext.createPackageContext(packageName, 0);
+            } catch (NameNotFoundException e) {
+                c = baseContext;
+            }
+        } else {
+            c = baseContext;
+        }
+
+        String pkg;
+        String type;
+        String name;
+
+        Resources r = c.getResources();
+        try {
+            return r.getResourceName(id);
+        } catch (Resources.NotFoundException e) {
+            return "<name unknown>";
+        }
+    }
+
+
+    /**
+     * Remove notification access for any services that no longer exist.
+     */
+    void disableNonexistentListeners() {
+        int currentUser = ActivityManager.getCurrentUser();
+        String flatIn = Settings.Secure.getStringForUser(
+                getContext().getContentResolver(),
+                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                currentUser);
+        if (!TextUtils.isEmpty(flatIn)) {
+            if (DBG) Slog.v(TAG, "flat before: " + flatIn);
+            PackageManager pm = getContext().getPackageManager();
+            List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+                    new Intent(NotificationListenerService.SERVICE_INTERFACE),
+                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                    currentUser);
+
+            Set<ComponentName> installed = new HashSet<ComponentName>();
+            for (int i = 0, count = installedServices.size(); i < count; i++) {
+                ResolveInfo resolveInfo = installedServices.get(i);
+                ServiceInfo info = resolveInfo.serviceInfo;
+
+                if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE.equals(
+                                info.permission)) {
+                    Slog.w(TAG, "Skipping notification listener service "
+                            + info.packageName + "/" + info.name
+                            + ": it does not require the permission "
+                            + android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
+                    continue;
+                }
+                installed.add(new ComponentName(info.packageName, info.name));
+            }
+
+            String flatOut = "";
+            if (!installed.isEmpty()) {
+                String[] enabled = flatIn.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
+                ArrayList<String> remaining = new ArrayList<String>(enabled.length);
+                for (int i = 0; i < enabled.length; i++) {
+                    ComponentName enabledComponent = ComponentName.unflattenFromString(enabled[i]);
+                    if (installed.contains(enabledComponent)) {
+                        remaining.add(enabled[i]);
+                    }
+                }
+                flatOut = TextUtils.join(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR, remaining);
+            }
+            if (DBG) Slog.v(TAG, "flat after: " + flatOut);
+            if (!flatIn.equals(flatOut)) {
+                Settings.Secure.putStringForUser(getContext().getContentResolver(),
+                        Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                        flatOut, currentUser);
+            }
+        }
+    }
+
+    /**
+     * Called whenever packages change, the user switches, or ENABLED_NOTIFICATION_LISTENERS
+     * is altered. (For example in response to USER_SWITCHED in our broadcast receiver)
+     */
+    void rebindListenerServices() {
+        final int currentUser = ActivityManager.getCurrentUser();
+        String flat = Settings.Secure.getStringForUser(
+                getContext().getContentResolver(),
+                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
+                currentUser);
+
+        NotificationListenerInfo[] toRemove = new NotificationListenerInfo[mListeners.size()];
+        final ArrayList<ComponentName> toAdd;
+
+        synchronized (mNotificationList) {
+            // unbind and remove all existing listeners
+            toRemove = mListeners.toArray(toRemove);
+
+            toAdd = new ArrayList<ComponentName>();
+            final HashSet<ComponentName> newEnabled = new HashSet<ComponentName>();
+            final HashSet<String> newPackages = new HashSet<String>();
+
+            // decode the list of components
+            if (flat != null) {
+                String[] components = flat.split(ENABLED_NOTIFICATION_LISTENERS_SEPARATOR);
+                for (int i=0; i<components.length; i++) {
+                    final ComponentName component
+                            = ComponentName.unflattenFromString(components[i]);
+                    if (component != null) {
+                        newEnabled.add(component);
+                        toAdd.add(component);
+                        newPackages.add(component.getPackageName());
+                    }
+                }
+
+                mEnabledListenersForCurrentUser = newEnabled;
+                mEnabledListenerPackageNames = newPackages;
+            }
+        }
+
+        for (NotificationListenerInfo info : toRemove) {
+            final ComponentName component = info.component;
+            final int oldUser = info.userid;
+            Slog.v(TAG, "disabling notification listener for user " + oldUser + ": " + component);
+            unregisterListenerService(component, info.userid);
+        }
+
+        final int N = toAdd.size();
+        for (int i=0; i<N; i++) {
+            final ComponentName component = toAdd.get(i);
+            Slog.v(TAG, "enabling notification listener for user " + currentUser + ": "
+                    + component);
+            registerListenerService(component, currentUser);
+        }
+    }
+
+
+    /**
+     * Version of registerListener that takes the name of a
+     * {@link android.service.notification.NotificationListenerService} to bind to.
+     *
+     * This is the mechanism by which third parties may subscribe to notifications.
+     */
+    private void registerListenerService(final ComponentName name, final int userid) {
+        checkCallerIsSystem();
+
+        if (DBG) Slog.v(TAG, "registerListenerService: " + name + " u=" + userid);
+
+        synchronized (mNotificationList) {
+            final String servicesBindingTag = name.toString() + "/" + userid;
+            if (mServicesBinding.contains(servicesBindingTag)) {
+                // stop registering this thing already! we're working on it
+                return;
+            }
+            mServicesBinding.add(servicesBindingTag);
+
+            final int N = mListeners.size();
+            for (int i=N-1; i>=0; i--) {
+                final NotificationListenerInfo info = mListeners.get(i);
+                if (name.equals(info.component)
+                        && info.userid == userid) {
+                    // cut old connections
+                    if (DBG) Slog.v(TAG, "    disconnecting old listener: " + info.listener);
+                    mListeners.remove(i);
+                    if (info.connection != null) {
+                        getContext().unbindService(info.connection);
+                    }
+                }
+            }
+
+            Intent intent = new Intent(NotificationListenerService.SERVICE_INTERFACE);
+            intent.setComponent(name);
+
+            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                    R.string.notification_listener_binding_label);
+
+            final PendingIntent pendingIntent = PendingIntent.getActivity(
+                    getContext(), 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0);
+            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+
+            try {
+                if (DBG) Slog.v(TAG, "binding: " + intent);
+                if (!getContext().bindServiceAsUser(intent,
+                        new ServiceConnection() {
+                            INotificationListener mListener;
+
+                            @Override
+                            public void onServiceConnected(ComponentName name, IBinder service) {
+                                synchronized (mNotificationList) {
+                                    mServicesBinding.remove(servicesBindingTag);
+                                    try {
+                                        mListener = INotificationListener.Stub.asInterface(service);
+                                        NotificationListenerInfo info
+                                                = new NotificationListenerInfo(
+                                                mListener, name, userid, this);
+                                        service.linkToDeath(info, 0);
+                                        mListeners.add(info);
+                                    } catch (RemoteException e) {
+                                        // already dead
+                                    }
+                                }
+                            }
+
+                            @Override
+                            public void onServiceDisconnected(ComponentName name) {
+                                Slog.v(TAG, "notification listener connection lost: " + name);
+                            }
+                        },
+                        Context.BIND_AUTO_CREATE,
+                        new UserHandle(userid)))
+                {
+                    mServicesBinding.remove(servicesBindingTag);
+                    Slog.w(TAG, "Unable to bind listener service: " + intent);
+                    return;
+                }
+            } catch (SecurityException ex) {
+                Slog.e(TAG, "Unable to bind listener service: " + intent, ex);
+                return;
+            }
+        }
+    }
+
+
+    /**
+     * Remove a listener service for the given user by ComponentName
+     */
+    private void unregisterListenerService(ComponentName name, int userid) {
+        checkCallerIsSystem();
+
+        synchronized (mNotificationList) {
+            final int N = mListeners.size();
+            for (int i=N-1; i>=0; i--) {
+                final NotificationListenerInfo info = mListeners.get(i);
+                if (name.equals(info.component)
+                        && info.userid == userid) {
+                    mListeners.remove(i);
+                    if (info.connection != null) {
+                        try {
+                            getContext().unbindService(info.connection);
+                        } catch (IllegalArgumentException ex) {
+                            // something happened to the service: we think we have a connection
+                            // but it's bogus.
+                            Slog.e(TAG, "Listener " + name + " could not be unbound: " + ex);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * asynchronously notify all listeners about a new notification
+     */
+    void notifyPostedLocked(NotificationRecord n) {
+        // make a copy in case changes are made to the underlying Notification object
+        final StatusBarNotification sbn = n.sbn.clone();
+        for (final NotificationListenerInfo info : mListeners) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    info.notifyPostedIfUserMatch(sbn);
+                }});
+        }
+    }
+
+    /**
+     * asynchronously notify all listeners about a removed notification
+     */
+    void notifyRemovedLocked(NotificationRecord n) {
+        // make a copy in case changes are made to the underlying Notification object
+        // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the notification
+        final StatusBarNotification sbn_light = n.sbn.cloneLight();
+
+        for (final NotificationListenerInfo info : mListeners) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    info.notifyRemovedIfUserMatch(sbn_light);
+                }});
+        }
+    }
+
+    // -- APIs to support listeners clicking/clearing notifications --
+
+    private NotificationListenerInfo checkListenerToken(INotificationListener listener) {
+        final IBinder token = listener.asBinder();
+        final int N = mListeners.size();
+        for (int i=0; i<N; i++) {
+            final NotificationListenerInfo info = mListeners.get(i);
+            if (info.listener.asBinder() == token) return info;
+        }
+        throw new SecurityException("Disallowed call from unknown listener: " + listener);
+    }
+
+
+
+    // -- end of listener APIs --
+
+    public static final class NotificationRecord
+    {
+        final StatusBarNotification sbn;
+        IBinder statusBarKey;
+
+        NotificationRecord(StatusBarNotification sbn)
+        {
+            this.sbn = sbn;
+        }
+
+        public Notification getNotification() { return sbn.getNotification(); }
+        public int getFlags() { return sbn.getNotification().flags; }
+        public int getUserId() { return sbn.getUserId(); }
+
+        void dump(PrintWriter pw, String prefix, Context baseContext) {
+            final Notification notification = sbn.getNotification();
+            pw.println(prefix + this);
+            pw.println(prefix + "  uid=" + sbn.getUid() + " userId=" + sbn.getUserId());
+            pw.println(prefix + "  icon=0x" + Integer.toHexString(notification.icon)
+                    + " / " + idDebugString(baseContext, sbn.getPackageName(), notification.icon));
+            pw.println(prefix + "  pri=" + notification.priority + " score=" + sbn.getScore());
+            pw.println(prefix + "  contentIntent=" + notification.contentIntent);
+            pw.println(prefix + "  deleteIntent=" + notification.deleteIntent);
+            pw.println(prefix + "  tickerText=" + notification.tickerText);
+            pw.println(prefix + "  contentView=" + notification.contentView);
+            pw.println(prefix + String.format("  defaults=0x%08x flags=0x%08x",
+                    notification.defaults, notification.flags));
+            pw.println(prefix + "  sound=" + notification.sound);
+            pw.println(prefix + "  vibrate=" + Arrays.toString(notification.vibrate));
+            pw.println(prefix + String.format("  led=0x%08x onMs=%d offMs=%d",
+                    notification.ledARGB, notification.ledOnMS, notification.ledOffMS));
+            if (notification.actions != null && notification.actions.length > 0) {
+                pw.println(prefix + "  actions={");
+                final int N = notification.actions.length;
+                for (int i=0; i<N; i++) {
+                    final Notification.Action action = notification.actions[i];
+                    pw.println(String.format("%s    [%d] \"%s\" -> %s",
+                            prefix,
+                            i,
+                            action.title,
+                            action.actionIntent.toString()
+                            ));
+                }
+                pw.println(prefix + "  }");
+            }
+            if (notification.extras != null && notification.extras.size() > 0) {
+                pw.println(prefix + "  extras={");
+                for (String key : notification.extras.keySet()) {
+                    pw.print(prefix + "    " + key + "=");
+                    Object val = notification.extras.get(key);
+                    if (val == null) {
+                        pw.println("null");
+                    } else {
+                        pw.print(val.toString());
+                        if (val instanceof Bitmap) {
+                            pw.print(String.format(" (%dx%d)",
+                                    ((Bitmap) val).getWidth(),
+                                    ((Bitmap) val).getHeight()));
+                        } else if (val.getClass().isArray()) {
+                            pw.println(" {");
+                            final int N = Array.getLength(val);
+                            for (int i=0; i<N; i++) {
+                                if (i > 0) pw.println(",");
+                                pw.print(prefix + "      " + Array.get(val, i));
+                            }
+                            pw.print("\n" + prefix + "    }");
+                        }
+                        pw.println();
+                    }
+                }
+                pw.println(prefix + "  }");
+            }
+        }
+
+        @Override
+        public final String toString() {
+            return String.format(
+                    "NotificationRecord(0x%08x: pkg=%s user=%s id=%d tag=%s score=%d: %s)",
+                    System.identityHashCode(this),
+                    this.sbn.getPackageName(), this.sbn.getUser(), this.sbn.getId(),
+                    this.sbn.getTag(), this.sbn.getScore(), this.sbn.getNotification());
+        }
+    }
+
+    private static final class ToastRecord
+    {
+        final int pid;
+        final String pkg;
+        final ITransientNotification callback;
+        int duration;
+
+        ToastRecord(int pid, String pkg, ITransientNotification callback, int duration)
+        {
+            this.pid = pid;
+            this.pkg = pkg;
+            this.callback = callback;
+            this.duration = duration;
+        }
+
+        void update(int duration) {
+            this.duration = duration;
+        }
+
+        void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + this);
+        }
+
+        @Override
+        public final String toString()
+        {
+            return "ToastRecord{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " pkg=" + pkg
+                + " callback=" + callback
+                + " duration=" + duration;
+        }
+    }
+
+    private final NotificationDelegate mNotificationDelegate = new NotificationDelegate() {
+
+        @Override
+        public void onSetDisabled(int status) {
+            synchronized (mNotificationList) {
+                mDisabledNotifications = status;
+                if ((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) != 0) {
+                    // cancel whatever's going on
+                    long identity = Binder.clearCallingIdentity();
+                    try {
+                        final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                        if (player != null) {
+                            player.stopAsync();
+                        }
+                    } catch (RemoteException e) {
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+
+                    identity = Binder.clearCallingIdentity();
+                    try {
+                        mVibrator.cancel();
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onClearAll() {
+            // XXX to be totally correct, the caller should tell us which user
+            // this is for.
+            cancelAll(ActivityManager.getCurrentUser());
+        }
+
+        @Override
+        public void onNotificationClick(String pkg, String tag, int id) {
+            // XXX to be totally correct, the caller should tell us which user
+            // this is for.
+            cancelNotification(pkg, tag, id, Notification.FLAG_AUTO_CANCEL,
+                    Notification.FLAG_FOREGROUND_SERVICE, false,
+                    ActivityManager.getCurrentUser());
+        }
+
+        @Override
+        public void onNotificationClear(String pkg, String tag, int id) {
+            // XXX to be totally correct, the caller should tell us which user
+            // this is for.
+            cancelNotification(pkg, tag, id, 0,
+                Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
+                true, ActivityManager.getCurrentUser());
+        }
+
+        @Override
+        public void onPanelRevealed() {
+            synchronized (mNotificationList) {
+                // sound
+                mSoundNotification = null;
+
+                long identity = Binder.clearCallingIdentity();
+                try {
+                    final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                    if (player != null) {
+                        player.stopAsync();
+                    }
+                } catch (RemoteException e) {
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+
+                // vibrate
+                mVibrateNotification = null;
+                identity = Binder.clearCallingIdentity();
+                try {
+                    mVibrator.cancel();
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+
+                // light
+                mLights.clear();
+                mLedNotification = null;
+                updateLightsLocked();
+            }
+        }
+
+        @Override
+        public void onNotificationError(String pkg, String tag, int id,
+                int uid, int initialPid, String message) {
+            Slog.d(TAG, "onNotification error pkg=" + pkg + " tag=" + tag + " id=" + id
+                    + "; will crashApplication(uid=" + uid + ", pid=" + initialPid + ")");
+            // XXX to be totally correct, the caller should tell us which user
+            // this is for.
+            cancelNotification(pkg, tag, id, 0, 0, false, UserHandle.getUserId(uid));
+            long ident = Binder.clearCallingIdentity();
+            try {
+                ActivityManagerNative.getDefault().crashApplication(uid, initialPid, pkg,
+                        "Bad notification posted from package " + pkg
+                        + ": " + message);
+            } catch (RemoteException e) {
+            }
+            Binder.restoreCallingIdentity(ident);
+        }
+    };
+
+    private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            boolean queryRestart = false;
+            boolean queryRemove = false;
+            boolean packageChanged = false;
+            boolean cancelNotifications = true;
+            
+            if (action.equals(Intent.ACTION_PACKAGE_ADDED)
+                    || (queryRemove=action.equals(Intent.ACTION_PACKAGE_REMOVED))
+                    || action.equals(Intent.ACTION_PACKAGE_RESTARTED)
+                    || (packageChanged=action.equals(Intent.ACTION_PACKAGE_CHANGED))
+                    || (queryRestart=action.equals(Intent.ACTION_QUERY_PACKAGE_RESTART))
+                    || action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
+                String pkgList[] = null;
+                boolean queryReplace = queryRemove &&
+                        intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+                if (DBG) Slog.i(TAG, "queryReplace=" + queryReplace);
+                if (action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) {
+                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                } else if (queryRestart) {
+                    pkgList = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
+                } else {
+                    Uri uri = intent.getData();
+                    if (uri == null) {
+                        return;
+                    }
+                    String pkgName = uri.getSchemeSpecificPart();
+                    if (pkgName == null) {
+                        return;
+                    }
+                    if (packageChanged) {
+                        // We cancel notifications for packages which have just been disabled
+                        try {
+                            final int enabled = getContext().getPackageManager()
+                                    .getApplicationEnabledSetting(pkgName);
+                            if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                                    || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+                                cancelNotifications = false;
+                            }
+                        } catch (IllegalArgumentException e) {
+                            // Package doesn't exist; probably racing with uninstall.
+                            // cancelNotifications is already true, so nothing to do here.
+                            if (DBG) {
+                                Slog.i(TAG, "Exception trying to look up app enabled setting", e);
+                            }
+                        }
+                    }
+                    pkgList = new String[]{pkgName};
+                }
+
+                boolean anyListenersInvolved = false;
+                if (pkgList != null && (pkgList.length > 0)) {
+                    for (String pkgName : pkgList) {
+                        if (cancelNotifications) {
+                            cancelAllNotificationsInt(pkgName, 0, 0, !queryRestart,
+                                    UserHandle.USER_ALL);
+                        }
+                        if (mEnabledListenerPackageNames.contains(pkgName)) {
+                            anyListenersInvolved = true;
+                        }
+                    }
+                }
+
+                if (anyListenersInvolved) {
+                    // if we're not replacing a package, clean up orphaned bits
+                    if (!queryReplace) {
+                        disableNonexistentListeners();
+                    }
+                    // make sure we're still bound to any of our
+                    // listeners who may have just upgraded
+                    rebindListenerServices();
+                }
+            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                // Keep track of screen on/off state, but do not turn off the notification light
+                // until user passes through the lock screen or views the notification.
+                mScreenOn = true;
+            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                mScreenOn = false;
+            } else if (action.equals(TelephonyManager.ACTION_PHONE_STATE_CHANGED)) {
+                mInCall = (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(
+                        TelephonyManager.EXTRA_STATE_OFFHOOK));
+                updateNotificationPulse();
+            } else if (action.equals(Intent.ACTION_USER_STOPPED)) {
+                int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userHandle >= 0) {
+                    cancelAllNotificationsInt(null, 0, 0, true, userHandle);
+                }
+            } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
+                // turn off LED when user passes through lock screen
+                mNotificationLight.turnOff();
+            } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+                // reload per-user settings
+                mSettingsObserver.update(null);
+            }
+        }
+    };
+
+    class SettingsObserver extends ContentObserver {
+        private final Uri NOTIFICATION_LIGHT_PULSE_URI
+                = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
+
+        private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
+                = Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void observe() {
+            ContentResolver resolver = getContext().getContentResolver();
+            resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
+                    false, this, UserHandle.USER_ALL);
+            resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
+                    false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            ContentResolver resolver = getContext().getContentResolver();
+            if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
+                boolean pulseEnabled = Settings.System.getInt(resolver,
+                            Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+                if (mNotificationPulseEnabled != pulseEnabled) {
+                    mNotificationPulseEnabled = pulseEnabled;
+                    updateNotificationPulse();
+                }
+            }
+            if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
+                rebindListenerServices();
+            }
+        }
+    }
+
+    private SettingsObserver mSettingsObserver;
+
+    static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
+        int[] ar = r.getIntArray(resid);
+        if (ar == null) {
+            return def;
+        }
+        final int len = ar.length > maxlen ? maxlen : ar.length;
+        long[] out = new long[len];
+        for (int i=0; i<len; i++) {
+            out[i] = ar[i];
+        }
+        return out;
+    }
+
+    @Override
+    public void onStart() {
+        mAm = ActivityManagerNative.getDefault();
+        mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
+        mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
+
+        mHandler = new WorkerHandler();
+
+        importOldBlockDb();
+
+        mStatusBar = getLocalService(StatusBarManagerInternal.class);
+        mStatusBar.setNotificationDelegate(mNotificationDelegate);
+
+        final LightsManager lights = getLocalService(LightsManager.class);
+        mNotificationLight = lights.getLight(LightsManager.LIGHT_ID_NOTIFICATIONS);
+        mAttentionLight = lights.getLight(LightsManager.LIGHT_ID_ATTENTION);
+
+        Resources resources = getContext().getResources();
+        mDefaultNotificationColor = resources.getColor(
+                R.color.config_defaultNotificationColor);
+        mDefaultNotificationLedOn = resources.getInteger(
+                R.integer.config_defaultNotificationLedOn);
+        mDefaultNotificationLedOff = resources.getInteger(
+                R.integer.config_defaultNotificationLedOff);
+
+        mDefaultVibrationPattern = getLongArray(resources,
+                R.array.config_defaultNotificationVibePattern,
+                VIBRATE_PATTERN_MAXLEN,
+                DEFAULT_VIBRATE_PATTERN);
+
+        mFallbackVibrationPattern = getLongArray(resources,
+                R.array.config_notificationFallbackVibePattern,
+                VIBRATE_PATTERN_MAXLEN,
+                DEFAULT_VIBRATE_PATTERN);
+
+        // Don't start allowing notifications until the setup wizard has run once.
+        // After that, including subsequent boots, init with notifications turned on.
+        // This works on the first boot because the setup wizard will toggle this
+        // flag at least once and we'll go back to 0 after that.
+        if (0 == Settings.Global.getInt(getContext().getContentResolver(),
+                    Settings.Global.DEVICE_PROVISIONED, 0)) {
+            mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+        }
+
+        // register for various Intents
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
+        filter.addAction(Intent.ACTION_USER_PRESENT);
+        filter.addAction(Intent.ACTION_USER_STOPPED);
+        filter.addAction(Intent.ACTION_USER_SWITCHED);
+        getContext().registerReceiver(mIntentReceiver, filter);
+        IntentFilter pkgFilter = new IntentFilter();
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        pkgFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+        pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);
+        pkgFilter.addDataScheme("package");
+        getContext().registerReceiver(mIntentReceiver, pkgFilter);
+        IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
+        getContext().registerReceiver(mIntentReceiver, sdFilter);
+
+        mSettingsObserver = new SettingsObserver(mHandler);
+        mSettingsObserver.observe();
+
+        // spin up NotificationScorers
+        String[] notificationScorerNames = resources.getStringArray(
+                R.array.config_notificationScorers);
+        for (String scorerName : notificationScorerNames) {
+            try {
+                Class<?> scorerClass = getContext().getClassLoader().loadClass(scorerName);
+                NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance();
+                scorer.initialize(getContext());
+                mScorers.add(scorer);
+            } catch (ClassNotFoundException e) {
+                Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e);
+            } catch (InstantiationException e) {
+                Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e);
+            } catch (IllegalAccessException e) {
+                Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e);
+            }
+        }
+
+        publishBinderService(Context.NOTIFICATION_SERVICE, mService);
+        publishLocalService(NotificationManagerInternal.class, mInternalService);
+    }
+
+    /**
+     * Read the old XML-based app block database and import those blockages into the AppOps system.
+     */
+    private void importOldBlockDb() {
+        loadBlockDb();
+
+        PackageManager pm = getContext().getPackageManager();
+        for (String pkg : mBlockedPackages) {
+            PackageInfo info = null;
+            try {
+                info = pm.getPackageInfo(pkg, 0);
+                setNotificationsEnabledForPackageImpl(pkg, info.applicationInfo.uid, false);
+            } catch (NameNotFoundException e) {
+                // forget you
+            }
+        }
+        mBlockedPackages.clear();
+        if (mPolicyFile != null) {
+            mPolicyFile.delete();
+        }
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            // no beeping until we're basically done booting
+            mSystemReady = true;
+
+            // Grab our optional AudioService
+            mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+
+            // make sure our listener services are properly bound
+            rebindListenerServices();
+        }
+    }
+
+    void setNotificationsEnabledForPackageImpl(String pkg, int uid, boolean enabled) {
+        Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
+
+        mAppOps.setMode(AppOpsManager.OP_POST_NOTIFICATION, uid, pkg,
+                enabled ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
+
+        // Now, cancel any outstanding notifications that are part of a just-disabled app
+        if (ENABLE_BLOCKED_NOTIFICATIONS && !enabled) {
+            cancelAllNotificationsInt(pkg, 0, 0, true, UserHandle.getUserId(uid));
+        }
+    }
+
+    private final IBinder mService = new INotificationManager.Stub() {
+        // Toasts
+        // ============================================================================
+
+        @Override
+        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
+        {
+            if (DBG) {
+                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
+                        + " duration=" + duration);
+            }
+
+            if (pkg == null || callback == null) {
+                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
+                return ;
+            }
+
+            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
+
+            if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid())) {
+                if (!isSystemToast) {
+                    Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
+                    return;
+                }
+            }
+
+            synchronized (mToastQueue) {
+                int callingPid = Binder.getCallingPid();
+                long callingId = Binder.clearCallingIdentity();
+                try {
+                    ToastRecord record;
+                    int index = indexOfToastLocked(pkg, callback);
+                    // If it's already in the queue, we update it in place, we don't
+                    // move it to the end of the queue.
+                    if (index >= 0) {
+                        record = mToastQueue.get(index);
+                        record.update(duration);
+                    } else {
+                        // Limit the number of toasts that any given package except the android
+                        // package can enqueue.  Prevents DOS attacks and deals with leaks.
+                        if (!isSystemToast) {
+                            int count = 0;
+                            final int N = mToastQueue.size();
+                            for (int i=0; i<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);
+        }
+    };
+
+    void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid,
+            final int callingPid, final String tag, final int id, final Notification notification,
+            int[] idOut, int incomingUserId) {
+        if (DBG) {
+            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+                    + " notification=" + notification);
+        }
+        checkCallerIsSystemOrSameApp(pkg);
+        final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
+
+        final int userId = ActivityManager.handleIncomingUser(callingPid,
+                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
+        final UserHandle user = new UserHandle(userId);
+
+        // Limit the number of notifications that any given package except the android
+        // package can enqueue.  Prevents DOS attacks and deals with leaks.
+        if (!isSystemNotification) {
+            synchronized (mNotificationList) {
+                int count = 0;
+                final int N = mNotificationList.size();
+                for (int i=0; i<N; i++) {
+                    final NotificationRecord r = mNotificationList.get(i);
+                    if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
+                        count++;
+                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
+                            Slog.e(TAG, "Package has already posted " + count
+                                    + " notifications.  Not showing more.  package=" + pkg);
+                            return;
+                        }
+                    }
+                }
+            }
+        }
+
+        // This conditional is a dirty hack to limit the logging done on
+        //     behalf of the download manager without affecting other apps.
+        if (!pkg.equals("com.android.providers.downloads")
+                || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
+            EventLog.writeEvent(EventLogTags.NOTIFICATION_ENQUEUE, pkg, id, tag, userId,
+                    notification.toString());
+        }
+
+        if (pkg == null || notification == null) {
+            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+                    + " id=" + id + " notification=" + notification);
+        }
+        if (notification.icon != 0) {
+            if (notification.contentView == null) {
+                throw new IllegalArgumentException("contentView required: pkg=" + pkg
+                        + " id=" + id + " notification=" + notification);
+            }
+        }
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+
+                // === Scoring ===
+
+                // 0. Sanitize inputs
+                notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
+                        Notification.PRIORITY_MAX);
+                // Migrate notification flags to scores
+                if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
+                    if (notification.priority < Notification.PRIORITY_MAX) {
+                        notification.priority = Notification.PRIORITY_MAX;
+                    }
+                } else if (SCORE_ONGOING_HIGHER &&
+                        0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
+                    if (notification.priority < Notification.PRIORITY_HIGH) {
+                        notification.priority = Notification.PRIORITY_HIGH;
+                    }
+                }
+
+                // 1. initial score: buckets of 10, around the app
+                int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20]
+
+                // 2. Consult external heuristics (TBD)
+
+                // 3. Apply local rules
+
+                int initialScore = score;
+                if (!mScorers.isEmpty()) {
+                    if (DBG) Slog.v(TAG, "Initial score is " + score + ".");
+                    for (NotificationScorer scorer : mScorers) {
+                        try {
+                            score = scorer.getScore(notification, score);
+                        } catch (Throwable t) {
+                            Slog.w(TAG, "Scorer threw on .getScore.", t);
+                        }
+                    }
+                    if (DBG) Slog.v(TAG, "Final score is " + score + ".");
+                }
+
+                // add extra to indicate score modified by NotificationScorer
+                notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED,
+                        score != initialScore);
+
+                // blocked apps
+                if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
+                    if (!isSystemNotification) {
+                        score = JUNK_SCORE;
+                        Slog.e(TAG, "Suppressing notification from package " + pkg
+                                + " by user request.");
+                    }
+                }
+
+                if (DBG) {
+                    Slog.v(TAG, "Assigned score=" + score + " to " + notification);
+                }
+
+                if (score < SCORE_DISPLAY_THRESHOLD) {
+                    // Notification will be blocked because the score is too low.
+                    return;
+                }
+
+                // Should this notification make noise, vibe, or use the LED?
+                final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD);
+
+                synchronized (mNotificationList) {
+                    final StatusBarNotification n = new StatusBarNotification(
+                            pkg, id, tag, callingUid, callingPid, score, notification, user);
+                    NotificationRecord r = new NotificationRecord(n);
+                    NotificationRecord old = null;
+
+                    int index = indexOfNotificationLocked(pkg, tag, id, userId);
+                    if (index < 0) {
+                        mNotificationList.add(r);
+                    } else {
+                        old = mNotificationList.remove(index);
+                        mNotificationList.add(index, r);
+                        // Make sure we don't lose the foreground service state.
+                        if (old != null) {
+                            notification.flags |=
+                                old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
+                        }
+                    }
+
+                    // Ensure if this is a foreground service that the proper additional
+                    // flags are set.
+                    if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+                        notification.flags |= Notification.FLAG_ONGOING_EVENT
+                                | Notification.FLAG_NO_CLEAR;
+                    }
+
+                    final int currentUser;
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        currentUser = ActivityManager.getCurrentUser();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+
+                    if (notification.icon != 0) {
+                        if (old != null && old.statusBarKey != null) {
+                            r.statusBarKey = old.statusBarKey;
+                            final long identity = Binder.clearCallingIdentity();
+                            try {
+                                mStatusBar.updateNotification(r.statusBarKey, n);
+                            } finally {
+                                Binder.restoreCallingIdentity(identity);
+                            }
+                        } else {
+                            final long identity = Binder.clearCallingIdentity();
+                            try {
+                                r.statusBarKey = mStatusBar.addNotification(n);
+                                if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0
+                                        && canInterrupt) {
+                                    mAttentionLight.pulse();
+                                }
+                            } finally {
+                                Binder.restoreCallingIdentity(identity);
+                            }
+                        }
+                        // Send accessibility events only for the current user.
+                        if (currentUser == userId) {
+                            sendAccessibilityEvent(notification, pkg);
+                        }
+
+                        notifyPostedLocked(r);
+                    } else {
+                        Slog.e(TAG, "Not posting notification with icon==0: " + notification);
+                        if (old != null && old.statusBarKey != null) {
+                            final long identity = Binder.clearCallingIdentity();
+                            try {
+                                mStatusBar.removeNotification(old.statusBarKey);
+                            } finally {
+                                Binder.restoreCallingIdentity(identity);
+                            }
+
+                            notifyRemovedLocked(r);
+                        }
+                        // ATTENTION: in a future release we will bail out here
+                        // so that we do not play sounds, show lights, etc. for invalid
+                        // notifications
+                        Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+                                + n.getPackageName());
+                    }
+
+                    // If we're not supposed to beep, vibrate, etc. then don't.
+                    if (((mDisabledNotifications
+                            & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
+                            && (!(old != null
+                                && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
+                            && (r.getUserId() == UserHandle.USER_ALL ||
+                                (r.getUserId() == userId && r.getUserId() == currentUser))
+                            && canInterrupt
+                            && mSystemReady
+                            && mAudioManager != null) {
+
+                        // sound
+
+                        // should we use the default notification sound? (indicated either by
+                        // DEFAULT_SOUND or because notification.sound is pointing at
+                        // Settings.System.NOTIFICATION_SOUND)
+                        final boolean useDefaultSound =
+                               (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
+                                       Settings.System.DEFAULT_NOTIFICATION_URI
+                                               .equals(notification.sound);
+
+                        Uri soundUri = null;
+                        boolean hasValidSound = false;
+
+                        if (useDefaultSound) {
+                            soundUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+
+                            // check to see if the default notification sound is silent
+                            ContentResolver resolver = getContext().getContentResolver();
+                            hasValidSound = Settings.System.getString(resolver,
+                                   Settings.System.NOTIFICATION_SOUND) != null;
+                        } else if (notification.sound != null) {
+                            soundUri = notification.sound;
+                            hasValidSound = (soundUri != null);
+                        }
+
+                        if (hasValidSound) {
+                            boolean looping =
+                                    (notification.flags & Notification.FLAG_INSISTENT) != 0;
+                            int audioStreamType;
+                            if (notification.audioStreamType >= 0) {
+                                audioStreamType = notification.audioStreamType;
+                            } else {
+                                audioStreamType = DEFAULT_STREAM_TYPE;
+                            }
+                            mSoundNotification = r;
+                            // do not play notifications if stream volume is 0 (typically because
+                            // ringer mode is silent) or if there is a user of exclusive audio focus
+                            if ((mAudioManager.getStreamVolume(audioStreamType) != 0)
+                                    && !mAudioManager.isAudioFocusExclusive()) {
+                                final long identity = Binder.clearCallingIdentity();
+                                try {
+                                    final IRingtonePlayer player =
+                                            mAudioManager.getRingtonePlayer();
+                                    if (player != null) {
+                                        player.playAsync(soundUri, user, looping, audioStreamType);
+                                    }
+                                } catch (RemoteException e) {
+                                } finally {
+                                    Binder.restoreCallingIdentity(identity);
+                                }
+                            }
+                        }
+
+                        // vibrate
+                        // Does the notification want to specify its own vibration?
+                        final boolean hasCustomVibrate = notification.vibrate != null;
+
+                        // new in 4.2: if there was supposed to be a sound and we're in vibrate
+                        // mode, and no other vibration is specified, we fall back to vibration
+                        final boolean convertSoundToVibration =
+                                   !hasCustomVibrate
+                                && hasValidSound
+                                && (mAudioManager.getRingerMode()
+                                           == AudioManager.RINGER_MODE_VIBRATE);
+
+                        // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback.
+                        final boolean useDefaultVibrate =
+                                (notification.defaults & Notification.DEFAULT_VIBRATE) != 0;
+
+                        if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate)
+                                && !(mAudioManager.getRingerMode()
+                                        == AudioManager.RINGER_MODE_SILENT)) {
+                            mVibrateNotification = r;
+
+                            if (useDefaultVibrate || convertSoundToVibration) {
+                                // Escalate privileges so we can use the vibrator even if the
+                                // notifying app does not have the VIBRATE permission.
+                                long identity = Binder.clearCallingIdentity();
+                                try {
+                                    mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
+                                        useDefaultVibrate ? mDefaultVibrationPattern
+                                            : mFallbackVibrationPattern,
+                                        ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+                                                ? 0: -1);
+                                } finally {
+                                    Binder.restoreCallingIdentity(identity);
+                                }
+                            } else if (notification.vibrate.length > 1) {
+                                // If you want your own vibration pattern, you need the VIBRATE
+                                // permission
+                                mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
+                                        notification.vibrate,
+                                    ((notification.flags & Notification.FLAG_INSISTENT) != 0)
+                                            ? 0: -1);
+                            }
+                        }
+                    }
+
+                    // light
+                    // the most recent thing gets the light
+                    mLights.remove(old);
+                    if (mLedNotification == old) {
+                        mLedNotification = null;
+                    }
+                    //Slog.i(TAG, "notification.lights="
+                    //        + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS)
+                    //                  != 0));
+                    if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0
+                            && canInterrupt) {
+                        mLights.add(r);
+                        updateLightsLocked();
+                    } else {
+                        if (old != null
+                                && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) {
+                            updateLightsLocked();
+                        }
+                    }
+                }
+            }
+        });
+
+        idOut[0] = id;
+    }
+
+     void registerListenerImpl(final INotificationListener listener,
+            final ComponentName component, final int userid) {
+        synchronized (mNotificationList) {
+            try {
+                NotificationListenerInfo info
+                        = new NotificationListenerInfo(listener, component, userid, true);
+                listener.asBinder().linkToDeath(info, 0);
+                mListeners.add(info);
+            } catch (RemoteException e) {
+                // already dead
+            }
+        }
+    }
+
+    void unregisterListenerImpl(final INotificationListener listener, final int userid) {
+        synchronized (mNotificationList) {
+            final int N = mListeners.size();
+            for (int i=N-1; i>=0; i--) {
+                final NotificationListenerInfo info = mListeners.get(i);
+                if (info.listener.asBinder() == listener.asBinder()
+                        && info.userid == userid) {
+                    mListeners.remove(i);
+                    if (info.connection != null) {
+                        getContext().unbindService(info.connection);
+                    }
+                }
+            }
+        }
+    }
+
+    void showNextToastLocked() {
+        ToastRecord record = mToastQueue.get(0);
+        while (record != null) {
+            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
+            try {
+                record.callback.show();
+                scheduleTimeoutLocked(record);
+                return;
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Object died trying to show notification " + record.callback
+                        + " in package " + record.pkg);
+                // remove it from the list and let the process die
+                int index = mToastQueue.indexOf(record);
+                if (index >= 0) {
+                    mToastQueue.remove(index);
+                }
+                keepProcessAliveLocked(record.pid);
+                if (mToastQueue.size() > 0) {
+                    record = mToastQueue.get(0);
+                } else {
+                    record = null;
+                }
+            }
+        }
+    }
+
+    void cancelToastLocked(int index) {
+        ToastRecord record = mToastQueue.get(index);
+        try {
+            record.callback.hide();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Object died trying to hide notification " + record.callback
+                    + " in package " + record.pkg);
+            // don't worry about this, we're about to remove it from
+            // the list anyway
+        }
+        mToastQueue.remove(index);
+        keepProcessAliveLocked(record.pid);
+        if (mToastQueue.size() > 0) {
+            // Show the next one. If the callback fails, this will remove
+            // it from the list, so don't assume that the list hasn't changed
+            // after this point.
+            showNextToastLocked();
+        }
+    }
+
+    private void scheduleTimeoutLocked(ToastRecord r)
+    {
+        mHandler.removeCallbacksAndMessages(r);
+        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
+        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
+        mHandler.sendMessageDelayed(m, delay);
+    }
+
+    private void handleTimeout(ToastRecord record)
+    {
+        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
+        synchronized (mToastQueue) {
+            int index = indexOfToastLocked(record.pkg, record.callback);
+            if (index >= 0) {
+                cancelToastLocked(index);
+            }
+        }
+    }
+
+    // lock on mToastQueue
+    int indexOfToastLocked(String pkg, ITransientNotification callback)
+    {
+        IBinder cbak = callback.asBinder();
+        ArrayList<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;
+        }
+
+        AccessibilityEvent event =
+            AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
+        event.setPackageName(packageName);
+        event.setClassName(Notification.class.getName());
+        event.setParcelableData(notification);
+        CharSequence tickerText = notification.tickerText;
+        if (!TextUtils.isEmpty(tickerText)) {
+            event.getText().add(tickerText);
+        }
+
+        manager.sendAccessibilityEvent(event);
+    }
+
+    private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete) {
+        // tell the app
+        if (sendDelete) {
+            if (r.getNotification().deleteIntent != null) {
+                try {
+                    r.getNotification().deleteIntent.send();
+                } catch (PendingIntent.CanceledException ex) {
+                    // do nothing - there's no relevant way to recover, and
+                    //     no reason to let this propagate
+                    Slog.w(TAG, "canceled PendingIntent for " + r.sbn.getPackageName(), ex);
+                }
+            }
+        }
+
+        // status bar
+        if (r.getNotification().icon != 0) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mStatusBar.removeNotification(r.statusBarKey);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            r.statusBarKey = null;
+            notifyRemovedLocked(r);
+        }
+
+        // sound
+        if (mSoundNotification == r) {
+            mSoundNotification = null;
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
+                if (player != null) {
+                    player.stopAsync();
+                }
+            } catch (RemoteException e) {
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        // vibrate
+        if (mVibrateNotification == r) {
+            mVibrateNotification = null;
+            long identity = Binder.clearCallingIdentity();
+            try {
+                mVibrator.cancel();
+            }
+            finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        // light
+        mLights.remove(r);
+        if (mLedNotification == r) {
+            mLedNotification = null;
+        }
+
+        // Save it for users of getHistoricalNotifications()
+        mArchive.record(r.sbn);
+    }
+
+    /**
+     * Cancels a notification ONLY if it has all of the {@code mustHaveFlags}
+     * and none of the {@code mustNotHaveFlags}.
+     */
+    void cancelNotification(final String pkg, final String tag, final int id,
+            final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
+            final int userId) {
+        // In enqueueNotificationInternal notifications are added by scheduling the
+        // work on the worker handler. Hence, we also schedule the cancel on this
+        // handler to avoid a scenario where an add notification call followed by a
+        // remove notification call ends up in not removing the notification.
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId,
+                        mustHaveFlags, mustNotHaveFlags);
+
+                synchronized (mNotificationList) {
+                    int index = indexOfNotificationLocked(pkg, tag, id, userId);
+                    if (index >= 0) {
+                        NotificationRecord r = mNotificationList.get(index);
+
+                        if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
+                            return;
+                        }
+                        if ((r.getNotification().flags & mustNotHaveFlags) != 0) {
+                            return;
+                        }
+
+                        mNotificationList.remove(index);
+
+                        cancelNotificationLocked(r, sendDelete);
+                        updateLightsLocked();
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Determine whether the userId applies to the notification in question, either because
+     * they match exactly, or one of them is USER_ALL (which is treated as a wildcard).
+     */
+    private boolean notificationMatchesUserId(NotificationRecord r, int userId) {
+        return
+                // looking for USER_ALL notifications? match everything
+                   userId == UserHandle.USER_ALL
+                // a notification sent to USER_ALL matches any query
+                || r.getUserId() == UserHandle.USER_ALL
+                // an exact user match
+                || r.getUserId() == userId;
+    }
+
+    /**
+     * Cancels all notifications from a given package that have all of the
+     * {@code mustHaveFlags}.
+     */
+    boolean cancelAllNotificationsInt(String pkg, int mustHaveFlags,
+            int mustNotHaveFlags, boolean doit, int userId) {
+        EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL_ALL, pkg, userId,
+                mustHaveFlags, mustNotHaveFlags);
+
+        synchronized (mNotificationList) {
+            final int N = mNotificationList.size();
+            boolean canceledSomething = false;
+            for (int i = N-1; i >= 0; --i) {
+                NotificationRecord r = mNotificationList.get(i);
+                if (!notificationMatchesUserId(r, userId)) {
+                    continue;
+                }
+                // Don't remove notifications to all, if there's no package name specified
+                if (r.getUserId() == UserHandle.USER_ALL && pkg == null) {
+                    continue;
+                }
+                if ((r.getFlags() & mustHaveFlags) != mustHaveFlags) {
+                    continue;
+                }
+                if ((r.getFlags() & mustNotHaveFlags) != 0) {
+                    continue;
+                }
+                if (pkg != null && !r.sbn.getPackageName().equals(pkg)) {
+                    continue;
+                }
+                canceledSomething = true;
+                if (!doit) {
+                    return true;
+                }
+                mNotificationList.remove(i);
+                cancelNotificationLocked(r, false);
+            }
+            if (canceledSomething) {
+                updateLightsLocked();
+            }
+            return canceledSomething;
+        }
+    }
+
+
+
+    // Return true if the UID is a system or phone UID and therefore should not have
+    // any notifications or toasts blocked.
+    boolean isUidSystem(int uid) {
+        final int appid = UserHandle.getAppId(uid);
+        return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);
+    }
+
+    // same as isUidSystem(int, int) for the Binder caller's UID.
+    boolean isCallerSystem() {
+        return isUidSystem(Binder.getCallingUid());
+    }
+
+    void checkCallerIsSystem() {
+        if (isCallerSystem()) {
+            return;
+        }
+        throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid());
+    }
+
+    void checkCallerIsSystemOrSameApp(String pkg) {
+        if (isCallerSystem()) {
+            return;
+        }
+        final int uid = Binder.getCallingUid();
+        try {
+            ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
+                    pkg, 0, UserHandle.getCallingUserId());
+            if (!UserHandle.isSameApp(ai.uid, uid)) {
+                throw new SecurityException("Calling uid " + uid + " gave package"
+                        + pkg + " which is owned by uid " + ai.uid);
+            }
+        } catch (RemoteException re) {
+            throw new SecurityException("Unknown package " + pkg + "\n" + re);
+        }
+    }
+
+    void cancelAll(int userId) {
+        synchronized (mNotificationList) {
+            final int N = mNotificationList.size();
+            for (int i=N-1; i>=0; i--) {
+                NotificationRecord r = mNotificationList.get(i);
+
+                if (!notificationMatchesUserId(r, userId)) {
+                    continue;
+                }
+
+                if ((r.getFlags() & (Notification.FLAG_ONGOING_EVENT
+                                | Notification.FLAG_NO_CLEAR)) == 0) {
+                    mNotificationList.remove(i);
+                    cancelNotificationLocked(r, true);
+                }
+            }
+
+            updateLightsLocked();
+        }
+    }
+
+    // lock on mNotificationList
+    void updateLightsLocked()
+    {
+        // handle notification lights
+        if (mLedNotification == null) {
+            // get next notification, if any
+            int n = mLights.size();
+            if (n > 0) {
+                mLedNotification = mLights.get(n-1);
+            }
+        }
+
+        // Don't flash while we are in a call or screen is on
+        if (mLedNotification == null || mInCall || mScreenOn) {
+            mNotificationLight.turnOff();
+        } else {
+            final Notification ledno = mLedNotification.sbn.getNotification();
+            int ledARGB = ledno.ledARGB;
+            int ledOnMS = ledno.ledOnMS;
+            int ledOffMS = ledno.ledOffMS;
+            if ((ledno.defaults & Notification.DEFAULT_LIGHTS) != 0) {
+                ledARGB = mDefaultNotificationColor;
+                ledOnMS = mDefaultNotificationLedOn;
+                ledOffMS = mDefaultNotificationLedOff;
+            }
+            if (mNotificationPulseEnabled) {
+                // pulse repeatedly
+                mNotificationLight.setFlashing(ledARGB, Light.LIGHT_FLASH_TIMED,
+                        ledOnMS, ledOffMS);
+            }
+        }
+    }
+
+    // lock on mNotificationList
+    int indexOfNotificationLocked(String pkg, String tag, int id, int userId)
+    {
+        ArrayList<NotificationRecord> list = mNotificationList;
+        final int len = list.size();
+        for (int i=0; i<len; i++) {
+            NotificationRecord r = list.get(i);
+            if (!notificationMatchesUserId(r, userId) || r.sbn.getId() != id) {
+                continue;
+            }
+            if (tag == null) {
+                if (r.sbn.getTag() != null) {
+                    continue;
+                }
+            } else {
+                if (!tag.equals(r.sbn.getTag())) {
+                    continue;
+                }
+            }
+            if (r.sbn.getPackageName().equals(pkg)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private void updateNotificationPulse() {
+        synchronized (mNotificationList) {
+            updateLightsLocked();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
new file mode 100644
index 0000000..c0123bf
--- /dev/null
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.os;
+
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.ISchedulingPolicyService;
+import android.os.Process;
+
+/**
+ * The implementation of the scheduling policy service interface.
+ *
+ * @hide
+ */
+public class SchedulingPolicyService extends ISchedulingPolicyService.Stub {
+
+    private static final String TAG = "SchedulingPolicyService";
+
+    // Minimum and maximum values allowed for requestPriority parameter prio
+    private static final int PRIORITY_MIN = 1;
+    private static final int PRIORITY_MAX = 3;
+
+    public SchedulingPolicyService() {
+    }
+
+    public int requestPriority(int pid, int tid, int prio) {
+        //Log.i(TAG, "requestPriority(pid=" + pid + ", tid=" + tid + ", prio=" + prio + ")");
+
+        // Verify that caller is mediaserver, priority is in range, and that the
+        // callback thread specified by app belongs to the app that called mediaserver.
+        // Once we've verified that the caller is mediaserver, we can trust the pid but
+        // we can't trust the tid.  No need to explicitly check for pid == 0 || tid == 0,
+        // since if not the case then the getThreadGroupLeader() test will also fail.
+        if (Binder.getCallingUid() != Process.MEDIA_UID || prio < PRIORITY_MIN ||
+                prio > PRIORITY_MAX || Process.getThreadGroupLeader(tid) != pid) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+        try {
+            // make good use of our CAP_SYS_NICE capability
+            Process.setThreadGroup(tid, Binder.getCallingPid() == pid ?
+                    Process.THREAD_GROUP_AUDIO_SYS : Process.THREAD_GROUP_AUDIO_APP);
+            // must be in this order or it fails the schedulability constraint
+            Process.setThreadScheduler(tid, Process.SCHED_FIFO, prio);
+        } catch (RuntimeException e) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+        return PackageManager.PERMISSION_GRANTED;
+    }
+
+}
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
new file mode 100644
index 0000000..4f27408
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2006 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.pm;
+
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionInfo;
+
+final class BasePermission {
+    final static int TYPE_NORMAL = 0;
+
+    final static int TYPE_BUILTIN = 1;
+
+    final static int TYPE_DYNAMIC = 2;
+
+    final String name;
+
+    String sourcePackage;
+
+    PackageSettingBase packageSetting;
+
+    final int type;
+
+    int protectionLevel;
+
+    PackageParser.Permission perm;
+
+    PermissionInfo pendingInfo;
+
+    int uid;
+
+    int[] gids;
+
+    BasePermission(String _name, String _sourcePackage, int _type) {
+        name = _name;
+        sourcePackage = _sourcePackage;
+        type = _type;
+        // Default to most conservative protection level.
+        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    public String toString() {
+        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+                + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/pm/GrantedPermissions.java b/services/core/java/com/android/server/pm/GrantedPermissions.java
new file mode 100644
index 0000000..14258a4
--- /dev/null
+++ b/services/core/java/com/android/server/pm/GrantedPermissions.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+
+import java.util.HashSet;
+
+class GrantedPermissions {
+    int pkgFlags;
+
+    HashSet<String> grantedPermissions = new HashSet<String>();
+
+    int[] gids;
+
+    GrantedPermissions(int pkgFlags) {
+        setFlags(pkgFlags);
+    }
+
+    @SuppressWarnings("unchecked")
+    GrantedPermissions(GrantedPermissions base) {
+        pkgFlags = base.pkgFlags;
+        grantedPermissions = (HashSet<String>) base.grantedPermissions.clone();
+
+        if (base.gids != null) {
+            gids = base.gids.clone();
+        }
+    }
+
+    void setFlags(int pkgFlags) {
+        this.pkgFlags = pkgFlags
+                & (ApplicationInfo.FLAG_SYSTEM
+                        | ApplicationInfo.FLAG_PRIVILEGED
+                        | ApplicationInfo.FLAG_FORWARD_LOCK
+                        | ApplicationInfo.FLAG_EXTERNAL_STORAGE);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
new file mode 100644
index 0000000..0d2b503
--- /dev/null
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageStats;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public final class Installer {
+    private static final String TAG = "Installer";
+
+    private static final boolean LOCAL_DEBUG = false;
+
+    InputStream mIn;
+
+    OutputStream mOut;
+
+    LocalSocket mSocket;
+
+    byte buf[] = new byte[1024];
+
+    int buflen = 0;
+
+    private boolean connect() {
+        if (mSocket != null) {
+            return true;
+        }
+        Slog.i(TAG, "connecting...");
+        try {
+            mSocket = new LocalSocket();
+
+            LocalSocketAddress address = new LocalSocketAddress("installd",
+                    LocalSocketAddress.Namespace.RESERVED);
+
+            mSocket.connect(address);
+
+            mIn = mSocket.getInputStream();
+            mOut = mSocket.getOutputStream();
+        } catch (IOException ex) {
+            disconnect();
+            return false;
+        }
+        return true;
+    }
+
+    private void disconnect() {
+        Slog.i(TAG, "disconnecting...");
+        try {
+            if (mSocket != null)
+                mSocket.close();
+        } catch (IOException ex) {
+        }
+        try {
+            if (mIn != null)
+                mIn.close();
+        } catch (IOException ex) {
+        }
+        try {
+            if (mOut != null)
+                mOut.close();
+        } catch (IOException ex) {
+        }
+        mSocket = null;
+        mIn = null;
+        mOut = null;
+    }
+
+    private boolean readBytes(byte buffer[], int len) {
+        int off = 0, count;
+        if (len < 0)
+            return false;
+        while (off != len) {
+            try {
+                count = mIn.read(buffer, off, len - off);
+                if (count <= 0) {
+                    Slog.e(TAG, "read error " + count);
+                    break;
+                }
+                off += count;
+            } catch (IOException ex) {
+                Slog.e(TAG, "read exception");
+                break;
+            }
+        }
+        if (LOCAL_DEBUG) {
+            Slog.i(TAG, "read " + len + " bytes");
+        }
+        if (off == len)
+            return true;
+        disconnect();
+        return false;
+    }
+
+    private boolean readReply() {
+        int len;
+        buflen = 0;
+        if (!readBytes(buf, 2))
+            return false;
+        len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
+        if ((len < 1) || (len > 1024)) {
+            Slog.e(TAG, "invalid reply length (" + len + ")");
+            disconnect();
+            return false;
+        }
+        if (!readBytes(buf, len))
+            return false;
+        buflen = len;
+        return true;
+    }
+
+    private boolean writeCommand(String _cmd) {
+        byte[] cmd = _cmd.getBytes();
+        int len = cmd.length;
+        if ((len < 1) || (len > 1024))
+            return false;
+        buf[0] = (byte) (len & 0xff);
+        buf[1] = (byte) ((len >> 8) & 0xff);
+        try {
+            mOut.write(buf, 0, 2);
+            mOut.write(cmd, 0, len);
+        } catch (IOException ex) {
+            Slog.e(TAG, "write error");
+            disconnect();
+            return false;
+        }
+        return true;
+    }
+
+    private synchronized String transaction(String cmd) {
+        if (!connect()) {
+            Slog.e(TAG, "connection failed");
+            return "-1";
+        }
+
+        if (!writeCommand(cmd)) {
+            /*
+             * If installd died and restarted in the background (unlikely but
+             * possible) we'll fail on the next write (this one). Try to
+             * reconnect and write the command one more time before giving up.
+             */
+            Slog.e(TAG, "write command failed? reconnect!");
+            if (!connect() || !writeCommand(cmd)) {
+                return "-1";
+            }
+        }
+        if (LOCAL_DEBUG) {
+            Slog.i(TAG, "send: '" + cmd + "'");
+        }
+        if (readReply()) {
+            String s = new String(buf, 0, buflen);
+            if (LOCAL_DEBUG) {
+                Slog.i(TAG, "recv: '" + s + "'");
+            }
+            return s;
+        } else {
+            if (LOCAL_DEBUG) {
+                Slog.i(TAG, "fail");
+            }
+            return "-1";
+        }
+    }
+
+    private int execute(String cmd) {
+        String res = transaction(cmd);
+        try {
+            return Integer.parseInt(res);
+        } catch (NumberFormatException ex) {
+            return -1;
+        }
+    }
+
+    public int install(String name, int uid, int gid, String seinfo) {
+        StringBuilder builder = new StringBuilder("install");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(' ');
+        builder.append(gid);
+        builder.append(' ');
+        builder.append(seinfo != null ? seinfo : "!");
+        return execute(builder.toString());
+    }
+
+    public int dexopt(String apkPath, int uid, boolean isPublic) {
+        StringBuilder builder = new StringBuilder("dexopt");
+        builder.append(' ');
+        builder.append(apkPath);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(isPublic ? " 1" : " 0");
+        return execute(builder.toString());
+    }
+
+    public int movedex(String srcPath, String dstPath) {
+        StringBuilder builder = new StringBuilder("movedex");
+        builder.append(' ');
+        builder.append(srcPath);
+        builder.append(' ');
+        builder.append(dstPath);
+        return execute(builder.toString());
+    }
+
+    public int rmdex(String codePath) {
+        StringBuilder builder = new StringBuilder("rmdex");
+        builder.append(' ');
+        builder.append(codePath);
+        return execute(builder.toString());
+    }
+
+    public int remove(String name, int userId) {
+        StringBuilder builder = new StringBuilder("remove");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int rename(String oldname, String newname) {
+        StringBuilder builder = new StringBuilder("rename");
+        builder.append(' ');
+        builder.append(oldname);
+        builder.append(' ');
+        builder.append(newname);
+        return execute(builder.toString());
+    }
+
+    public int fixUid(String name, int uid, int gid) {
+        StringBuilder builder = new StringBuilder("fixuid");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(' ');
+        builder.append(gid);
+        return execute(builder.toString());
+    }
+
+    public int deleteCacheFiles(String name, int userId) {
+        StringBuilder builder = new StringBuilder("rmcache");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int createUserData(String name, int uid, int userId, String seinfo) {
+        StringBuilder builder = new StringBuilder("mkuserdata");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(uid);
+        builder.append(' ');
+        builder.append(userId);
+        builder.append(' ');
+        builder.append(seinfo != null ? seinfo : "!");
+        return execute(builder.toString());
+    }
+
+    public int removeUserDataDirs(int userId) {
+        StringBuilder builder = new StringBuilder("rmuser");
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public int clearUserData(String name, int userId) {
+        StringBuilder builder = new StringBuilder("rmuserdata");
+        builder.append(' ');
+        builder.append(name);
+        builder.append(' ');
+        builder.append(userId);
+        return execute(builder.toString());
+    }
+
+    public boolean ping() {
+        if (execute("ping") < 0) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    public int freeCache(long freeStorageSize) {
+        StringBuilder builder = new StringBuilder("freecache");
+        builder.append(' ');
+        builder.append(String.valueOf(freeStorageSize));
+        return execute(builder.toString());
+    }
+
+    public int getSizeInfo(String pkgName, int persona, String apkPath, String libDirPath,
+            String fwdLockApkPath, String asecPath, PackageStats pStats) {
+        StringBuilder builder = new StringBuilder("getsize");
+        builder.append(' ');
+        builder.append(pkgName);
+        builder.append(' ');
+        builder.append(persona);
+        builder.append(' ');
+        builder.append(apkPath);
+        builder.append(' ');
+        builder.append(libDirPath != null ? libDirPath : "!");
+        builder.append(' ');
+        builder.append(fwdLockApkPath != null ? fwdLockApkPath : "!");
+        builder.append(' ');
+        builder.append(asecPath != null ? asecPath : "!");
+
+        String s = transaction(builder.toString());
+        String res[] = s.split(" ");
+
+        if ((res == null) || (res.length != 5)) {
+            return -1;
+        }
+        try {
+            pStats.codeSize = Long.parseLong(res[1]);
+            pStats.dataSize = Long.parseLong(res[2]);
+            pStats.cacheSize = Long.parseLong(res[3]);
+            pStats.externalCodeSize = Long.parseLong(res[4]);
+            return Integer.parseInt(res[0]);
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    public int moveFiles() {
+        return execute("movefiles");
+    }
+
+    /**
+     * Links the native library directory in an application's directory to its
+     * real location.
+     *
+     * @param dataPath data directory where the application is
+     * @param nativeLibPath target native library path
+     * @return -1 on error
+     */
+    public int linkNativeLibraryDirectory(String dataPath, String nativeLibPath, int userId) {
+        if (dataPath == null) {
+            Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
+            return -1;
+        } else if (nativeLibPath == null) {
+            Slog.e(TAG, "linkNativeLibraryDirectory nativeLibPath is null");
+            return -1;
+        }
+
+        StringBuilder builder = new StringBuilder("linklib ");
+        builder.append(dataPath);
+        builder.append(' ');
+        builder.append(nativeLibPath);
+        builder.append(' ');
+        builder.append(userId);
+
+        return execute(builder.toString());
+    }
+}
diff --git a/services/core/java/com/android/server/pm/KeySetManager.java b/services/core/java/com/android/server/pm/KeySetManager.java
new file mode 100644
index 0000000..66dc1d1
--- /dev/null
+++ b/services/core/java/com/android/server/pm/KeySetManager.java
@@ -0,0 +1,586 @@
+/*
+ * 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.pm;
+
+import android.content.pm.KeySet;
+import android.content.pm.PackageParser;
+import android.os.Binder;
+import android.util.Base64;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+/*
+ * Manages system-wide KeySet state.
+ */
+public class KeySetManager {
+
+    static final String TAG = "KeySetManager";
+
+    /** Sentinel value returned when a {@code KeySet} is not found. */
+    public static final long KEYSET_NOT_FOUND = -1;
+
+    /** Sentinel value returned when public key is not found. */
+    private static final long PUBLIC_KEY_NOT_FOUND = -1;
+
+    private final Object mLockObject = new Object();
+
+    private final LongSparseArray<KeySet> mKeySets;
+
+    private final LongSparseArray<PublicKey> mPublicKeys;
+
+    private final LongSparseArray<Set<Long>> mKeySetMapping;
+
+    private final Map<String, PackageSetting> mPackages;
+
+    private static long lastIssuedKeySetId = 0;
+
+    private static long lastIssuedKeyId = 0;
+
+    public KeySetManager(Map<String, PackageSetting> packages) {
+        mKeySets = new LongSparseArray<KeySet>();
+        mPublicKeys = new LongSparseArray<PublicKey>();
+        mKeySetMapping = new LongSparseArray<Set<Long>>();
+        mPackages = packages;
+    }
+
+    /**
+     * Determine if a package is signed by the given KeySet.
+     *
+     * Returns false if the package was not signed by all the
+     * keys in the KeySet.
+     *
+     * Returns true if the package was signed by at least the
+     * keys in the given KeySet.
+     *
+     * Note that this can return true for multiple KeySets.
+     */
+    public boolean packageIsSignedBy(String packageName, KeySet ks) {
+        synchronized (mLockObject) {
+            PackageSetting pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                throw new NullPointerException("Invalid package name");
+            }
+            if (pkg.keySetData == null) {
+                throw new NullPointerException("Package has no KeySet data");
+            }
+            long id = getIdByKeySetLocked(ks);
+            return pkg.keySetData.packageIsSignedBy(id);
+        }
+    }
+
+    /**
+     * This informs the system that the given package has defined a KeySet
+     * in its manifest that a) contains the given keys and b) is named
+     * alias by that package.
+     */
+    public void addDefinedKeySetToPackage(String packageName,
+            Set<PublicKey> keys, String alias) {
+        if ((packageName == null) || (keys == null) || (alias == null)) {
+            //Log.d(TAG, "Got null argument for a defined keyset, ignoring!");
+            return;
+        }
+        synchronized (mLockObject) {
+            KeySet ks = addKeySetLocked(keys);
+            PackageSetting pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                throw new NullPointerException("Unknown package");
+            }
+            long id = getIdByKeySetLocked(ks);
+            pkg.keySetData.addDefinedKeySet(id, alias);
+        }
+    }
+
+    /**
+     * Similar to the above, this informs the system that the given package
+     * was signed by the provided KeySet.
+     */
+    public void addSigningKeySetToPackage(String packageName,
+            Set<PublicKey> signingKeys) {
+        if ((packageName == null) || (signingKeys == null)) {
+            //Log.d(TAG, "Got null argument for a signing keyset, ignoring!");
+            return;
+        }
+        synchronized (mLockObject) {
+            // add the signing KeySet
+            KeySet ks = addKeySetLocked(signingKeys);
+            long id = getIdByKeySetLocked(ks);
+            Set<Long> publicKeyIds = mKeySetMapping.get(id);
+            if (publicKeyIds == null) {
+                throw new NullPointerException("Got invalid KeySet id");
+            }
+
+            // attach it to the package
+            PackageSetting pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                throw new NullPointerException("No such package!");
+            }
+            pkg.keySetData.addSigningKeySet(id);
+
+            // for each KeySet the package defines which is a subset of
+            // the one above, add the KeySet id to the package's signing KeySets
+            for (Long keySetID : pkg.keySetData.getDefinedKeySets()) {
+                Set<Long> definedKeys = mKeySetMapping.get(keySetID);
+                if (publicKeyIds.contains(definedKeys)) {
+                    pkg.keySetData.addSigningKeySet(keySetID);
+                }
+            }
+        }
+    }
+
+    /**
+     * Fetches the stable identifier associated with the given KeySet. Returns
+     * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found.
+     */
+    public long getIdByKeySet(KeySet ks) {
+        synchronized (mLockObject) {
+            return getIdByKeySetLocked(ks);
+        }
+    }
+
+    private long getIdByKeySetLocked(KeySet ks) {
+        for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) {
+            KeySet value = mKeySets.valueAt(keySetIndex);
+            if (ks.equals(value)) {
+                return mKeySets.keyAt(keySetIndex);
+            }
+        }
+        return KEYSET_NOT_FOUND;
+    }
+
+    /**
+     * Fetches the KeySet corresponding to the given stable identifier.
+     *
+     * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't
+     * identify a {@link KeySet}.
+     */
+    public KeySet getKeySetById(long id) {
+        synchronized (mLockObject) {
+            return mKeySets.get(id);
+        }
+    }
+
+    /**
+     * Fetches the KeySet that a given package refers to by the provided alias.
+     *
+     * If the package isn't known to us, throws an IllegalArgumentException.
+     * Returns null if the alias isn't known to us.
+     */
+    public KeySet getKeySetByAliasAndPackageName(String packageName, String alias) {
+        synchronized (mLockObject) {
+            PackageSetting p = mPackages.get(packageName);
+            if (p == null) {
+                throw new NullPointerException("Unknown package");
+            }
+            if (p.keySetData == null) {
+                throw new IllegalArgumentException("Package has no keySet data");
+            }
+            long keySetId = p.keySetData.getAliases().get(alias);
+            return mKeySets.get(keySetId);
+        }
+    }
+
+    /**
+     * Fetches all the known {@link KeySet KeySets} that signed the given
+     * package. Returns {@code null} if package is unknown.
+     */
+    public Set<KeySet> getSigningKeySetsByPackageName(String packageName) {
+        synchronized (mLockObject) {
+            Set<KeySet> signingKeySets = new HashSet<KeySet>();
+            PackageSetting p = mPackages.get(packageName);
+            if (p == null) {
+                throw new NullPointerException("Unknown package");
+            }
+            if (p.keySetData == null) {
+                throw new IllegalArgumentException("Package has no keySet data");
+            }
+            for (long l : p.keySetData.getSigningKeySets()) {
+                signingKeySets.add(mKeySets.get(l));
+            }
+            return signingKeySets;
+        }
+    }
+
+    /**
+     * Creates a new KeySet corresponding to the given keys.
+     *
+     * If the {@link PublicKey PublicKeys} aren't known to the system, this
+     * adds them. Otherwise, they're deduped.
+     *
+     * If the KeySet isn't known to the system, this adds that and creates the
+     * mapping to the PublicKeys. If it is known, then it's deduped.
+     *
+     * Throws if the provided set is {@code null}.
+     */
+    private KeySet addKeySetLocked(Set<PublicKey> keys) {
+        if (keys == null) {
+            throw new NullPointerException("Provided keys cannot be null");
+        }
+        // add each of the keys in the provided set
+        Set<Long> addedKeyIds = new HashSet<Long>(keys.size());
+        for (PublicKey k : keys) {
+            long id = addPublicKeyLocked(k);
+            addedKeyIds.add(id);
+        }
+
+        // check to see if the resulting keyset is new
+        long existingKeySetId = getIdFromKeyIdsLocked(addedKeyIds);
+        if (existingKeySetId != KEYSET_NOT_FOUND) {
+            return mKeySets.get(existingKeySetId);
+        }
+
+        // create the KeySet object
+        KeySet ks = new KeySet(new Binder());
+        // get the first unoccupied slot in mKeySets
+        long id = getFreeKeySetIDLocked();
+        // add the KeySet object to it
+        mKeySets.put(id, ks);
+        // add the stable key ids to the mapping
+        mKeySetMapping.put(id, addedKeyIds);
+        // go home
+        return ks;
+    }
+
+    /**
+     * Adds the given PublicKey to the system, deduping as it goes.
+     */
+    private long addPublicKeyLocked(PublicKey key) {
+        // check if the public key is new
+        long existingKeyId = getIdForPublicKeyLocked(key);
+        if (existingKeyId != PUBLIC_KEY_NOT_FOUND) {
+            return existingKeyId;
+        }
+        // if it's new find the first unoccupied slot in the public keys
+        long id = getFreePublicKeyIdLocked();
+        // add the public key to it
+        mPublicKeys.put(id, key);
+        // return the stable identifier
+        return id;
+    }
+
+    /**
+     * Finds the stable identifier for a KeySet based on a set of PublicKey stable IDs.
+     *
+     * Returns KEYSET_NOT_FOUND if there isn't one.
+     */
+    private long getIdFromKeyIdsLocked(Set<Long> publicKeyIds) {
+        for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) {
+            Set<Long> value = mKeySetMapping.valueAt(keyMapIndex);
+            if (value.equals(publicKeyIds)) {
+                return mKeySetMapping.keyAt(keyMapIndex);
+            }
+        }
+        return KEYSET_NOT_FOUND;
+    }
+
+    /**
+     * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND.
+     */
+    private long getIdForPublicKeyLocked(PublicKey k) {
+        String encodedPublicKey = new String(k.getEncoded());
+        for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) {
+            PublicKey value = mPublicKeys.valueAt(publicKeyIndex);
+            String encodedExistingKey = new String(value.getEncoded());
+            if (encodedPublicKey.equals(encodedExistingKey)) {
+                return mPublicKeys.keyAt(publicKeyIndex);
+            }
+        }
+        return PUBLIC_KEY_NOT_FOUND;
+    }
+
+    /**
+     * Gets an unused stable identifier for a KeySet.
+     */
+    private long getFreeKeySetIDLocked() {
+        lastIssuedKeySetId += 1;
+        return lastIssuedKeySetId;
+    }
+
+    /**
+     * Same as above, but for public keys.
+     */
+    private long getFreePublicKeyIdLocked() {
+        lastIssuedKeyId += 1;
+        return lastIssuedKeyId;
+    }
+
+    public void removeAppKeySetData(String packageName) {
+        synchronized (mLockObject) {
+            // Get the package's known keys and KeySets
+            Set<Long> deletableKeySets = getKnownKeySetsByPackageNameLocked(packageName);
+            Set<Long> deletableKeys = new HashSet<Long>();
+            Set<Long> knownKeys = null;
+            for (Long ks : deletableKeySets) {
+                knownKeys = mKeySetMapping.get(ks);
+                if (knownKeys != null) {
+                    deletableKeys.addAll(knownKeys);
+                }
+            }
+
+            // Now remove the keys and KeySets known to any other package
+            for (String pkgName : mPackages.keySet()) {
+                if (pkgName.equals(packageName)) {
+                    continue;
+                }
+                Set<Long> knownKeySets = getKnownKeySetsByPackageNameLocked(pkgName);
+                deletableKeySets.removeAll(knownKeySets);
+                knownKeys = new HashSet<Long>();
+                for (Long ks : knownKeySets) {
+                    knownKeys = mKeySetMapping.get(ks);
+                    if (knownKeys != null) {
+                        deletableKeys.removeAll(knownKeys);
+                    }
+                }
+            }
+
+            // The remaining keys and KeySets are not known to any other
+            // application and so can be safely deleted.
+            for (Long ks : deletableKeySets) {
+                mKeySets.delete(ks);
+                mKeySetMapping.delete(ks);
+            }
+            for (Long keyId : deletableKeys) {
+                mPublicKeys.delete(keyId);
+            }
+
+            // Now remove them from the KeySets known to each package
+            for (String pkgName : mPackages.keySet()) {
+                PackageSetting p = mPackages.get(pkgName);
+                for (Long ks : deletableKeySets) {
+                    p.keySetData.removeSigningKeySet(ks);
+                    p.keySetData.removeDefinedKeySet(ks);
+                }
+            }
+        }
+    }
+
+    private Set<Long> getKnownKeySetsByPackageNameLocked(String packageName) {
+        PackageSetting p = mPackages.get(packageName);
+        if (p == null) {
+            throw new NullPointerException("Unknown package");
+        }
+        if (p.keySetData == null) {
+            throw new IllegalArgumentException("Package has no keySet data");
+        }
+        Set<Long> knownKeySets = new HashSet<Long>();
+        for (long ks : p.keySetData.getSigningKeySets()) {
+            knownKeySets.add(ks);
+        }
+        for (long ks : p.keySetData.getDefinedKeySets()) {
+            knownKeySets.add(ks);
+        }
+        return knownKeySets;
+    }
+
+    public String encodePublicKey(PublicKey k) throws IOException {
+        return new String(Base64.encode(k.getEncoded(), 0));
+    }
+
+    public void dump(PrintWriter pw, String packageName,
+            PackageManagerService.DumpState dumpState) {
+        synchronized (mLockObject) {
+            boolean printedHeader = false;
+            for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) {
+                String keySetPackage = e.getKey();
+                if (packageName != null && !packageName.equals(keySetPackage)) {
+                    continue;
+                }
+                if (!printedHeader) {
+                    if (dumpState.onTitlePrinted())
+                        pw.println();
+                    pw.println("Key Set Manager:");
+                    printedHeader = true;
+                }
+                PackageSetting pkg = e.getValue();
+                pw.print("  ["); pw.print(keySetPackage); pw.println("]");
+                if (pkg.keySetData != null) {
+                    boolean printedLabel = false;
+                    for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) {
+                        if (!printedLabel) {
+                            pw.print("      KeySets Aliases: ");
+                            printedLabel = true;
+                        } else {
+                            pw.print(", ");
+                        }
+                        pw.print(entry.getKey());
+                        pw.print('=');
+                        pw.print(Long.toString(entry.getValue()));
+                    }
+                    if (printedLabel) {
+                        pw.println("");
+                    }
+                    printedLabel = false;
+                    for (long keySetId : pkg.keySetData.getDefinedKeySets()) {
+                        if (!printedLabel) {
+                            pw.print("      Defined KeySets: ");
+                            printedLabel = true;
+                        } else {
+                            pw.print(", ");
+                        }
+                        pw.print(Long.toString(keySetId));
+                    }
+                    if (printedLabel) {
+                        pw.println("");
+                    }
+                    printedLabel = false;
+                    for (long keySetId : pkg.keySetData.getSigningKeySets()) {
+                        if (!printedLabel) {
+                            pw.print("      Signing KeySets: ");
+                            printedLabel = true;
+                        } else {
+                            pw.print(", ");
+                        }
+                        pw.print(Long.toString(keySetId));
+                    }
+                    if (printedLabel) {
+                        pw.println("");
+                    }
+                }
+            }
+        }
+    }
+
+    void writeKeySetManagerLPr(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, "keyset-settings");
+        writePublicKeysLPr(serializer);
+        writeKeySetsLPr(serializer);
+        serializer.startTag(null, "lastIssuedKeyId");
+        serializer.attribute(null, "value", Long.toString(lastIssuedKeyId));
+        serializer.endTag(null, "lastIssuedKeyId");
+        serializer.startTag(null, "lastIssuedKeySetId");
+        serializer.attribute(null, "value", Long.toString(lastIssuedKeySetId));
+        serializer.endTag(null, "lastIssuedKeySetId");
+        serializer.endTag(null, "keyset-settings");
+    }
+
+    void writePublicKeysLPr(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, "keys");
+        for (int pKeyIndex = 0; pKeyIndex < mPublicKeys.size(); pKeyIndex++) {
+            long id = mPublicKeys.keyAt(pKeyIndex);
+            PublicKey key = mPublicKeys.valueAt(pKeyIndex);
+            String encodedKey = encodePublicKey(key);
+            serializer.startTag(null, "public-key");
+            serializer.attribute(null, "identifier", Long.toString(id));
+            serializer.attribute(null, "value", encodedKey);
+            serializer.endTag(null, "public-key");
+        }
+        serializer.endTag(null, "keys");
+    }
+
+    void writeKeySetsLPr(XmlSerializer serializer) throws IOException {
+        serializer.startTag(null, "keysets");
+        for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) {
+            long id = mKeySetMapping.keyAt(keySetIndex);
+            Set<Long> keys = mKeySetMapping.valueAt(keySetIndex);
+            serializer.startTag(null, "keyset");
+            serializer.attribute(null, "identifier", Long.toString(id));
+            for (long keyId : keys) {
+                serializer.startTag(null, "key-id");
+                serializer.attribute(null, "identifier", Long.toString(keyId));
+                serializer.endTag(null, "key-id");
+            }
+            serializer.endTag(null, "keyset");
+        }
+        serializer.endTag(null, "keysets");
+    }
+
+    void readKeySetsLPw(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int type;
+        long currentKeySetId = 0;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            final String tagName = parser.getName();
+            if (tagName.equals("keys")) {
+                readKeysLPw(parser);
+            } else if (tagName.equals("keysets")) {
+                readKeySetListLPw(parser);
+            }
+        }
+    }
+
+    void readKeysLPw(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            final String tagName = parser.getName();
+            if (tagName.equals("public-key")) {
+                readPublicKeyLPw(parser);
+            } else if (tagName.equals("lastIssuedKeyId")) {
+                lastIssuedKeyId = Long.parseLong(parser.getAttributeValue(null, "value"));
+            } else if (tagName.equals("lastIssuedKeySetId")) {
+                lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value"));
+            }
+        }
+    }
+
+    void readKeySetListLPw(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        long currentKeySetId = 0;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            final String tagName = parser.getName();
+            if (tagName.equals("keyset")) {
+                currentKeySetId = readIdentifierLPw(parser);
+                mKeySets.put(currentKeySetId, new KeySet(new Binder()));
+                mKeySetMapping.put(currentKeySetId, new HashSet<Long>());
+            } else if (tagName.equals("key-id")) {
+                long id = readIdentifierLPw(parser);
+                mKeySetMapping.get(currentKeySetId).add(id);
+            }
+        }
+    }
+
+    long readIdentifierLPw(XmlPullParser parser)
+            throws XmlPullParserException {
+        return Long.parseLong(parser.getAttributeValue(null, "identifier"));
+    }
+
+    void readPublicKeyLPw(XmlPullParser parser)
+            throws XmlPullParserException {
+        String encodedID = parser.getAttributeValue(null, "identifier");
+        long identifier = Long.parseLong(encodedID);
+        String encodedPublicKey = parser.getAttributeValue(null, "value");
+        PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey);
+        if (pub != null) {
+            mPublicKeys.put(identifier, pub);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageKeySetData.java b/services/core/java/com/android/server/pm/PackageKeySetData.java
new file mode 100644
index 0000000..cb60621
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageKeySetData.java
@@ -0,0 +1,125 @@
+/*
+ * 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.pm;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class PackageKeySetData {
+
+    private long[] mSigningKeySets;
+
+    private long[] mDefinedKeySets;
+
+    private final Map<String, Long> mKeySetAliases;
+
+    PackageKeySetData() {
+        mSigningKeySets = new long[0];
+        mDefinedKeySets = new long[0];
+        mKeySetAliases =  new HashMap<String, Long>();
+    }
+
+    PackageKeySetData(PackageKeySetData original) {
+        mSigningKeySets = original.getSigningKeySets().clone();
+        mDefinedKeySets = original.getDefinedKeySets().clone();
+        mKeySetAliases = new HashMap<String, Long>();
+        mKeySetAliases.putAll(original.getAliases());
+    }
+
+    public void addSigningKeySet(long ks) {
+        // deduplicate
+        for (long knownKeySet : mSigningKeySets) {
+            if (ks == knownKeySet) {
+                return;
+            }
+        }
+        int end = mSigningKeySets.length;
+        mSigningKeySets = Arrays.copyOf(mSigningKeySets, end + 1);
+        mSigningKeySets[end] = ks;
+    }
+
+    public void removeSigningKeySet(long ks) {
+        if (packageIsSignedBy(ks)) {
+            long[] keysets = new long[mSigningKeySets.length - 1];
+            int index = 0;
+            for (long signingKeySet : mSigningKeySets) {
+                if (signingKeySet != ks) {
+                    keysets[index] = signingKeySet;
+                    index += 1;
+                }
+            }
+            mSigningKeySets = keysets;
+        }
+    }
+
+    public void addDefinedKeySet(long ks, String alias) {
+        // deduplicate
+        for (long knownKeySet : mDefinedKeySets) {
+            if (ks == knownKeySet) {
+                return;
+            }
+        }
+        int end = mDefinedKeySets.length;
+        mDefinedKeySets = Arrays.copyOf(mDefinedKeySets, end + 1);
+        mDefinedKeySets[end] = ks;
+        mKeySetAliases.put(alias, ks);
+    }
+
+    public void removeDefinedKeySet(long ks) {
+        if (mKeySetAliases.containsValue(ks)) {
+            long[] keysets = new long[mDefinedKeySets.length - 1];
+            int index = 0;
+            for (long definedKeySet : mDefinedKeySets) {
+                if (definedKeySet != ks) {
+                    keysets[index] = definedKeySet;
+                    index += 1;
+                }
+            }
+            mDefinedKeySets = keysets;
+            for (String alias : mKeySetAliases.keySet()) {
+                if (mKeySetAliases.get(alias) == ks) {
+                    mKeySetAliases.remove(alias);
+                    break;
+                }
+            }
+        }
+    }
+
+    public boolean packageIsSignedBy(long ks) {
+        for (long signingKeySet : mSigningKeySets) {
+            if (ks == signingKeySet) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public long[] getSigningKeySets() {
+        return mSigningKeySets;
+    }
+
+    public long[] getDefinedKeySets() {
+        return mDefinedKeySets;
+    }
+
+    public Map<String, Long> getAliases() {
+        return mKeySetAliases;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
new file mode 100755
index 0000000..b11ebf5
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -0,0 +1,11592 @@
+/*
+ * Copyright (C) 2006 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.pm;
+
+import static android.Manifest.permission.GRANT_REVOKE_PERMISSIONS;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static com.android.internal.util.ArrayUtils.appendInt;
+import static com.android.internal.util.ArrayUtils.removeInt;
+import static libcore.io.OsConstants.S_IRWXU;
+import static libcore.io.OsConstants.S_IRGRP;
+import static libcore.io.OsConstants.S_IXGRP;
+import static libcore.io.OsConstants.S_IROTH;
+import static libcore.io.OsConstants.S_IXOTH;
+
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.app.ResolverActivity;
+import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.content.PackageHelper;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+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;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.admin.IDevicePolicyManager;
+import android.app.backup.IBackupManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
+import android.content.IntentSender.SendIntentException;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ContainerEncryptionParams;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageInstallObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageCleanItem;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInfoLite;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageUserState;
+import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageStats;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.ManifestDigest;
+import android.content.pm.VerificationParams;
+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;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.Environment.UserEnvironment;
+import android.os.UserManager;
+import android.security.KeyStore;
+import android.security.SystemKeyStore;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.PrintStreamPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+import android.view.Display;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.StructStat;
+
+import com.android.internal.R;
+import com.android.server.storage.DeviceStorageMonitorInternal;
+
+/**
+ * Keep track of all those .apks everywhere.
+ * 
+ * This is very central to the platform's security; please run the unit
+ * tests whenever making modifications here:
+ * 
+mmm frameworks/base/tests/AndroidTests
+adb install -r -f out/target/product/passion/data/app/AndroidTests.apk
+adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests com.android.unit_tests/android.test.InstrumentationTestRunner
+ * 
+ * {@hide}
+ */
+public class PackageManagerService extends IPackageManager.Stub {
+    static final String TAG = "PackageManager";
+    static final boolean DEBUG_SETTINGS = false;
+    static final boolean DEBUG_PREFERRED = false;
+    static final boolean DEBUG_UPGRADE = false;
+    private static final boolean DEBUG_INSTALL = false;
+    private static final boolean DEBUG_REMOVE = false;
+    private static final boolean DEBUG_BROADCASTS = false;
+    private static final boolean DEBUG_SHOW_INFO = false;
+    private static final boolean DEBUG_PACKAGE_INFO = false;
+    private static final boolean DEBUG_INTENT_MATCHING = false;
+    private static final boolean DEBUG_PACKAGE_SCANNING = false;
+    private static final boolean DEBUG_APP_DIR_OBSERVER = false;
+    private static final boolean DEBUG_VERIFY = false;
+
+    private static final int RADIO_UID = Process.PHONE_UID;
+    private static final int LOG_UID = Process.LOG_UID;
+    private static final int NFC_UID = Process.NFC_UID;
+    private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
+    private static final int SHELL_UID = Process.SHELL_UID;
+
+    private static final boolean GET_CERTIFICATES = true;
+
+    private static final int REMOVE_EVENTS =
+        FileObserver.CLOSE_WRITE | FileObserver.DELETE | FileObserver.MOVED_FROM;
+    private static final int ADD_EVENTS =
+        FileObserver.CLOSE_WRITE /*| FileObserver.CREATE*/ | FileObserver.MOVED_TO;
+
+    private static final int OBSERVER_EVENTS = REMOVE_EVENTS | ADD_EVENTS;
+    // Suffix used during package installation when copying/moving
+    // package apks to install directory.
+    private static final String INSTALL_PACKAGE_SUFFIX = "-";
+
+    static final int SCAN_MONITOR = 1<<0;
+    static final int SCAN_NO_DEX = 1<<1;
+    static final int SCAN_FORCE_DEX = 1<<2;
+    static final int SCAN_UPDATE_SIGNATURE = 1<<3;
+    static final int SCAN_NEW_INSTALL = 1<<4;
+    static final int SCAN_NO_PATHS = 1<<5;
+    static final int SCAN_UPDATE_TIME = 1<<6;
+    static final int SCAN_DEFER_DEX = 1<<7;
+    static final int SCAN_BOOTING = 1<<8;
+
+    static final int REMOVE_CHATTY = 1<<16;
+
+    /**
+     * Timeout (in milliseconds) after which the watchdog should declare that
+     * our handler thread is wedged.  The usual default for such things is one
+     * minute but we sometimes do very lengthy I/O operations on this thread,
+     * such as installing multi-gigabyte applications, so ours needs to be longer.
+     */
+    private static final long WATCHDOG_TIMEOUT = 1000*60*10;     // ten minutes
+
+    /**
+     * Whether verification is enabled by default.
+     */
+    private static final boolean DEFAULT_VERIFY_ENABLE = true;
+
+    /**
+     * The default maximum time to wait for the verification agent to return in
+     * milliseconds.
+     */
+    private static final long DEFAULT_VERIFICATION_TIMEOUT = 10 * 1000;
+
+    /**
+     * The default response for package verification timeout.
+     *
+     * This can be either PackageManager.VERIFICATION_ALLOW or
+     * PackageManager.VERIFICATION_REJECT.
+     */
+    private static final int DEFAULT_VERIFICATION_RESPONSE = PackageManager.VERIFICATION_ALLOW;
+
+    static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
+
+    static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
+            DEFAULT_CONTAINER_PACKAGE,
+            "com.android.defcontainer.DefaultContainerService");
+
+    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
+    private static final String LIB_DIR_NAME = "lib";
+
+    static final String mTempContainerPrefix = "smdl2tmp";
+
+    final HandlerThread mHandlerThread = new HandlerThread("PackageManager",
+            Process.THREAD_PRIORITY_BACKGROUND);
+    final PackageHandler mHandler;
+
+    final int mSdkVersion = Build.VERSION.SDK_INT;
+    final String mSdkCodename = "REL".equals(Build.VERSION.CODENAME)
+            ? null : Build.VERSION.CODENAME;
+
+    final Context mContext;
+    final boolean mFactoryTest;
+    final boolean mOnlyCore;
+    final boolean mNoDexOpt;
+    final DisplayMetrics mMetrics;
+    final int mDefParseFlags;
+    final String[] mSeparateProcesses;
+
+    // This is where all application persistent data goes.
+    final File mAppDataDir;
+
+    // This is where all application persistent data goes for secondary users.
+    final File mUserAppDataDir;
+
+    /** The location for ASEC container files on internal storage. */
+    final String mAsecInternalPath;
+
+    // This is the object monitoring the framework dir.
+    final FileObserver mFrameworkInstallObserver;
+
+    // This is the object monitoring the system app dir.
+    final FileObserver mSystemInstallObserver;
+
+    // This is the object monitoring the privileged system app dir.
+    final FileObserver mPrivilegedInstallObserver;
+
+    // This is the object monitoring the system app dir.
+    final FileObserver mVendorInstallObserver;
+
+    // This is the object monitoring mAppInstallDir.
+    final FileObserver mAppInstallObserver;
+
+    // This is the object monitoring mDrmAppPrivateInstallDir.
+    final FileObserver mDrmAppInstallObserver;
+
+    // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
+    // LOCK HELD.  Can be called with mInstallLock held.
+    final Installer mInstaller;
+
+    final File mAppInstallDir;
+
+    /**
+     * Directory to which applications installed internally have native
+     * libraries copied.
+     */
+    private File mAppLibInstallDir;
+
+    // Directory containing the private parts (e.g. code and non-resource assets) of forward-locked
+    // apps.
+    final File mDrmAppPrivateInstallDir;
+
+    // ----------------------------------------------------------------
+
+    // Lock for state used when installing and doing other long running
+    // operations.  Methods that must be called with this lock held have
+    // the prefix "LI".
+    final Object mInstallLock = new Object();
+
+    // These are the directories in the 3rd party applications installed dir
+    // that we have currently loaded packages from.  Keys are the application's
+    // installed zip file (absolute codePath), and values are Package.
+    final HashMap<String, PackageParser.Package> mAppDirs =
+            new HashMap<String, PackageParser.Package>();
+
+    // Information for the parser to write more useful error messages.
+    File mScanningPath;
+    int mLastScanError;
+
+    // ----------------------------------------------------------------
+
+    // Keys are String (package name), values are Package.  This also serves
+    // as the lock for the global state.  Methods that must be called with
+    // this lock held have the prefix "LP".
+    final HashMap<String, PackageParser.Package> mPackages =
+            new HashMap<String, PackageParser.Package>();
+
+    final Settings mSettings;
+    boolean mRestoredSettings;
+
+    // Group-ids that are given to all packages as read from etc/permissions/*.xml.
+    int[] mGlobalGids;
+
+    // These are the built-in uid -> permission mappings that were read from the
+    // etc/permissions.xml file.
+    final SparseArray<HashSet<String>> mSystemPermissions =
+            new SparseArray<HashSet<String>>();
+
+    static final class SharedLibraryEntry {
+        final String path;
+        final String apk;
+
+        SharedLibraryEntry(String _path, String _apk) {
+            path = _path;
+            apk = _apk;
+        }
+    }
+
+    // These are the built-in shared libraries that were read from the
+    // etc/permissions.xml file.
+    final HashMap<String, SharedLibraryEntry> mSharedLibraries
+            = new HashMap<String, SharedLibraryEntry>();
+
+    // Temporary for building the final shared libraries for an .apk.
+    String[] mTmpSharedLibraries = null;
+
+    // These are the features this devices supports that were read from the
+    // etc/permissions.xml file.
+    final HashMap<String, FeatureInfo> mAvailableFeatures =
+            new HashMap<String, FeatureInfo>();
+
+    // If mac_permissions.xml was found for seinfo labeling.
+    boolean mFoundPolicyFile;
+
+    // All available activities, for your resolving pleasure.
+    final ActivityIntentResolver mActivities =
+            new ActivityIntentResolver();
+
+    // All available receivers, for your resolving pleasure.
+    final ActivityIntentResolver mReceivers =
+            new ActivityIntentResolver();
+
+    // All available services, for your resolving pleasure.
+    final ServiceIntentResolver mServices = new ServiceIntentResolver();
+
+    // All available providers, for your resolving pleasure.
+    final ProviderIntentResolver mProviders = new ProviderIntentResolver();
+
+    // Mapping from provider base names (first directory in content URI codePath)
+    // to the provider information.
+    final HashMap<String, PackageParser.Provider> mProvidersByAuthority =
+            new HashMap<String, PackageParser.Provider>();
+
+    // Mapping from instrumentation class names to info about them.
+    final HashMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
+            new HashMap<ComponentName, PackageParser.Instrumentation>();
+
+    // Mapping from permission names to info about them.
+    final HashMap<String, PackageParser.PermissionGroup> mPermissionGroups =
+            new HashMap<String, PackageParser.PermissionGroup>();
+
+    // Packages whose data we have transfered into another package, thus
+    // should no longer exist.
+    final HashSet<String> mTransferedPackages = new HashSet<String>();
+    
+    // Broadcast actions that are only available to the system.
+    final HashSet<String> mProtectedBroadcasts = new HashSet<String>();
+
+    /** List of packages waiting for verification. */
+    final SparseArray<PackageVerificationState> mPendingVerification
+            = new SparseArray<PackageVerificationState>();
+
+    HashSet<PackageParser.Package> mDeferredDexOpt = null;
+
+    /** Token for keys in mPendingVerification. */
+    private int mPendingVerificationToken = 0;
+
+    boolean mSystemReady;
+    boolean mSafeMode;
+    boolean mHasSystemUidErrors;
+
+    ApplicationInfo mAndroidApplication;
+    final ActivityInfo mResolveActivity = new ActivityInfo();
+    final ResolveInfo mResolveInfo = new ResolveInfo();
+    ComponentName mResolveComponentName;
+    PackageParser.Package mPlatformPackage;
+    ComponentName mCustomResolverComponentName;
+
+    boolean mResolverReplaced = false;
+
+    // Set of pending broadcasts for aggregating enable/disable of components.
+    static class PendingPackageBroadcasts {
+        // for each user id, a map of <package name -> components within that package>
+        final SparseArray<HashMap<String, ArrayList<String>>> mUidMap;
+
+        public PendingPackageBroadcasts() {
+            mUidMap = new SparseArray<HashMap<String, ArrayList<String>>>(2);
+        }
+
+        public ArrayList<String> get(int userId, String packageName) {
+            HashMap<String, ArrayList<String>> packages = getOrAllocate(userId);
+            return packages.get(packageName);
+        }
+
+        public void put(int userId, String packageName, ArrayList<String> components) {
+            HashMap<String, ArrayList<String>> packages = getOrAllocate(userId);
+            packages.put(packageName, components);
+        }
+
+        public void remove(int userId, String packageName) {
+            HashMap<String, ArrayList<String>> packages = mUidMap.get(userId);
+            if (packages != null) {
+                packages.remove(packageName);
+            }
+        }
+
+        public void remove(int userId) {
+            mUidMap.remove(userId);
+        }
+
+        public int userIdCount() {
+            return mUidMap.size();
+        }
+
+        public int userIdAt(int n) {
+            return mUidMap.keyAt(n);
+        }
+
+        public HashMap<String, ArrayList<String>> packagesForUserId(int userId) {
+            return mUidMap.get(userId);
+        }
+
+        public int size() {
+            // total number of pending broadcast entries across all userIds
+            int num = 0;
+            for (int i = 0; i< mUidMap.size(); i++) {
+                num += mUidMap.valueAt(i).size();
+            }
+            return num;
+        }
+
+        public void clear() {
+            mUidMap.clear();
+        }
+
+        private HashMap<String, ArrayList<String>> getOrAllocate(int userId) {
+            HashMap<String, ArrayList<String>> map = mUidMap.get(userId);
+            if (map == null) {
+                map = new HashMap<String, ArrayList<String>>();
+                mUidMap.put(userId, map);
+            }
+            return map;
+        }
+    }
+    final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts();
+
+    // Service Connection to remote media container service to copy
+    // package uri's from external media onto secure containers
+    // or internal storage.
+    private IMediaContainerService mContainerService = null;
+
+    static final int SEND_PENDING_BROADCAST = 1;
+    static final int MCS_BOUND = 3;
+    static final int END_COPY = 4;
+    static final int INIT_COPY = 5;
+    static final int MCS_UNBIND = 6;
+    static final int START_CLEANING_PACKAGE = 7;
+    static final int FIND_INSTALL_LOC = 8;
+    static final int POST_INSTALL = 9;
+    static final int MCS_RECONNECT = 10;
+    static final int MCS_GIVE_UP = 11;
+    static final int UPDATED_MEDIA_STATUS = 12;
+    static final int WRITE_SETTINGS = 13;
+    static final int WRITE_PACKAGE_RESTRICTIONS = 14;
+    static final int PACKAGE_VERIFIED = 15;
+    static final int CHECK_PENDING_VERIFICATION = 16;
+
+    static final int WRITE_SETTINGS_DELAY = 10*1000;  // 10 seconds
+
+    // Delay time in millisecs
+    static final int BROADCAST_DELAY = 10 * 1000;
+
+    static UserManagerService sUserManager;
+
+    // Stores a list of users whose package restrictions file needs to be updated
+    private HashSet<Integer> mDirtyUsers = new HashSet<Integer>();
+
+    final private DefaultContainerConnection mDefContainerConn =
+            new DefaultContainerConnection();
+    class DefaultContainerConnection implements ServiceConnection {
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
+            IMediaContainerService imcs =
+                IMediaContainerService.Stub.asInterface(service);
+            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
+        }
+
+        public void onServiceDisconnected(ComponentName name) {
+            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
+        }
+    };
+
+    // Recordkeeping of restore-after-install operations that are currently in flight
+    // between the Package Manager and the Backup Manager
+    class PostInstallData {
+        public InstallArgs args;
+        public PackageInstalledInfo res;
+
+        PostInstallData(InstallArgs _a, PackageInstalledInfo _r) {
+            args = _a;
+            res = _r;
+        }
+    };
+    final SparseArray<PostInstallData> mRunningInstalls = new SparseArray<PostInstallData>();
+    int mNextInstallToken = 1;  // nonzero; will be wrapped back to 1 when ++ overflows
+
+    private final String mRequiredVerifierPackage;
+
+    class PackageHandler extends Handler {
+        private boolean mBound = false;
+        final ArrayList<HandlerParams> mPendingInstalls =
+            new ArrayList<HandlerParams>();
+
+        private boolean connectToService() {
+            if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
+                    " DefaultContainerService");
+            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+            if (mContext.bindServiceAsUser(service, mDefContainerConn,
+                    Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                mBound = true;
+                return true;
+            }
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            return false;
+        }
+
+        private void disconnectService() {
+            mContainerService = null;
+            mBound = false;
+            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+            mContext.unbindService(mDefContainerConn);
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+        }
+
+        PackageHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void handleMessage(Message msg) {
+            try {
+                doHandleMessage(msg);
+            } finally {
+                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            }
+        }
+        
+        void doHandleMessage(Message msg) {
+            switch (msg.what) {
+                case INIT_COPY: {
+                    HandlerParams params = (HandlerParams) msg.obj;
+                    int idx = mPendingInstalls.size();
+                    if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
+                    // If a bind was already initiated we dont really
+                    // need to do anything. The pending install
+                    // will be processed later on.
+                    if (!mBound) {
+                        // If this is the only one pending we might
+                        // have to bind to the service again.
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            params.serviceError();
+                            return;
+                        } else {
+                            // Once we bind to the service, the first
+                            // pending request will be processed.
+                            mPendingInstalls.add(idx, params);
+                        }
+                    } else {
+                        mPendingInstalls.add(idx, params);
+                        // Already bound to the service. Just make
+                        // sure we trigger off processing the first request.
+                        if (idx == 0) {
+                            mHandler.sendEmptyMessage(MCS_BOUND);
+                        }
+                    }
+                    break;
+                }
+                case MCS_BOUND: {
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
+                    if (msg.obj != null) {
+                        mContainerService = (IMediaContainerService) msg.obj;
+                    }
+                    if (mContainerService == null) {
+                        // Something seriously wrong. Bail out
+                        Slog.e(TAG, "Cannot bind to media container service");
+                        for (HandlerParams params : mPendingInstalls) {
+                            // Indicate service bind error
+                            params.serviceError();
+                        }
+                        mPendingInstalls.clear();
+                    } else if (mPendingInstalls.size() > 0) {
+                        HandlerParams params = mPendingInstalls.get(0);
+                        if (params != null) {
+                            if (params.startCopy()) {
+                                // We are done...  look for more work or to
+                                // go idle.
+                                if (DEBUG_SD_INSTALL) Log.i(TAG,
+                                        "Checking for more work or unbind...");
+                                // Delete pending install
+                                if (mPendingInstalls.size() > 0) {
+                                    mPendingInstalls.remove(0);
+                                }
+                                if (mPendingInstalls.size() == 0) {
+                                    if (mBound) {
+                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
+                                                "Posting delayed MCS_UNBIND");
+                                        removeMessages(MCS_UNBIND);
+                                        Message ubmsg = obtainMessage(MCS_UNBIND);
+                                        // Unbind after a little delay, to avoid
+                                        // continual thrashing.
+                                        sendMessageDelayed(ubmsg, 10000);
+                                    }
+                                } else {
+                                    // There are more pending requests in queue.
+                                    // Just post MCS_BOUND message to trigger processing
+                                    // of next pending install.
+                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
+                                            "Posting MCS_BOUND for next woek");
+                                    mHandler.sendEmptyMessage(MCS_BOUND);
+                                }
+                            }
+                        }
+                    } else {
+                        // Should never happen ideally.
+                        Slog.w(TAG, "Empty queue");
+                    }
+                    break;
+                }
+                case MCS_RECONNECT: {
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_reconnect");
+                    if (mPendingInstalls.size() > 0) {
+                        if (mBound) {
+                            disconnectService();
+                        }
+                        if (!connectToService()) {
+                            Slog.e(TAG, "Failed to bind to media container service");
+                            for (HandlerParams params : mPendingInstalls) {
+                                // Indicate service bind error
+                                params.serviceError();
+                            }
+                            mPendingInstalls.clear();
+                        }
+                    }
+                    break;
+                }
+                case MCS_UNBIND: {
+                    // If there is no actual work left, then time to unbind.
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_unbind");
+
+                    if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {
+                        if (mBound) {
+                            if (DEBUG_INSTALL) Slog.i(TAG, "calling disconnectService()");
+
+                            disconnectService();
+                        }
+                    } else if (mPendingInstalls.size() > 0) {
+                        // There are more pending requests in queue.
+                        // Just post MCS_BOUND message to trigger processing
+                        // of next pending install.
+                        mHandler.sendEmptyMessage(MCS_BOUND);
+                    }
+
+                    break;
+                }
+                case MCS_GIVE_UP: {
+                    if (DEBUG_INSTALL) Slog.i(TAG, "mcs_giveup too many retries");
+                    mPendingInstalls.remove(0);
+                    break;
+                }
+                case SEND_PENDING_BROADCAST: {
+                    String packages[];
+                    ArrayList<String> components[];
+                    int size = 0;
+                    int uids[];
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    synchronized (mPackages) {
+                        if (mPendingBroadcasts == null) {
+                            return;
+                        }
+                        size = mPendingBroadcasts.size();
+                        if (size <= 0) {
+                            // Nothing to be done. Just return
+                            return;
+                        }
+                        packages = new String[size];
+                        components = new ArrayList[size];
+                        uids = new int[size];
+                        int i = 0;  // filling out the above arrays
+
+                        for (int n = 0; n < mPendingBroadcasts.userIdCount(); n++) {
+                            int packageUserId = mPendingBroadcasts.userIdAt(n);
+                            Iterator<Map.Entry<String, ArrayList<String>>> it
+                                    = mPendingBroadcasts.packagesForUserId(packageUserId)
+                                            .entrySet().iterator();
+                            while (it.hasNext() && i < size) {
+                                Map.Entry<String, ArrayList<String>> ent = it.next();
+                                packages[i] = ent.getKey();
+                                components[i] = ent.getValue();
+                                PackageSetting ps = mSettings.mPackages.get(ent.getKey());
+                                uids[i] = (ps != null)
+                                        ? UserHandle.getUid(packageUserId, ps.appId)
+                                        : -1;
+                                i++;
+                            }
+                        }
+                        size = i;
+                        mPendingBroadcasts.clear();
+                    }
+                    // Send broadcasts
+                    for (int i = 0; i < size; i++) {
+                        sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]);
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    break;
+                }
+                case START_CLEANING_PACKAGE: {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    final String packageName = (String)msg.obj;
+                    final int userId = msg.arg1;
+                    final boolean andCode = msg.arg2 != 0;
+                    synchronized (mPackages) {
+                        if (userId == UserHandle.USER_ALL) {
+                            int[] users = sUserManager.getUserIds();
+                            for (int user : users) {
+                                mSettings.addPackageToCleanLPw(
+                                        new PackageCleanItem(user, packageName, andCode));
+                            }
+                        } else {
+                            mSettings.addPackageToCleanLPw(
+                                    new PackageCleanItem(userId, packageName, andCode));
+                        }
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    startCleaningPackages();
+                } break;
+                case POST_INSTALL: {
+                    if (DEBUG_INSTALL) Log.v(TAG, "Handling post-install for " + msg.arg1);
+                    PostInstallData data = mRunningInstalls.get(msg.arg1);
+                    mRunningInstalls.delete(msg.arg1);
+                    boolean deleteOld = false;
+
+                    if (data != null) {
+                        InstallArgs args = data.args;
+                        PackageInstalledInfo res = data.res;
+
+                        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                            res.removedInfo.sendBroadcast(false, true, false);
+                            Bundle extras = new Bundle(1);
+                            extras.putInt(Intent.EXTRA_UID, res.uid);
+                            // Determine the set of users who are adding this
+                            // package for the first time vs. those who are seeing
+                            // an update.
+                            int[] firstUsers;
+                            int[] updateUsers = new int[0];
+                            if (res.origUsers == null || res.origUsers.length == 0) {
+                                firstUsers = res.newUsers;
+                            } else {
+                                firstUsers = new int[0];
+                                for (int i=0; i<res.newUsers.length; i++) {
+                                    int user = res.newUsers[i];
+                                    boolean isNew = true;
+                                    for (int j=0; j<res.origUsers.length; j++) {
+                                        if (res.origUsers[j] == user) {
+                                            isNew = false;
+                                            break;
+                                        }
+                                    }
+                                    if (isNew) {
+                                        int[] newFirst = new int[firstUsers.length+1];
+                                        System.arraycopy(firstUsers, 0, newFirst, 0,
+                                                firstUsers.length);
+                                        newFirst[firstUsers.length] = user;
+                                        firstUsers = newFirst;
+                                    } else {
+                                        int[] newUpdate = new int[updateUsers.length+1];
+                                        System.arraycopy(updateUsers, 0, newUpdate, 0,
+                                                updateUsers.length);
+                                        newUpdate[updateUsers.length] = user;
+                                        updateUsers = newUpdate;
+                                    }
+                                }
+                            }
+                            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+                                    res.pkg.applicationInfo.packageName,
+                                    extras, null, null, firstUsers);
+                            final boolean update = res.removedInfo.removedPackage != null;
+                            if (update) {
+                                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+                            }
+                            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+                                    res.pkg.applicationInfo.packageName,
+                                    extras, null, null, updateUsers);
+                            if (update) {
+                                sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
+                                        res.pkg.applicationInfo.packageName,
+                                        extras, null, null, updateUsers);
+                                sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
+                                        null, null,
+                                        res.pkg.applicationInfo.packageName, null, updateUsers);
+
+                                // treat asec-hosted packages like removable media on upgrade
+                                if (isForwardLocked(res.pkg) || isExternal(res.pkg)) {
+                                    if (DEBUG_INSTALL) {
+                                        Slog.i(TAG, "upgrading pkg " + res.pkg
+                                                + " is ASEC-hosted -> AVAILABLE");
+                                    }
+                                    int[] uidArray = new int[] { res.pkg.applicationInfo.uid };
+                                    ArrayList<String> pkgList = new ArrayList<String>(1);
+                                    pkgList.add(res.pkg.applicationInfo.packageName);
+                                    sendResourcesChangedBroadcast(true, false,
+                                            pkgList,uidArray, null);
+                                }
+                            }
+                            if (res.removedInfo.args != null) {
+                                // Remove the replaced package's older resources safely now
+                                deleteOld = true;
+                            }
+
+                            // Log current value of "unknown sources" setting
+                            EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
+                                getUnknownSourcesSettings());
+                        }
+                        // Force a gc to clear up things
+                        Runtime.getRuntime().gc();
+                        // We delete after a gc for applications  on sdcard.
+                        if (deleteOld) {
+                            synchronized (mInstallLock) {
+                                res.removedInfo.args.doPostDeleteLI(true);
+                            }
+                        }
+                        if (args.observer != null) {
+                            try {
+                                args.observer.packageInstalled(res.name, res.returnCode);
+                            } catch (RemoteException e) {
+                                Slog.i(TAG, "Observer no longer exists.");
+                            }
+                        }
+                    } else {
+                        Slog.e(TAG, "Bogus post-install token " + msg.arg1);
+                    }
+                } break;
+                case UPDATED_MEDIA_STATUS: {
+                    if (DEBUG_SD_INSTALL) Log.i(TAG, "Got message UPDATED_MEDIA_STATUS");
+                    boolean reportStatus = msg.arg1 == 1;
+                    boolean doGc = msg.arg2 == 1;
+                    if (DEBUG_SD_INSTALL) Log.i(TAG, "reportStatus=" + reportStatus + ", doGc = " + doGc);
+                    if (doGc) {
+                        // Force a gc to clear up stale containers.
+                        Runtime.getRuntime().gc();
+                    }
+                    if (msg.obj != null) {
+                        @SuppressWarnings("unchecked")
+                        Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj;
+                        if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
+                        // Unload containers
+                        unloadAllContainers(args);
+                    }
+                    if (reportStatus) {
+                        try {
+                            if (DEBUG_SD_INSTALL) Log.i(TAG, "Invoking MountService call back");
+                            PackageHelper.getMountService().finishMediaUpdate();
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "MountService not running?");
+                        }
+                    }
+                } break;
+                case WRITE_SETTINGS: {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    synchronized (mPackages) {
+                        removeMessages(WRITE_SETTINGS);
+                        removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+                        mSettings.writeLPr();
+                        mDirtyUsers.clear();
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                } break;
+                case WRITE_PACKAGE_RESTRICTIONS: {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+                    synchronized (mPackages) {
+                        removeMessages(WRITE_PACKAGE_RESTRICTIONS);
+                        for (int userId : mDirtyUsers) {
+                            mSettings.writePackageRestrictionsLPr(userId);
+                        }
+                        mDirtyUsers.clear();
+                    }
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                } break;
+                case CHECK_PENDING_VERIFICATION: {
+                    final int verificationId = msg.arg1;
+                    final PackageVerificationState state = mPendingVerification.get(verificationId);
+
+                    if ((state != null) && !state.timeoutExtended()) {
+                        final InstallArgs args = state.getInstallArgs();
+                        Slog.i(TAG, "Verification timed out for " + args.packageURI.toString());
+                        mPendingVerification.remove(verificationId);
+
+                        int ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+
+                        if (getDefaultVerificationResponse() == PackageManager.VERIFICATION_ALLOW) {
+                            Slog.i(TAG, "Continuing with installation of "
+                                    + args.packageURI.toString());
+                            state.setVerifierResponse(Binder.getCallingUid(),
+                                    PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT);
+                            broadcastPackageVerified(verificationId, args.packageURI,
+                                    PackageManager.VERIFICATION_ALLOW,
+                                    state.getInstallArgs().getUser());
+                            try {
+                                ret = args.copyApk(mContainerService, true);
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Could not contact the ContainerService");
+                            }
+                        } else {
+                            broadcastPackageVerified(verificationId, args.packageURI,
+                                    PackageManager.VERIFICATION_REJECT,
+                                    state.getInstallArgs().getUser());
+                        }
+
+                        processPendingInstall(args, ret);
+                        mHandler.sendEmptyMessage(MCS_UNBIND);
+                    }
+                    break;
+                }
+                case PACKAGE_VERIFIED: {
+                    final int verificationId = msg.arg1;
+
+                    final PackageVerificationState state = mPendingVerification.get(verificationId);
+                    if (state == null) {
+                        Slog.w(TAG, "Invalid verification token " + verificationId + " received");
+                        break;
+                    }
+
+                    final PackageVerificationResponse response = (PackageVerificationResponse) msg.obj;
+
+                    state.setVerifierResponse(response.callerUid, response.code);
+
+                    if (state.isVerificationComplete()) {
+                        mPendingVerification.remove(verificationId);
+
+                        final InstallArgs args = state.getInstallArgs();
+
+                        int ret;
+                        if (state.isInstallAllowed()) {
+                            ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                            broadcastPackageVerified(verificationId, args.packageURI,
+                                    response.code, state.getInstallArgs().getUser());
+                            try {
+                                ret = args.copyApk(mContainerService, true);
+                            } catch (RemoteException e) {
+                                Slog.e(TAG, "Could not contact the ContainerService");
+                            }
+                        } else {
+                            ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+                        }
+
+                        processPendingInstall(args, ret);
+
+                        mHandler.sendEmptyMessage(MCS_UNBIND);
+                    }
+
+                    break;
+                }
+            }
+        }
+    }
+
+    void scheduleWriteSettingsLocked() {
+        if (!mHandler.hasMessages(WRITE_SETTINGS)) {
+            mHandler.sendEmptyMessageDelayed(WRITE_SETTINGS, WRITE_SETTINGS_DELAY);
+        }
+    }
+
+    void scheduleWritePackageRestrictionsLocked(int userId) {
+        if (!sUserManager.exists(userId)) return;
+        mDirtyUsers.add(userId);
+        if (!mHandler.hasMessages(WRITE_PACKAGE_RESTRICTIONS)) {
+            mHandler.sendEmptyMessageDelayed(WRITE_PACKAGE_RESTRICTIONS, WRITE_SETTINGS_DELAY);
+        }
+    }
+
+    public static final IPackageManager main(Context context, Installer installer,
+            boolean factoryTest, boolean onlyCore) {
+        PackageManagerService m = new PackageManagerService(context, installer,
+                factoryTest, onlyCore);
+        ServiceManager.addService("package", m);
+        return m;
+    }
+
+    static String[] splitString(String str, char sep) {
+        int count = 1;
+        int i = 0;
+        while ((i=str.indexOf(sep, i)) >= 0) {
+            count++;
+            i++;
+        }
+
+        String[] res = new String[count];
+        i=0;
+        count = 0;
+        int lastI=0;
+        while ((i=str.indexOf(sep, i)) >= 0) {
+            res[count] = str.substring(lastI, i);
+            count++;
+            i++;
+            lastI = i;
+        }
+        res[count] = str.substring(lastI, str.length());
+        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,
+                SystemClock.uptimeMillis());
+
+        if (mSdkVersion <= 0) {
+            Slog.w(TAG, "**** ro.build.version.sdk not set!");
+        }
+
+        mContext = context;
+        mFactoryTest = factoryTest;
+        mOnlyCore = onlyCore;
+        mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
+        mMetrics = new DisplayMetrics();
+        mSettings = new Settings(context);
+        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+        mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+        mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+        mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+        mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
+                ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
+
+        String separateProcesses = SystemProperties.get("debug.separate_processes");
+        if (separateProcesses != null && separateProcesses.length() > 0) {
+            if ("*".equals(separateProcesses)) {
+                mDefParseFlags = PackageParser.PARSE_IGNORE_PROCESSES;
+                mSeparateProcesses = null;
+                Slog.w(TAG, "Running with debug.separate_processes: * (ALL)");
+            } else {
+                mDefParseFlags = 0;
+                mSeparateProcesses = separateProcesses.split(",");
+                Slog.w(TAG, "Running with debug.separate_processes: "
+                        + separateProcesses);
+            }
+        } else {
+            mDefParseFlags = 0;
+            mSeparateProcesses = null;
+        }
+
+        mInstaller = installer;
+
+        getDefaultDisplayMetrics(context, mMetrics);
+
+        synchronized (mInstallLock) {
+        // writer
+        synchronized (mPackages) {
+            mHandlerThread.start();
+            mHandler = new PackageHandler(mHandlerThread.getLooper());
+            Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName(),
+                    WATCHDOG_TIMEOUT);
+
+            File dataDir = Environment.getDataDirectory();
+            mAppDataDir = new File(dataDir, "data");
+            mAppInstallDir = new File(dataDir, "app");
+            mAppLibInstallDir = new File(dataDir, "app-lib");
+            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
+            mUserAppDataDir = new File(dataDir, "user");
+            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
+
+            sUserManager = new UserManagerService(context, this,
+                    mInstallLock, mPackages);
+
+            readPermissions();
+
+            mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
+
+            mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
+                    mSdkVersion, mOnlyCore);
+
+            String customResolverActivity = Resources.getSystem().getString(
+                    R.string.config_customResolverActivity);
+            if (TextUtils.isEmpty(customResolverActivity)) {
+                customResolverActivity = null;
+            } else {
+                mCustomResolverComponentName = ComponentName.unflattenFromString(
+                        customResolverActivity);
+            }
+
+            long startTime = SystemClock.uptimeMillis();
+
+            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
+                    startTime);
+
+            // Set flag to monitor and not change apk file paths when
+            // scanning install directories.
+            int scanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING;
+            if (mNoDexOpt) {
+                Slog.w(TAG, "Running ENG build: no pre-dexopt!");
+                scanMode |= SCAN_NO_DEX;
+            }
+
+            final HashSet<String> alreadyDexOpted = new HashSet<String>();
+
+            /**
+             * Add everything in the in the boot class path to the
+             * list of process files because dexopt will have been run
+             * if necessary during zygote startup.
+             */
+            String bootClassPath = System.getProperty("java.boot.class.path");
+            if (bootClassPath != null) {
+                String[] paths = splitString(bootClassPath, ':');
+                for (int i=0; i<paths.length; i++) {
+                    alreadyDexOpted.add(paths[i]);
+                }
+            } else {
+                Slog.w(TAG, "No BOOTCLASSPATH found!");
+            }
+
+            boolean didDexOpt = false;
+
+            /**
+             * Ensure all external libraries have had dexopt run on them.
+             */
+            if (mSharedLibraries.size() > 0) {
+                Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator();
+                while (libs.hasNext()) {
+                    String lib = libs.next().path;
+                    if (lib == null) {
+                        continue;
+                    }
+                    try {
+                        if (dalvik.system.DexFile.isDexOptNeeded(lib)) {
+                            alreadyDexOpted.add(lib);
+                            mInstaller.dexopt(lib, Process.SYSTEM_UID, true);
+                            didDexOpt = true;
+                        }
+                    } catch (FileNotFoundException e) {
+                        Slog.w(TAG, "Library not found: " + lib);
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
+                                + e.getMessage());
+                    }
+                }
+            }
+
+            File frameworkDir = new File(Environment.getRootDirectory(), "framework");
+
+            // Gross hack for now: we know this file doesn't contain any
+            // code, so don't dexopt it to avoid the resulting log spew.
+            alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
+
+            // Gross hack for now: we know this file is only part of
+            // the boot class path for art, so don't dexopt it to
+            // avoid the resulting log spew.
+            alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
+
+            /**
+             * And there are a number of commands implemented in Java, which
+             * we currently need to do the dexopt on so that they can be
+             * run from a non-root shell.
+             */
+            String[] frameworkFiles = frameworkDir.list();
+            if (frameworkFiles != null) {
+                for (int i=0; i<frameworkFiles.length; i++) {
+                    File libPath = new File(frameworkDir, frameworkFiles[i]);
+                    String path = libPath.getPath();
+                    // Skip the file if we alrady did it.
+                    if (alreadyDexOpted.contains(path)) {
+                        continue;
+                    }
+                    // Skip the file if it is not a type we want to dexopt.
+                    if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
+                        continue;
+                    }
+                    try {
+                        if (dalvik.system.DexFile.isDexOptNeeded(path)) {
+                            mInstaller.dexopt(path, Process.SYSTEM_UID, true);
+                            didDexOpt = true;
+                        }
+                    } catch (FileNotFoundException e) {
+                        Slog.w(TAG, "Jar not found: " + path);
+                    } catch (IOException e) {
+                        Slog.w(TAG, "Exception reading jar: " + path, e);
+                    }
+                }
+            }
+
+            if (didDexOpt) {
+                File dalvikCacheDir = new File(dataDir, "dalvik-cache");
+
+                // If we had to do a dexopt of one of the previous
+                // things, then something on the system has changed.
+                // Consider this significant, and wipe away all other
+                // existing dexopt files to ensure we don't leave any
+                // dangling around.
+                String[] files = dalvikCacheDir.list();
+                if (files != null) {
+                    for (int i=0; i<files.length; i++) {
+                        String fn = files[i];
+                        if (fn.startsWith("data@app@")
+                                || fn.startsWith("data@app-private@")) {
+                            Slog.i(TAG, "Pruning dalvik file: " + fn);
+                            (new File(dalvikCacheDir, fn)).delete();
+                        }
+                    }
+                }
+            }
+
+            // Find base frameworks (resource packages without code).
+            mFrameworkInstallObserver = new AppDirObserver(
+                frameworkDir.getPath(), OBSERVER_EVENTS, true, false);
+            mFrameworkInstallObserver.startWatching();
+            scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM
+                    | PackageParser.PARSE_IS_SYSTEM_DIR
+                    | PackageParser.PARSE_IS_PRIVILEGED,
+                    scanMode | SCAN_NO_DEX, 0);
+
+            // Collected privileged system packages.
+            File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
+            mPrivilegedInstallObserver = new AppDirObserver(
+                    privilegedAppDir.getPath(), OBSERVER_EVENTS, true, true);
+            mPrivilegedInstallObserver.startWatching();
+                scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
+                        | PackageParser.PARSE_IS_SYSTEM_DIR
+                        | PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0);
+
+            // Collect ordinary system packages.
+            File systemAppDir = new File(Environment.getRootDirectory(), "app");
+            mSystemInstallObserver = new AppDirObserver(
+                systemAppDir.getPath(), OBSERVER_EVENTS, true, false);
+            mSystemInstallObserver.startWatching();
+            scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
+                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
+
+            // Collect all vendor packages.
+            File vendorAppDir = new File("/vendor/app");
+            mVendorInstallObserver = new AppDirObserver(
+                vendorAppDir.getPath(), OBSERVER_EVENTS, true, false);
+            mVendorInstallObserver.startWatching();
+            scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
+                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
+
+            if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
+            mInstaller.moveFiles();
+
+            // Prune any system packages that no longer exist.
+            final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
+            if (!mOnlyCore) {
+                Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
+                while (psit.hasNext()) {
+                    PackageSetting ps = psit.next();
+
+                    /*
+                     * If this is not a system app, it can't be a
+                     * disable system app.
+                     */
+                    if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                        continue;
+                    }
+
+                    /*
+                     * If the package is scanned, it's not erased.
+                     */
+                    final PackageParser.Package scannedPkg = mPackages.get(ps.name);
+                    if (scannedPkg != null) {
+                        /*
+                         * If the system app is both scanned and in the
+                         * disabled packages list, then it must have been
+                         * added via OTA. Remove it from the currently
+                         * scanned package so the previously user-installed
+                         * application can be scanned.
+                         */
+                        if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
+                            Slog.i(TAG, "Expecting better updatd system app for " + ps.name
+                                    + "; removing system app");
+                            removePackageLI(ps, true);
+                        }
+
+                        continue;
+                    }
+
+                    if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
+                        psit.remove();
+                        String msg = "System package " + ps.name
+                                + " no longer exists; wiping its data";
+                        reportSettingsProblem(Log.WARN, msg);
+                        removeDataDirsLI(ps.name);
+                    } else {
+                        final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
+                        if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
+                            possiblyDeletedUpdatedSystemApps.add(ps.name);
+                        }
+                    }
+                }
+            }
+
+            //look for any incomplete package installations
+            ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
+            //clean up list
+            for(int i = 0; i < deletePkgsList.size(); i++) {
+                //clean up here
+                cleanupInstallFailedPackage(deletePkgsList.get(i));
+            }
+            //delete tmp files
+            deleteTempPackageFiles();
+
+            // Remove any shared userIDs that have no associated packages
+            mSettings.pruneSharedUsersLPw();
+
+            if (!mOnlyCore) {
+                EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
+                        SystemClock.uptimeMillis());
+                mAppInstallObserver = new AppDirObserver(
+                    mAppInstallDir.getPath(), OBSERVER_EVENTS, false, false);
+                mAppInstallObserver.startWatching();
+                scanDirLI(mAppInstallDir, 0, scanMode, 0);
+    
+                mDrmAppInstallObserver = new AppDirObserver(
+                    mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false, false);
+                mDrmAppInstallObserver.startWatching();
+                scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
+                        scanMode, 0);
+
+                /**
+                 * Remove disable package settings for any updated system
+                 * apps that were removed via an OTA. If they're not a
+                 * previously-updated app, remove them completely.
+                 * Otherwise, just revoke their system-level permissions.
+                 */
+                for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
+                    PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
+                    mSettings.removeDisabledSystemPackageLPw(deletedAppName);
+
+                    String msg;
+                    if (deletedPkg == null) {
+                        msg = "Updated system package " + deletedAppName
+                                + " no longer exists; wiping its data";
+                        removeDataDirsLI(deletedAppName);
+                    } else {
+                        msg = "Updated system app + " + deletedAppName
+                                + " no longer present; removing system privileges for "
+                                + deletedAppName;
+
+                        deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
+
+                        PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
+                        deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
+                    }
+                    reportSettingsProblem(Log.WARN, msg);
+                }
+            } else {
+                mAppInstallObserver = null;
+                mDrmAppInstallObserver = null;
+            }
+
+            // Now that we know all of the shared libraries, update all clients to have
+            // the correct library paths.
+            updateAllSharedLibrariesLPw();
+
+            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
+                    SystemClock.uptimeMillis());
+            Slog.i(TAG, "Time to scan packages: "
+                    + ((SystemClock.uptimeMillis()-startTime)/1000f)
+                    + " seconds");
+
+            // If the platform SDK has changed since the last time we booted,
+            // we need to re-grant app permission to catch any new ones that
+            // appear.  This is really a hack, and means that apps can in some
+            // cases get permissions that the user didn't initially explicitly
+            // allow...  it would be nice to have some better way to handle
+            // this situation.
+            final boolean regrantPermissions = mSettings.mInternalSdkPlatform
+                    != mSdkVersion;
+            if (regrantPermissions) Slog.i(TAG, "Platform changed from "
+                    + mSettings.mInternalSdkPlatform + " to " + mSdkVersion
+                    + "; regranting permissions for internal storage");
+            mSettings.mInternalSdkPlatform = mSdkVersion;
+            
+            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+                    | (regrantPermissions
+                            ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
+                            : 0));
+
+            // If this is the first boot, and it is a normal boot, then
+            // we need to initialize the default preferred apps.
+            if (!mRestoredSettings && !onlyCore) {
+                mSettings.readDefaultPreferredAppsLPw(this, 0);
+            }
+
+            // can downgrade to reader
+            mSettings.writeLPr();
+
+            EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
+                    SystemClock.uptimeMillis());
+
+            // Now after opening every single application zip, make sure they
+            // are all flushed.  Not really needed, but keeps things nice and
+            // tidy.
+            Runtime.getRuntime().gc();
+
+            mRequiredVerifierPackage = getRequiredVerifierLPr();
+        } // synchronized (mPackages)
+        } // synchronized (mInstallLock)
+    }
+
+    public boolean isFirstBoot() {
+        return !mRestoredSettings;
+    }
+
+    public boolean isOnlyCoreApps() {
+        return mOnlyCore;
+    }
+
+    private String getRequiredVerifierLPr() {
+        final Intent verification = new Intent(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+        final List<ResolveInfo> receivers = queryIntentReceivers(verification, PACKAGE_MIME_TYPE,
+                PackageManager.GET_DISABLED_COMPONENTS, 0 /* TODO: Which userId? */);
+
+        String requiredVerifier = null;
+
+        final int N = receivers.size();
+        for (int i = 0; i < N; i++) {
+            final ResolveInfo info = receivers.get(i);
+
+            if (info.activityInfo == null) {
+                continue;
+            }
+
+            final String packageName = info.activityInfo.packageName;
+
+            final PackageSetting ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                continue;
+            }
+
+            final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+            if (!gp.grantedPermissions
+                    .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
+                continue;
+            }
+
+            if (requiredVerifier != null) {
+                throw new RuntimeException("There can be only one required verifier");
+            }
+
+            requiredVerifier = packageName;
+        }
+
+        return requiredVerifier;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)) {
+                Slog.wtf(TAG, "Package Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    void cleanupInstallFailedPackage(PackageSetting ps) {
+        Slog.i(TAG, "Cleaning up incompletely installed app: " + ps.name);
+        removeDataDirsLI(ps.name);
+        if (ps.codePath != null) {
+            if (!ps.codePath.delete()) {
+                Slog.w(TAG, "Unable to remove old code file: " + ps.codePath);
+            }
+        }
+        if (ps.resourcePath != null) {
+            if (!ps.resourcePath.delete() && !ps.resourcePath.equals(ps.codePath)) {
+                Slog.w(TAG, "Unable to remove old code file: " + ps.resourcePath);
+            }
+        }
+        mSettings.removePackageLPw(ps.name);
+    }
+
+    void readPermissions() {
+        // Read permissions from .../etc/permission directory.
+        File libraryDir = new File(Environment.getRootDirectory(), "etc/permissions");
+        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
+            Slog.w(TAG, "No directory " + libraryDir + ", skipping");
+            return;
+        }
+        if (!libraryDir.canRead()) {
+            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
+            return;
+        }
+
+        // Iterate over the files in the directory and scan .xml files
+        for (File f : libraryDir.listFiles()) {
+            // We'll read platform.xml last
+            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
+                continue;
+            }
+
+            if (!f.getPath().endsWith(".xml")) {
+                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
+                continue;
+            }
+            if (!f.canRead()) {
+                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
+                continue;
+            }
+
+            readPermissionsFromXml(f);
+        }
+
+        // Read permissions from .../etc/permissions/platform.xml last so it will take precedence
+        final File permFile = new File(Environment.getRootDirectory(),
+                "etc/permissions/platform.xml");
+        readPermissionsFromXml(permFile);
+    }
+
+    private void readPermissionsFromXml(File permFile) {
+        FileReader permReader = null;
+        try {
+            permReader = new FileReader(permFile);
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
+            return;
+        }
+
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(permReader);
+
+            XmlUtils.beginDocument(parser, "permissions");
+
+            while (true) {
+                XmlUtils.nextElement(parser);
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                String name = parser.getName();
+                if ("group".equals(name)) {
+                    String gidStr = parser.getAttributeValue(null, "gid");
+                    if (gidStr != null) {
+                        int gid = Process.getGidForName(gidStr);
+                        mGlobalGids = appendInt(mGlobalGids, gid);
+                    } else {
+                        Slog.w(TAG, "<group> without gid at "
+                                + parser.getPositionDescription());
+                    }
+
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                } else if ("permission".equals(name)) {
+                    String perm = parser.getAttributeValue(null, "name");
+                    if (perm == null) {
+                        Slog.w(TAG, "<permission> without name at "
+                                + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    perm = perm.intern();
+                    readPermission(parser, perm);
+
+                } else if ("assign-permission".equals(name)) {
+                    String perm = parser.getAttributeValue(null, "name");
+                    if (perm == null) {
+                        Slog.w(TAG, "<assign-permission> without name at "
+                                + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    String uidStr = parser.getAttributeValue(null, "uid");
+                    if (uidStr == null) {
+                        Slog.w(TAG, "<assign-permission> without uid at "
+                                + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    int uid = Process.getUidForName(uidStr);
+                    if (uid < 0) {
+                        Slog.w(TAG, "<assign-permission> with unknown uid \""
+                                + uidStr + "\" at "
+                                + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    perm = perm.intern();
+                    HashSet<String> perms = mSystemPermissions.get(uid);
+                    if (perms == null) {
+                        perms = new HashSet<String>();
+                        mSystemPermissions.put(uid, perms);
+                    }
+                    perms.add(perm);
+                    XmlUtils.skipCurrentTag(parser);
+
+                } else if ("library".equals(name)) {
+                    String lname = parser.getAttributeValue(null, "name");
+                    String lfile = parser.getAttributeValue(null, "file");
+                    if (lname == null) {
+                        Slog.w(TAG, "<library> without name at "
+                                + parser.getPositionDescription());
+                    } else if (lfile == null) {
+                        Slog.w(TAG, "<library> without file at "
+                                + parser.getPositionDescription());
+                    } else {
+                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
+                        mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null));
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+
+                } else if ("feature".equals(name)) {
+                    String fname = parser.getAttributeValue(null, "name");
+                    if (fname == null) {
+                        Slog.w(TAG, "<feature> without name at "
+                                + parser.getPositionDescription());
+                    } else {
+                        //Log.i(TAG, "Got feature " + fname);
+                        FeatureInfo fi = new FeatureInfo();
+                        fi.name = fname;
+                        mAvailableFeatures.put(fname, fi);
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+
+                } else {
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+            }
+            permReader.close();
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Got execption parsing permissions.", e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Got execption parsing permissions.", e);
+        }
+    }
+
+    void readPermission(XmlPullParser parser, String name)
+            throws IOException, XmlPullParserException {
+
+        name = name.intern();
+
+        BasePermission bp = mSettings.mPermissions.get(name);
+        if (bp == null) {
+            bp = new BasePermission(name, null, BasePermission.TYPE_BUILTIN);
+            mSettings.mPermissions.put(name, bp);
+        }
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if ("group".equals(tagName)) {
+                String gidStr = parser.getAttributeValue(null, "gid");
+                if (gidStr != null) {
+                    int gid = Process.getGidForName(gidStr);
+                    bp.gids = appendInt(bp.gids, gid);
+                } else {
+                    Slog.w(TAG, "<group> without gid at "
+                            + parser.getPositionDescription());
+                }
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    static int[] appendInts(int[] cur, int[] add) {
+        if (add == null) return cur;
+        if (cur == null) return add;
+        final int N = add.length;
+        for (int i=0; i<N; i++) {
+            cur = appendInt(cur, add[i]);
+        }
+        return cur;
+    }
+
+    static int[] removeInts(int[] cur, int[] rem) {
+        if (rem == null) return cur;
+        if (cur == null) return cur;
+        final int N = rem.length;
+        for (int i=0; i<N; i++) {
+            cur = removeInt(cur, rem[i]);
+        }
+        return cur;
+    }
+
+    PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        PackageInfo pi;
+        final PackageSetting ps = (PackageSetting) p.mExtras;
+        if (ps == null) {
+            return null;
+        }
+        final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+        final PackageUserState state = ps.readUserState(userId);
+        return PackageParser.generatePackageInfo(p, gp.gids, flags,
+                ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
+                state, userId);
+    }
+
+    public boolean isPackageAvailable(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return false;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "is package available");
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (p != null) {
+                final PackageSetting ps = (PackageSetting) p.mExtras;
+                if (ps != null) {
+                    final PackageUserState state = ps.readUserState(userId);
+                    if (state != null) {
+                        return PackageParser.isAvailable(state);
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(String packageName, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get package info");
+        // reader
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (DEBUG_PACKAGE_INFO)
+                Log.v(TAG, "getPackageInfo " + packageName + ": " + p);
+            if (p != null) {
+                return generatePackageInfo(p, flags, userId);
+            }
+            if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
+                return generatePackageInfoFromSettingsLPw(packageName, flags, userId);
+            }
+        }
+        return null;
+    }
+
+    public String[] currentToCanonicalPackageNames(String[] names) {
+        String[] out = new String[names.length];
+        // reader
+        synchronized (mPackages) {
+            for (int i=names.length-1; i>=0; i--) {
+                PackageSetting ps = mSettings.mPackages.get(names[i]);
+                out[i] = ps != null && ps.realName != null ? ps.realName : names[i];
+            }
+        }
+        return out;
+    }
+    
+    public String[] canonicalToCurrentPackageNames(String[] names) {
+        String[] out = new String[names.length];
+        // reader
+        synchronized (mPackages) {
+            for (int i=names.length-1; i>=0; i--) {
+                String cur = mSettings.mRenamedPackages.get(names[i]);
+                out[i] = cur != null ? cur : names[i];
+            }
+        }
+        return out;
+    }
+
+    @Override
+    public int getPackageUid(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return -1;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get package uid");
+        // reader
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if(p != null) {
+                return UserHandle.getUid(userId, p.applicationInfo.uid);
+            }
+            PackageSetting ps = mSettings.mPackages.get(packageName);
+            if((ps == null) || (ps.pkg == null) || (ps.pkg.applicationInfo == null)) {
+                return -1;
+            }
+            p = ps.pkg;
+            return p != null ? UserHandle.getUid(userId, p.applicationInfo.uid) : -1;
+        }
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName) {
+        // reader
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (DEBUG_PACKAGE_INFO)
+                Log.v(TAG, "getPackageGids" + packageName + ": " + p);
+            if (p != null) {
+                final PackageSetting ps = (PackageSetting)p.mExtras;
+                return ps.getGids();
+            }
+        }
+        // stupid thing to indicate an error.
+        return new int[0];
+    }
+
+    static final PermissionInfo generatePermissionInfo(
+            BasePermission bp, int flags) {
+        if (bp.perm != null) {
+            return PackageParser.generatePermissionInfo(bp.perm, flags);
+        }
+        PermissionInfo pi = new PermissionInfo();
+        pi.name = bp.name;
+        pi.packageName = bp.sourcePackage;
+        pi.nonLocalizedLabel = bp.name;
+        pi.protectionLevel = bp.protectionLevel;
+        return pi;
+    }
+    
+    public PermissionInfo getPermissionInfo(String name, int flags) {
+        // reader
+        synchronized (mPackages) {
+            final BasePermission p = mSettings.mPermissions.get(name);
+            if (p != null) {
+                return generatePermissionInfo(p, flags);
+            }
+            return null;
+        }
+    }
+
+    public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) {
+        // reader
+        synchronized (mPackages) {
+            ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
+            for (BasePermission p : mSettings.mPermissions.values()) {
+                if (group == null) {
+                    if (p.perm == null || p.perm.info.group == null) {
+                        out.add(generatePermissionInfo(p, flags));
+                    }
+                } else {
+                    if (p.perm != null && group.equals(p.perm.info.group)) {
+                        out.add(PackageParser.generatePermissionInfo(p.perm, flags));
+                    }
+                }
+            }
+
+            if (out.size() > 0) {
+                return out;
+            }
+            return mPermissionGroups.containsKey(group) ? out : null;
+        }
+    }
+
+    public PermissionGroupInfo getPermissionGroupInfo(String name, int flags) {
+        // reader
+        synchronized (mPackages) {
+            return PackageParser.generatePermissionGroupInfo(
+                    mPermissionGroups.get(name), flags);
+        }
+    }
+
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        // reader
+        synchronized (mPackages) {
+            final int N = mPermissionGroups.size();
+            ArrayList<PermissionGroupInfo> out
+                    = new ArrayList<PermissionGroupInfo>(N);
+            for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
+                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
+            }
+            return out;
+        }
+    }
+
+    private ApplicationInfo generateApplicationInfoFromSettingsLPw(String packageName, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        PackageSetting ps = mSettings.mPackages.get(packageName);
+        if (ps != null) {
+            if (ps.pkg == null) {
+                PackageInfo pInfo = generatePackageInfoFromSettingsLPw(packageName,
+                        flags, userId);
+                if (pInfo != null) {
+                    return pInfo.applicationInfo;
+                }
+                return null;
+            }
+            return PackageParser.generateApplicationInfo(ps.pkg, flags,
+                    ps.readUserState(userId), userId);
+        }
+        return null;
+    }
+
+    private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        PackageSetting ps = mSettings.mPackages.get(packageName);
+        if (ps != null) {
+            PackageParser.Package pkg = ps.pkg;
+            if (pkg == null) {
+                if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) {
+                    return null;
+                }
+                pkg = new PackageParser.Package(packageName);
+                pkg.applicationInfo.packageName = packageName;
+                pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
+                pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
+                pkg.applicationInfo.sourceDir = ps.codePathString;
+                pkg.applicationInfo.dataDir =
+                        getDataPathForPackage(packageName, 0).getPath();
+                pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString;
+            }
+            return generatePackageInfo(pkg, flags, userId);
+        }
+        return null;
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get application info");
+        // writer
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(packageName);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                    TAG, "getApplicationInfo " + packageName
+                    + ": " + p);
+            if (p != null) {
+                PackageSetting ps = mSettings.mPackages.get(packageName);
+                if (ps == null) return null;
+                // Note: isEnabledLP() does not apply here - always return info
+                return PackageParser.generateApplicationInfo(
+                        p, flags, ps.readUserState(userId), userId);
+            }
+            if ("android".equals(packageName)||"system".equals(packageName)) {
+                return mAndroidApplication;
+            }
+            if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) {
+                return generateApplicationInfoFromSettingsLPw(packageName, flags, userId);
+            }
+        }
+        return null;
+    }
+
+
+    public void freeStorageAndNotify(final long freeStorageSize, final IPackageDataObserver observer) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CLEAR_APP_CACHE, null);
+        // Queue up an async operation since clearing cache may take a little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                int retCode = -1;
+                synchronized (mInstallLock) {
+                    retCode = mInstaller.freeCache(freeStorageSize);
+                    if (retCode < 0) {
+                        Slog.w(TAG, "Couldn't clear application caches");
+                    }
+                }
+                if (observer != null) {
+                    try {
+                        observer.onRemoveCompleted(null, (retCode >= 0));
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "RemoveException when invoking call back");
+                    }
+                }
+            }
+        });
+    }
+
+    public void freeStorage(final long freeStorageSize, final IntentSender pi) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CLEAR_APP_CACHE, null);
+        // Queue up an async operation since clearing cache may take a little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                int retCode = -1;
+                synchronized (mInstallLock) {
+                    retCode = mInstaller.freeCache(freeStorageSize);
+                    if (retCode < 0) {
+                        Slog.w(TAG, "Couldn't clear application caches");
+                    }
+                }
+                if(pi != null) {
+                    try {
+                        // Callback via pending intent
+                        int code = (retCode >= 0) ? 1 : 0;
+                        pi.sendIntent(null, code, null,
+                                null, null);
+                    } catch (SendIntentException e1) {
+                        Slog.i(TAG, "Failed to send pending intent");
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get activity info");
+        synchronized (mPackages) {
+            PackageParser.Activity a = mActivities.mActivities.get(component);
+
+            if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a);
+            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
+                        userId);
+            }
+            if (mResolveComponentName.equals(component)) {
+                return mResolveActivity;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get receiver info");
+        synchronized (mPackages) {
+            PackageParser.Activity a = mReceivers.mActivities.get(component);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                TAG, "getReceiverInfo " + component + ": " + a);
+            if (a != null && mSettings.isEnabledLPr(a.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateActivityInfo(a, flags, ps.readUserState(userId),
+                        userId);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get service info");
+        synchronized (mPackages) {
+            PackageParser.Service s = mServices.mServices.get(component);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                TAG, "getServiceInfo " + component + ": " + s);
+            if (s != null && mSettings.isEnabledLPr(s.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateServiceInfo(s, flags, ps.readUserState(userId),
+                        userId);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName component, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get provider info");
+        synchronized (mPackages) {
+            PackageParser.Provider p = mProviders.mProviders.get(component);
+            if (DEBUG_PACKAGE_INFO) Log.v(
+                TAG, "getProviderInfo " + component + ": " + p);
+            if (p != null && mSettings.isEnabledLPr(p.info, flags, userId)) {
+                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                if (ps == null) return null;
+                return PackageParser.generateProviderInfo(p, flags, ps.readUserState(userId),
+                        userId);
+            }
+        }
+        return null;
+    }
+
+    public String[] getSystemSharedLibraryNames() {
+        Set<String> libSet;
+        synchronized (mPackages) {
+            libSet = mSharedLibraries.keySet();
+            int size = libSet.size();
+            if (size > 0) {
+                String[] libs = new String[size];
+                libSet.toArray(libs);
+                return libs;
+            }
+        }
+        return null;
+    }
+
+    public FeatureInfo[] getSystemAvailableFeatures() {
+        Collection<FeatureInfo> featSet;
+        synchronized (mPackages) {
+            featSet = mAvailableFeatures.values();
+            int size = featSet.size();
+            if (size > 0) {
+                FeatureInfo[] features = new FeatureInfo[size+1];
+                featSet.toArray(features);
+                FeatureInfo fi = new FeatureInfo();
+                fi.reqGlEsVersion = SystemProperties.getInt("ro.opengles.version",
+                        FeatureInfo.GL_ES_VERSION_UNDEFINED);
+                features[size] = fi;
+                return features;
+            }
+        }
+        return null;
+    }
+
+    public boolean hasSystemFeature(String name) {
+        synchronized (mPackages) {
+            return mAvailableFeatures.containsKey(name);
+        }
+    }
+
+    private void checkValidCaller(int uid, int userId) {
+        if (UserHandle.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0)
+            return;
+
+        throw new SecurityException("Caller uid=" + uid
+                + " is not privileged to communicate with user=" + userId);
+    }
+
+    public int checkPermission(String permName, String pkgName) {
+        synchronized (mPackages) {
+            PackageParser.Package p = mPackages.get(pkgName);
+            if (p != null && p.mExtras != null) {
+                PackageSetting ps = (PackageSetting)p.mExtras;
+                if (ps.sharedUser != null) {
+                    if (ps.sharedUser.grantedPermissions.contains(permName)) {
+                        return PackageManager.PERMISSION_GRANTED;
+                    }
+                } else if (ps.grantedPermissions.contains(permName)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    public int checkUidPermission(String permName, int uid) {
+        synchronized (mPackages) {
+            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
+            if (obj != null) {
+                GrantedPermissions gp = (GrantedPermissions)obj;
+                if (gp.grantedPermissions.contains(permName)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            } else {
+                HashSet<String> perms = mSystemPermissions.get(uid);
+                if (perms != null && perms.contains(permName)) {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+        }
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
+     * @param message the message to log on security exception
+     * @return
+     */
+    private void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (userId == UserHandle.getUserId(callingUid)) return;
+        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+            if (requireFullPermission) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+            } else {
+                try {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+                } catch (SecurityException se) {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
+                }
+            }
+        }
+    }
+
+    private BasePermission findPermissionTreeLP(String permName) {
+        for(BasePermission bp : mSettings.mPermissionTrees.values()) {
+            if (permName.startsWith(bp.name) &&
+                    permName.length() > bp.name.length() &&
+                    permName.charAt(bp.name.length()) == '.') {
+                return bp;
+            }
+        }
+        return null;
+    }
+
+    private BasePermission checkPermissionTreeLP(String permName) {
+        if (permName != null) {
+            BasePermission bp = findPermissionTreeLP(permName);
+            if (bp != null) {
+                if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
+                    return bp;
+                }
+                throw new SecurityException("Calling uid "
+                        + Binder.getCallingUid()
+                        + " is not allowed to add to permission tree "
+                        + bp.name + " owned by uid " + bp.uid);
+            }
+        }
+        throw new SecurityException("No permission tree found for " + permName);
+    }
+
+    static boolean compareStrings(CharSequence s1, CharSequence s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        if (s2 == null) {
+            return false;
+        }
+        if (s1.getClass() != s2.getClass()) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+    
+    static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+        if (pi1.icon != pi2.icon) return false;
+        if (pi1.logo != pi2.logo) return false;
+        if (pi1.protectionLevel != pi2.protectionLevel) return false;
+        if (!compareStrings(pi1.name, pi2.name)) return false;
+        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+        // We'll take care of setting this one.
+        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        // These are not currently stored in settings.
+        //if (!compareStrings(pi1.group, pi2.group)) return false;
+        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+        //if (pi1.labelRes != pi2.labelRes) return false;
+        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+        return true;
+    }
+    
+    boolean addPermissionLocked(PermissionInfo info, boolean async) {
+        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+            throw new SecurityException("Label must be specified in permission");
+        }
+        BasePermission tree = checkPermissionTreeLP(info.name);
+        BasePermission bp = mSettings.mPermissions.get(info.name);
+        boolean added = bp == null;
+        boolean changed = true;
+        int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+        if (added) {
+            bp = new BasePermission(info.name, tree.sourcePackage,
+                    BasePermission.TYPE_DYNAMIC);
+        } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
+            throw new SecurityException(
+                    "Not allowed to modify non-dynamic permission "
+                    + info.name);
+        } else {
+            if (bp.protectionLevel == fixedLevel
+                    && bp.perm.owner.equals(tree.perm.owner)
+                    && bp.uid == tree.uid
+                    && comparePermissionInfos(bp.perm.info, info)) {
+                changed = false;
+            }
+        }
+        bp.protectionLevel = fixedLevel;
+        info = new PermissionInfo(info);
+        info.protectionLevel = fixedLevel;
+        bp.perm = new PackageParser.Permission(tree.perm.owner, info);
+        bp.perm.info.packageName = tree.perm.info.packageName;
+        bp.uid = tree.uid;
+        if (added) {
+            mSettings.mPermissions.put(info.name, bp);
+        }
+        if (changed) {
+            if (!async) {
+                mSettings.writeLPr();
+            } else {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        return added;
+    }
+
+    public boolean addPermission(PermissionInfo info) {
+        synchronized (mPackages) {
+            return addPermissionLocked(info, false);
+        }
+    }
+
+    public boolean addPermissionAsync(PermissionInfo info) {
+        synchronized (mPackages) {
+            return addPermissionLocked(info, true);
+        }
+    }
+
+    public void removePermission(String name) {
+        synchronized (mPackages) {
+            checkPermissionTreeLP(name);
+            BasePermission bp = mSettings.mPermissions.get(name);
+            if (bp != null) {
+                if (bp.type != BasePermission.TYPE_DYNAMIC) {
+                    throw new SecurityException(
+                            "Not allowed to modify non-dynamic permission "
+                            + name);
+                }
+                mSettings.mPermissions.remove(name);
+                mSettings.writeLPr();
+            }
+        }
+    }
+
+    private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) {
+        int index = pkg.requestedPermissions.indexOf(bp.name);
+        if (index == -1) {
+            throw new SecurityException("Package " + pkg.packageName
+                    + " has not requested permission " + bp.name);
+        }
+        boolean isNormal =
+                ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+                        == PermissionInfo.PROTECTION_NORMAL);
+        boolean isDangerous =
+                ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
+                        == PermissionInfo.PROTECTION_DANGEROUS);
+        boolean isDevelopment =
+                ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
+
+        if (!isNormal && !isDangerous && !isDevelopment) {
+            throw new SecurityException("Permission " + bp.name
+                    + " is not a changeable permission type");
+        }
+
+        if (isNormal || isDangerous) {
+            if (pkg.requestedPermissionsRequired.get(index)) {
+                throw new SecurityException("Can't change " + bp.name
+                        + ". It is required by the application");
+            }
+        }
+    }
+
+    public void grantPermission(String packageName, String permissionName) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
+        synchronized (mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            final BasePermission bp = mSettings.mPermissions.get(permissionName);
+            if (bp == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permissionName);
+            }
+
+            checkGrantRevokePermissions(pkg, bp);
+
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            if (ps == null) {
+                return;
+            }
+            final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
+            if (gp.grantedPermissions.add(permissionName)) {
+                if (ps.haveGids) {
+                    gp.gids = appendInts(gp.gids, bp.gids);
+                }
+                mSettings.writeLPr();
+            }
+        }
+    }
+
+    public void revokePermission(String packageName, String permissionName) {
+        int changedAppId = -1;
+
+        synchronized (mPackages) {
+            final PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                throw new IllegalArgumentException("Unknown package: " + packageName);
+            }
+            if (pkg.applicationInfo.uid != Binder.getCallingUid()) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
+            }
+            final BasePermission bp = mSettings.mPermissions.get(permissionName);
+            if (bp == null) {
+                throw new IllegalArgumentException("Unknown permission: " + permissionName);
+            }
+
+            checkGrantRevokePermissions(pkg, bp);
+
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            if (ps == null) {
+                return;
+            }
+            final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
+            if (gp.grantedPermissions.remove(permissionName)) {
+                gp.grantedPermissions.remove(permissionName);
+                if (ps.haveGids) {
+                    gp.gids = removeInts(gp.gids, bp.gids);
+                }
+                mSettings.writeLPr();
+                changedAppId = ps.appId;
+            }
+        }
+
+        if (changedAppId >= 0) {
+            // We changed the perm on someone, kill its processes.
+            IActivityManager am = ActivityManagerNative.getDefault();
+            if (am != null) {
+                final int callingUserId = UserHandle.getCallingUserId();
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    //XXX we should only revoke for the calling user's app permissions,
+                    // but for now we impact all users.
+                    //am.killUid(UserHandle.getUid(callingUserId, changedAppId),
+                    //        "revoke " + permissionName);
+                    int[] users = sUserManager.getUserIds();
+                    for (int user : users) {
+                        am.killUid(UserHandle.getUid(user, changedAppId),
+                                "revoke " + permissionName);
+                    }
+                } catch (RemoteException e) {
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        }
+    }
+
+    public boolean isProtectedBroadcast(String actionName) {
+        synchronized (mPackages) {
+            return mProtectedBroadcasts.contains(actionName);
+        }
+    }
+
+    public int checkSignatures(String pkg1, String pkg2) {
+        synchronized (mPackages) {
+            final PackageParser.Package p1 = mPackages.get(pkg1);
+            final PackageParser.Package p2 = mPackages.get(pkg2);
+            if (p1 == null || p1.mExtras == null
+                    || p2 == null || p2.mExtras == null) {
+                return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
+            }
+            return compareSignatures(p1.mSignatures, p2.mSignatures);
+        }
+    }
+
+    public int checkUidSignatures(int uid1, int uid2) {
+        // Map to base uids.
+        uid1 = UserHandle.getAppId(uid1);
+        uid2 = UserHandle.getAppId(uid2);
+        // reader
+        synchronized (mPackages) {
+            Signature[] s1;
+            Signature[] s2;
+            Object obj = mSettings.getUserIdLPr(uid1);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    s1 = ((SharedUserSetting)obj).signatures.mSignatures;
+                } else if (obj instanceof PackageSetting) {
+                    s1 = ((PackageSetting)obj).signatures.mSignatures;
+                } else {
+                    return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
+                }
+            } else {
+                return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
+            }
+            obj = mSettings.getUserIdLPr(uid2);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    s2 = ((SharedUserSetting)obj).signatures.mSignatures;
+                } else if (obj instanceof PackageSetting) {
+                    s2 = ((PackageSetting)obj).signatures.mSignatures;
+                } else {
+                    return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
+                }
+            } else {
+                return PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
+            }
+            return compareSignatures(s1, s2);
+        }
+    }
+
+    static int compareSignatures(Signature[] s1, Signature[] s2) {
+        if (s1 == null) {
+            return s2 == null
+                    ? PackageManager.SIGNATURE_NEITHER_SIGNED
+                    : PackageManager.SIGNATURE_FIRST_NOT_SIGNED;
+        }
+        if (s2 == null) {
+            return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
+        }
+        HashSet<Signature> set1 = new HashSet<Signature>();
+        for (Signature sig : s1) {
+            set1.add(sig);
+        }
+        HashSet<Signature> set2 = new HashSet<Signature>();
+        for (Signature sig : s2) {
+            set2.add(sig);
+        }
+        // Make sure s2 contains all signatures in s1.
+        if (set1.equals(set2)) {
+            return PackageManager.SIGNATURE_MATCH;
+        }
+        return PackageManager.SIGNATURE_NO_MATCH;
+    }
+
+    public String[] getPackagesForUid(int uid) {
+        uid = UserHandle.getAppId(uid);
+        // reader
+        synchronized (mPackages) {
+            Object obj = mSettings.getUserIdLPr(uid);
+            if (obj instanceof SharedUserSetting) {
+                final SharedUserSetting sus = (SharedUserSetting) obj;
+                final int N = sus.packages.size();
+                final String[] res = new String[N];
+                final Iterator<PackageSetting> it = sus.packages.iterator();
+                int i = 0;
+                while (it.hasNext()) {
+                    res[i++] = it.next().name;
+                }
+                return res;
+            } else if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                return new String[] { ps.name };
+            }
+        }
+        return null;
+    }
+
+    public String getNameForUid(int uid) {
+        // reader
+        synchronized (mPackages) {
+            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
+            if (obj instanceof SharedUserSetting) {
+                final SharedUserSetting sus = (SharedUserSetting) obj;
+                return sus.name + ":" + sus.userId;
+            } else if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                return ps.name;
+            }
+        }
+        return null;
+    }
+
+    public int getUidForSharedUser(String sharedUserName) {
+        if(sharedUserName == null) {
+            return -1;
+        }
+        // reader
+        synchronized (mPackages) {
+            final SharedUserSetting suid = mSettings.getSharedUserLPw(sharedUserName, 0, false);
+            if (suid == null) {
+                return -1;
+            }
+            return suid.userId;
+        }
+    }
+
+    public int getFlagsForUid(int uid) {
+        synchronized (mPackages) {
+            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
+            if (obj instanceof SharedUserSetting) {
+                final SharedUserSetting sus = (SharedUserSetting) obj;
+                return sus.pkgFlags;
+            } else if (obj instanceof PackageSetting) {
+                final PackageSetting ps = (PackageSetting) obj;
+                return ps.pkgFlags;
+            }
+        }
+        return 0;
+    }
+
+    @Override
+    public ResolveInfo resolveIntent(Intent intent, String resolvedType,
+            int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "resolve intent");
+        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
+        return chooseBestActivity(intent, resolvedType, flags, query, userId);
+    }
+
+    @Override
+    public void setLastChosenActivity(Intent intent, String resolvedType, int flags,
+            IntentFilter filter, int match, ComponentName activity) {
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG_PREFERRED) {
+            Log.v(TAG, "setLastChosenActivity intent=" + intent
+                + " resolvedType=" + resolvedType
+                + " flags=" + flags
+                + " filter=" + filter
+                + " match=" + match
+                + " activity=" + activity);
+            filter.dump(new PrintStreamPrinter(System.out), "    ");
+        }
+        intent.setComponent(null);
+        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
+        // Find any earlier preferred or last chosen entries and nuke them
+        findPreferredActivity(intent, resolvedType,
+                flags, query, 0, false, true, false, userId);
+        // Add the new activity as the last chosen for this filter
+        addPreferredActivityInternal(filter, match, null, activity, false, userId);
+    }
+
+    @Override
+    public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) {
+        final int userId = UserHandle.getCallingUserId();
+        if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent);
+        List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId);
+        return findPreferredActivity(intent, resolvedType, flags, query, 0,
+                false, false, false, userId);
+    }
+
+    private ResolveInfo chooseBestActivity(Intent intent, String resolvedType,
+            int flags, List<ResolveInfo> query, int userId) {
+        if (query != null) {
+            final int N = query.size();
+            if (N == 1) {
+                return query.get(0);
+            } else if (N > 1) {
+                final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
+                // If there is more than one activity with the same priority,
+                // then let the user decide between them.
+                ResolveInfo r0 = query.get(0);
+                ResolveInfo r1 = query.get(1);
+                if (DEBUG_INTENT_MATCHING || debug) {
+                    Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs "
+                            + r1.activityInfo.name + "=" + r1.priority);
+                }
+                // If the first activity has a higher priority, or a different
+                // default, then it is always desireable to pick it.
+                if (r0.priority != r1.priority
+                        || r0.preferredOrder != r1.preferredOrder
+                        || r0.isDefault != r1.isDefault) {
+                    return query.get(0);
+                }
+                // If we have saved a preference for a preferred activity for
+                // this Intent, use that.
+                ResolveInfo ri = findPreferredActivity(intent, resolvedType,
+                        flags, query, r0.priority, true, false, debug, userId);
+                if (ri != null) {
+                    return ri;
+                }
+                if (userId != 0) {
+                    ri = new ResolveInfo(mResolveInfo);
+                    ri.activityInfo = new ActivityInfo(ri.activityInfo);
+                    ri.activityInfo.applicationInfo = new ApplicationInfo(
+                            ri.activityInfo.applicationInfo);
+                    ri.activityInfo.applicationInfo.uid = UserHandle.getUid(userId,
+                            UserHandle.getAppId(ri.activityInfo.applicationInfo.uid));
+                    return ri;
+                }
+                return mResolveInfo;
+            }
+        }
+        return null;
+    }
+
+    ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags,
+            List<ResolveInfo> query, int priority, boolean always,
+            boolean removeMatches, boolean debug, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        // writer
+        synchronized (mPackages) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector();
+            }
+            if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION);
+            PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
+            // Get the list of preferred activities that handle the intent
+            if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
+            List<PreferredActivity> prefs = pir != null
+                    ? pir.queryIntent(intent, resolvedType,
+                            (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId)
+                    : null;
+            if (prefs != null && prefs.size() > 0) {
+                // First figure out how good the original match set is.
+                // We will only allow preferred activities that came
+                // from the same match quality.
+                int match = 0;
+
+                if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
+
+                final int N = query.size();
+                for (int j=0; j<N; j++) {
+                    final ResolveInfo ri = query.get(j);
+                    if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo
+                            + ": 0x" + Integer.toHexString(match));
+                    if (ri.match > match) {
+                        match = ri.match;
+                    }
+                }
+
+                if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
+                        + Integer.toHexString(match));
+
+                match &= IntentFilter.MATCH_CATEGORY_MASK;
+                final int M = prefs.size();
+                for (int i=0; i<M; i++) {
+                    final PreferredActivity pa = prefs.get(i);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Checking PreferredActivity ds="
+                                + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
+                                + "\n  component=" + pa.mPref.mComponent);
+                        pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                    }
+                    if (pa.mPref.mMatch != match) {
+                        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
+                                + Integer.toHexString(pa.mPref.mMatch));
+                        continue;
+                    }
+                    // If it's not an "always" type preferred activity and that's what we're
+                    // looking for, skip it.
+                    if (always && !pa.mPref.mAlways) {
+                        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
+                        continue;
+                    }
+                    final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent,
+                            flags | PackageManager.GET_DISABLED_COMPONENTS, userId);
+                    if (DEBUG_PREFERRED || debug) {
+                        Slog.v(TAG, "Found preferred activity:");
+                        if (ai != null) {
+                            ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "  ");
+                        } else {
+                            Slog.v(TAG, "  null");
+                        }
+                    }
+                    if (ai == null) {
+                        // This previously registered preferred activity
+                        // component is no longer known.  Most likely an update
+                        // to the app was installed and in the new version this
+                        // component no longer exists.  Clean it up by removing
+                        // it from the preferred activities list, and skip it.
+                        Slog.w(TAG, "Removing dangling preferred activity: "
+                                + pa.mPref.mComponent);
+                        pir.removeFilter(pa);
+                        continue;
+                    }
+                    for (int j=0; j<N; j++) {
+                        final ResolveInfo ri = query.get(j);
+                        if (!ri.activityInfo.applicationInfo.packageName
+                                .equals(ai.applicationInfo.packageName)) {
+                            continue;
+                        }
+                        if (!ri.activityInfo.name.equals(ai.name)) {
+                            continue;
+                        }
+
+                        if (removeMatches) {
+                            pir.removeFilter(pa);
+                            if (DEBUG_PREFERRED) {
+                                Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
+                            }
+                            break;
+                        }
+
+                        // Okay we found a previously set preferred or last chosen app.
+                        // If the result set is different from when this
+                        // was created, we need to clear it and re-ask the
+                        // user their preference, if we're looking for an "always" type entry.
+                        if (always && !pa.mPref.sameSet(query, priority)) {
+                            Slog.i(TAG, "Result set changed, dropping preferred activity for "
+                                    + intent + " type " + resolvedType);
+                            if (DEBUG_PREFERRED) {
+                                Slog.v(TAG, "Removing preferred activity since set changed "
+                                        + pa.mPref.mComponent);
+                            }
+                            pir.removeFilter(pa);
+                            // Re-add the filter as a "last chosen" entry (!always)
+                            PreferredActivity lastChosen = new PreferredActivity(
+                                    pa, pa.mPref.mMatch, null, pa.mPref.mComponent, false);
+                            pir.addFilter(lastChosen);
+                            mSettings.writePackageRestrictionsLPr(userId);
+                            return null;
+                        }
+
+                        // Yay! Either the set matched or we're looking for the last chosen
+                        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: "
+                                + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+                        mSettings.writePackageRestrictionsLPr(userId);
+                        return ri;
+                    }
+                }
+            }
+            mSettings.writePackageRestrictionsLPr(userId);
+        }
+        if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return");
+        return null;
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivities(Intent intent,
+            String resolvedType, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return Collections.emptyList();
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "query intent activities");
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector(); 
+                comp = intent.getComponent();
+            }
+        }
+
+        if (comp != null) {
+            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
+            final ActivityInfo ai = getActivityInfo(comp, flags, userId);
+            if (ai != null) {
+                final ResolveInfo ri = new ResolveInfo();
+                ri.activityInfo = ai;
+                list.add(ri);
+            }
+            return list;
+        }
+
+        // reader
+        synchronized (mPackages) {
+            final String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                return mActivities.queryIntent(intent, resolvedType, flags, userId);
+            }
+            final PackageParser.Package pkg = mPackages.get(pkgName);
+            if (pkg != null) {
+                return mActivities.queryIntentForPackage(intent, resolvedType, flags,
+                        pkg.activities, userId);
+            }
+            return new ArrayList<ResolveInfo>();
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller,
+            Intent[] specifics, String[] specificTypes, Intent intent,
+            String resolvedType, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return Collections.emptyList();
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, false,
+                "query intent activity options");
+        final String resultsAction = intent.getAction();
+
+        List<ResolveInfo> results = queryIntentActivities(intent, resolvedType, flags
+                | PackageManager.GET_RESOLVED_FILTER, userId);
+
+        if (DEBUG_INTENT_MATCHING) {
+            Log.v(TAG, "Query " + intent + ": " + results);
+        }
+
+        int specificsPos = 0;
+        int N;
+
+        // todo: note that the algorithm used here is O(N^2).  This
+        // isn't a problem in our current environment, but if we start running
+        // into situations where we have more than 5 or 10 matches then this
+        // should probably be changed to something smarter...
+
+        // First we go through and resolve each of the specific items
+        // that were supplied, taking care of removing any corresponding
+        // duplicate items in the generic resolve list.
+        if (specifics != null) {
+            for (int i=0; i<specifics.length; i++) {
+                final Intent sintent = specifics[i];
+                if (sintent == null) {
+                    continue;
+                }
+
+                if (DEBUG_INTENT_MATCHING) {
+                    Log.v(TAG, "Specific #" + i + ": " + sintent);
+                }
+
+                String action = sintent.getAction();
+                if (resultsAction != null && resultsAction.equals(action)) {
+                    // If this action was explicitly requested, then don't
+                    // remove things that have it.
+                    action = null;
+                }
+
+                ResolveInfo ri = null;
+                ActivityInfo ai = null;
+
+                ComponentName comp = sintent.getComponent();
+                if (comp == null) {
+                    ri = resolveIntent(
+                        sintent,
+                        specificTypes != null ? specificTypes[i] : null,
+                            flags, userId);
+                    if (ri == null) {
+                        continue;
+                    }
+                    if (ri == mResolveInfo) {
+                        // ACK!  Must do something better with this.
+                    }
+                    ai = ri.activityInfo;
+                    comp = new ComponentName(ai.applicationInfo.packageName,
+                            ai.name);
+                } else {
+                    ai = getActivityInfo(comp, flags, userId);
+                    if (ai == null) {
+                        continue;
+                    }
+                }
+
+                // Look for any generic query activities that are duplicates
+                // of this specific one, and remove them from the results.
+                if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Specific #" + i + ": " + ai);
+                N = results.size();
+                int j;
+                for (j=specificsPos; j<N; j++) {
+                    ResolveInfo sri = results.get(j);
+                    if ((sri.activityInfo.name.equals(comp.getClassName())
+                            && sri.activityInfo.applicationInfo.packageName.equals(
+                                    comp.getPackageName()))
+                        || (action != null && sri.filter.matchAction(action))) {
+                        results.remove(j);
+                        if (DEBUG_INTENT_MATCHING) Log.v(
+                            TAG, "Removing duplicate item from " + j
+                            + " due to specific " + specificsPos);
+                        if (ri == null) {
+                            ri = sri;
+                        }
+                        j--;
+                        N--;
+                    }
+                }
+
+                // Add this specific item to its proper place.
+                if (ri == null) {
+                    ri = new ResolveInfo();
+                    ri.activityInfo = ai;
+                }
+                results.add(specificsPos, ri);
+                ri.specificIndex = i;
+                specificsPos++;
+            }
+        }
+
+        // Now we go through the remaining generic results and remove any
+        // duplicate actions that are found here.
+        N = results.size();
+        for (int i=specificsPos; i<N-1; i++) {
+            final ResolveInfo rii = results.get(i);
+            if (rii.filter == null) {
+                continue;
+            }
+
+            // Iterate over all of the actions of this result's intent
+            // filter...  typically this should be just one.
+            final Iterator<String> it = rii.filter.actionsIterator();
+            if (it == null) {
+                continue;
+            }
+            while (it.hasNext()) {
+                final String action = it.next();
+                if (resultsAction != null && resultsAction.equals(action)) {
+                    // If this action was explicitly requested, then don't
+                    // remove things that have it.
+                    continue;
+                }
+                for (int j=i+1; j<N; j++) {
+                    final ResolveInfo rij = results.get(j);
+                    if (rij.filter != null && rij.filter.hasAction(action)) {
+                        results.remove(j);
+                        if (DEBUG_INTENT_MATCHING) Log.v(
+                            TAG, "Removing duplicate item from " + j
+                            + " due to action " + action + " at " + i);
+                        j--;
+                        N--;
+                    }
+                }
+            }
+
+            // If the caller didn't request filter information, drop it now
+            // so we don't have to marshall/unmarshall it.
+            if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
+                rii.filter = null;
+            }
+        }
+
+        // Filter out the caller activity if so requested.
+        if (caller != null) {
+            N = results.size();
+            for (int i=0; i<N; i++) {
+                ActivityInfo ainfo = results.get(i).activityInfo;
+                if (caller.getPackageName().equals(ainfo.applicationInfo.packageName)
+                        && caller.getClassName().equals(ainfo.name)) {
+                    results.remove(i);
+                    break;
+                }
+            }
+        }
+
+        // If the caller didn't request filter information,
+        // drop them now so we don't have to
+        // marshall/unmarshall it.
+        if ((flags&PackageManager.GET_RESOLVED_FILTER) == 0) {
+            N = results.size();
+            for (int i=0; i<N; i++) {
+                results.get(i).filter = null;
+            }
+        }
+
+        if (DEBUG_INTENT_MATCHING) Log.v(TAG, "Result: " + results);
+        return results;
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return Collections.emptyList();
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector(); 
+                comp = intent.getComponent();
+            }
+        }
+        if (comp != null) {
+            List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
+            ActivityInfo ai = getReceiverInfo(comp, flags, userId);
+            if (ai != null) {
+                ResolveInfo ri = new ResolveInfo();
+                ri.activityInfo = ai;
+                list.add(ri);
+            }
+            return list;
+        }
+
+        // reader
+        synchronized (mPackages) {
+            String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                return mReceivers.queryIntent(intent, resolvedType, flags, userId);
+            }
+            final PackageParser.Package pkg = mPackages.get(pkgName);
+            if (pkg != null) {
+                return mReceivers.queryIntentForPackage(intent, resolvedType, flags, pkg.receivers,
+                        userId);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public ResolveInfo resolveService(Intent intent, String resolvedType, int flags, int userId) {
+        List<ResolveInfo> query = queryIntentServices(intent, resolvedType, flags, userId);
+        if (!sUserManager.exists(userId)) return null;
+        if (query != null) {
+            if (query.size() >= 1) {
+                // If there is more than one service with the same priority,
+                // just arbitrarily pick the first one.
+                return query.get(0);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentServices(Intent intent, String resolvedType, int flags,
+            int userId) {
+        if (!sUserManager.exists(userId)) return Collections.emptyList();
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector(); 
+                comp = intent.getComponent();
+            }
+        }
+        if (comp != null) {
+            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
+            final ServiceInfo si = getServiceInfo(comp, flags, userId);
+            if (si != null) {
+                final ResolveInfo ri = new ResolveInfo();
+                ri.serviceInfo = si;
+                list.add(ri);
+            }
+            return list;
+        }
+
+        // reader
+        synchronized (mPackages) {
+            String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                return mServices.queryIntent(intent, resolvedType, flags, userId);
+            }
+            final PackageParser.Package pkg = mPackages.get(pkgName);
+            if (pkg != null) {
+                return mServices.queryIntentForPackage(intent, resolvedType, flags, pkg.services,
+                        userId);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentContentProviders(
+            Intent intent, String resolvedType, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return Collections.emptyList();
+        ComponentName comp = intent.getComponent();
+        if (comp == null) {
+            if (intent.getSelector() != null) {
+                intent = intent.getSelector();
+                comp = intent.getComponent();
+            }
+        }
+        if (comp != null) {
+            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
+            final ProviderInfo pi = getProviderInfo(comp, flags, userId);
+            if (pi != null) {
+                final ResolveInfo ri = new ResolveInfo();
+                ri.providerInfo = pi;
+                list.add(ri);
+            }
+            return list;
+        }
+
+        // reader
+        synchronized (mPackages) {
+            String pkgName = intent.getPackage();
+            if (pkgName == null) {
+                return mProviders.queryIntent(intent, resolvedType, flags, userId);
+            }
+            final PackageParser.Package pkg = mPackages.get(pkgName);
+            if (pkg != null) {
+                return mProviders.queryIntentForPackage(
+                        intent, resolvedType, flags, pkg.providers, userId);
+            }
+            return null;
+        }
+    }
+
+    @Override
+    public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) {
+        final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
+
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages");
+
+        // writer
+        synchronized (mPackages) {
+            ArrayList<PackageInfo> list;
+            if (listUninstalled) {
+                list = new ArrayList<PackageInfo>(mSettings.mPackages.size());
+                for (PackageSetting ps : mSettings.mPackages.values()) {
+                    PackageInfo pi;
+                    if (ps.pkg != null) {
+                        pi = generatePackageInfo(ps.pkg, flags, userId);
+                    } else {
+                        pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
+                    }
+                    if (pi != null) {
+                        list.add(pi);
+                    }
+                }
+            } else {
+                list = new ArrayList<PackageInfo>(mPackages.size());
+                for (PackageParser.Package p : mPackages.values()) {
+                    PackageInfo pi = generatePackageInfo(p, flags, userId);
+                    if (pi != null) {
+                        list.add(pi);
+                    }
+                }
+            }
+
+            return new ParceledListSlice<PackageInfo>(list);
+        }
+    }
+
+    private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps,
+            String[] permissions, boolean[] tmp, int flags, int userId) {
+        int numMatch = 0;
+        final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+        for (int i=0; i<permissions.length; i++) {
+            if (gp.grantedPermissions.contains(permissions[i])) {
+                tmp[i] = true;
+                numMatch++;
+            } else {
+                tmp[i] = false;
+            }
+        }
+        if (numMatch == 0) {
+            return;
+        }
+        PackageInfo pi;
+        if (ps.pkg != null) {
+            pi = generatePackageInfo(ps.pkg, flags, userId);
+        } else {
+            pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId);
+        }
+        if ((flags&PackageManager.GET_PERMISSIONS) == 0) {
+            if (numMatch == permissions.length) {
+                pi.requestedPermissions = permissions;
+            } else {
+                pi.requestedPermissions = new String[numMatch];
+                numMatch = 0;
+                for (int i=0; i<permissions.length; i++) {
+                    if (tmp[i]) {
+                        pi.requestedPermissions[numMatch] = permissions[i];
+                        numMatch++;
+                    }
+                }
+            }
+        }
+        list.add(pi);
+    }
+
+    @Override
+    public ParceledListSlice<PackageInfo> getPackagesHoldingPermissions(
+            String[] permissions, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
+
+        // writer
+        synchronized (mPackages) {
+            ArrayList<PackageInfo> list = new ArrayList<PackageInfo>();
+            boolean[] tmpBools = new boolean[permissions.length];
+            if (listUninstalled) {
+                for (PackageSetting ps : mSettings.mPackages.values()) {
+                    addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags, userId);
+                }
+            } else {
+                for (PackageParser.Package pkg : mPackages.values()) {
+                    PackageSetting ps = (PackageSetting)pkg.mExtras;
+                    if (ps != null) {
+                        addPackageHoldingPermissions(list, ps, permissions, tmpBools, flags,
+                                userId);
+                    }
+                }
+            }
+
+            return new ParceledListSlice<PackageInfo>(list);
+        }
+    }
+
+    @Override
+    public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0;
+
+        // writer
+        synchronized (mPackages) {
+            ArrayList<ApplicationInfo> list;
+            if (listUninstalled) {
+                list = new ArrayList<ApplicationInfo>(mSettings.mPackages.size());
+                for (PackageSetting ps : mSettings.mPackages.values()) {
+                    ApplicationInfo ai;
+                    if (ps.pkg != null) {
+                        ai = PackageParser.generateApplicationInfo(ps.pkg, flags,
+                                ps.readUserState(userId), userId);
+                    } else {
+                        ai = generateApplicationInfoFromSettingsLPw(ps.name, flags, userId);
+                    }
+                    if (ai != null) {
+                        list.add(ai);
+                    }
+                }
+            } else {
+                list = new ArrayList<ApplicationInfo>(mPackages.size());
+                for (PackageParser.Package p : mPackages.values()) {
+                    if (p.mExtras != null) {
+                        ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
+                                ((PackageSetting)p.mExtras).readUserState(userId), userId);
+                        if (ai != null) {
+                            list.add(ai);
+                        }
+                    }
+                }
+            }
+
+            return new ParceledListSlice<ApplicationInfo>(list);
+        }
+    }
+
+    public List<ApplicationInfo> getPersistentApplications(int flags) {
+        final ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>();
+
+        // reader
+        synchronized (mPackages) {
+            final Iterator<PackageParser.Package> i = mPackages.values().iterator();
+            final int userId = UserHandle.getCallingUserId();
+            while (i.hasNext()) {
+                final PackageParser.Package p = i.next();
+                if (p.applicationInfo != null
+                        && (p.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) != 0
+                        && (!mSafeMode || isSystemApp(p))) {
+                    PackageSetting ps = mSettings.mPackages.get(p.packageName);
+                    if (ps != null) {
+                        ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags,
+                                ps.readUserState(userId), userId);
+                        if (ai != null) {
+                            finalList.add(ai);
+                        }
+                    }
+                }
+            }
+        }
+
+        return finalList;
+    }
+
+    @Override
+    public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return null;
+        // reader
+        synchronized (mPackages) {
+            final PackageParser.Provider provider = mProvidersByAuthority.get(name);
+            PackageSetting ps = provider != null
+                    ? mSettings.mPackages.get(provider.owner.packageName)
+                    : null;
+            return ps != null
+                    && mSettings.isEnabledLPr(provider.info, flags, userId)
+                    && (!mSafeMode || (provider.info.applicationInfo.flags
+                            &ApplicationInfo.FLAG_SYSTEM) != 0)
+                    ? PackageParser.generateProviderInfo(provider, flags,
+                            ps.readUserState(userId), userId)
+                    : null;
+        }
+    }
+
+    /**
+     * @deprecated
+     */
+    @Deprecated
+    public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) {
+        // reader
+        synchronized (mPackages) {
+            final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProvidersByAuthority
+                    .entrySet().iterator();
+            final int userId = UserHandle.getCallingUserId();
+            while (i.hasNext()) {
+                Map.Entry<String, PackageParser.Provider> entry = i.next();
+                PackageParser.Provider p = entry.getValue();
+                PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
+
+                if (ps != null && p.syncable
+                        && (!mSafeMode || (p.info.applicationInfo.flags
+                                &ApplicationInfo.FLAG_SYSTEM) != 0)) {
+                    ProviderInfo info = PackageParser.generateProviderInfo(p, 0,
+                            ps.readUserState(userId), userId);
+                    if (info != null) {
+                        outNames.add(entry.getKey());
+                        outInfo.add(info);
+                    }
+                }
+            }
+        }
+    }
+
+    public List<ProviderInfo> queryContentProviders(String processName,
+            int uid, int flags) {
+        ArrayList<ProviderInfo> finalList = null;
+        // reader
+        synchronized (mPackages) {
+            final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator();
+            final int userId = processName != null ?
+                    UserHandle.getUserId(uid) : UserHandle.getCallingUserId();
+            while (i.hasNext()) {
+                final PackageParser.Provider p = i.next();
+                PackageSetting ps = mSettings.mPackages.get(p.owner.packageName);
+                if (ps != null && p.info.authority != null
+                        && (processName == null
+                                || (p.info.processName.equals(processName)
+                                        && UserHandle.isSameApp(p.info.applicationInfo.uid, uid)))
+                        && mSettings.isEnabledLPr(p.info, flags, userId)
+                        && (!mSafeMode
+                                || (p.info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)) {
+                    if (finalList == null) {
+                        finalList = new ArrayList<ProviderInfo>(3);
+                    }
+                    ProviderInfo info = PackageParser.generateProviderInfo(p, flags,
+                            ps.readUserState(userId), userId);
+                    if (info != null) {
+                        finalList.add(info);
+                    }
+                }
+            }
+        }
+
+        if (finalList != null) {
+            Collections.sort(finalList, mProviderInitOrderSorter);
+        }
+
+        return finalList;
+    }
+
+    public InstrumentationInfo getInstrumentationInfo(ComponentName name,
+            int flags) {
+        // reader
+        synchronized (mPackages) {
+            final PackageParser.Instrumentation i = mInstrumentation.get(name);
+            return PackageParser.generateInstrumentationInfo(i, flags);
+        }
+    }
+
+    public List<InstrumentationInfo> queryInstrumentation(String targetPackage,
+            int flags) {
+        ArrayList<InstrumentationInfo> finalList =
+            new ArrayList<InstrumentationInfo>();
+
+        // reader
+        synchronized (mPackages) {
+            final Iterator<PackageParser.Instrumentation> i = mInstrumentation.values().iterator();
+            while (i.hasNext()) {
+                final PackageParser.Instrumentation p = i.next();
+                if (targetPackage == null
+                        || targetPackage.equals(p.info.targetPackage)) {
+                    InstrumentationInfo ii = PackageParser.generateInstrumentationInfo(p,
+                            flags);
+                    if (ii != null) {
+                        finalList.add(ii);
+                    }
+                }
+            }
+        }
+
+        return finalList;
+    }
+
+    private void scanDirLI(File dir, int flags, int scanMode, long currentTime) {
+        String[] files = dir.list();
+        if (files == null) {
+            Log.d(TAG, "No files in app dir " + dir);
+            return;
+        }
+
+        if (DEBUG_PACKAGE_SCANNING) {
+            Log.d(TAG, "Scanning app dir " + dir + " scanMode=" + scanMode
+                    + " flags=0x" + Integer.toHexString(flags));
+        }
+
+        int i;
+        for (i=0; i<files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (!isPackageFilename(files[i])) {
+                // Ignore entries which are not apk's
+                continue;
+            }
+            PackageParser.Package pkg = scanPackageLI(file,
+                    flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime, null);
+            // Don't mess around with apps in system partition.
+            if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
+                    mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
+                // Delete the apk
+                Slog.w(TAG, "Cleaning up failed install of " + file);
+                file.delete();
+            }
+        }
+    }
+
+    private static File getSettingsProblemFile() {
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File fname = new File(systemDir, "uiderrors.txt");
+        return fname;
+    }
+    
+    static void reportSettingsProblem(int priority, String msg) {
+        try {
+            File fname = getSettingsProblemFile();
+            FileOutputStream out = new FileOutputStream(fname, true);
+            PrintWriter pw = new FastPrintWriter(out);
+            SimpleDateFormat formatter = new SimpleDateFormat();
+            String dateString = formatter.format(new Date(System.currentTimeMillis()));
+            pw.println(dateString + ": " + msg);
+            pw.close();
+            FileUtils.setPermissions(
+                    fname.toString(),
+                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IROTH,
+                    -1, -1);
+        } catch (java.io.IOException e) {
+        }
+        Slog.println(priority, TAG, msg);
+    }
+
+    private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps,
+            PackageParser.Package pkg, File srcFile, int parseFlags) {
+        if (GET_CERTIFICATES) {
+            if (ps != null
+                    && ps.codePath.equals(srcFile)
+                    && ps.timeStamp == srcFile.lastModified()) {
+                if (ps.signatures.mSignatures != null
+                        && ps.signatures.mSignatures.length != 0) {
+                    // Optimization: reuse the existing cached certificates
+                    // if the package appears to be unchanged.
+                    pkg.mSignatures = ps.signatures.mSignatures;
+                    return true;
+                }
+                
+                Slog.w(TAG, "PackageSetting for " + ps.name + " is missing signatures.  Collecting certs again to recover them.");
+            } else {
+                Log.i(TAG, srcFile.toString() + " changed; collecting certs");
+            }
+            
+            if (!pp.collectCertificates(pkg, parseFlags)) {
+                mLastScanError = pp.getParseError();
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     *  Scan a package and return the newly parsed package.
+     *  Returns null in case of errors and the error code is stored in mLastScanError
+     */
+    private PackageParser.Package scanPackageLI(File scanFile,
+            int parseFlags, int scanMode, long currentTime, UserHandle user) {
+        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+        String scanPath = scanFile.getPath();
+        if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath);
+        parseFlags |= mDefParseFlags;
+        PackageParser pp = new PackageParser(scanPath);
+        pp.setSeparateProcesses(mSeparateProcesses);
+        pp.setOnlyCoreApps(mOnlyCore);
+        final PackageParser.Package pkg = pp.parsePackage(scanFile,
+                scanPath, mMetrics, parseFlags);
+
+        if (pkg == null) {
+            mLastScanError = pp.getParseError();
+            return null;
+        }
+
+        PackageSetting ps = null;
+        PackageSetting updatedPkg;
+        // reader
+        synchronized (mPackages) {
+            // Look to see if we already know about this package.
+            String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
+            if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
+                // This package has been renamed to its original name.  Let's
+                // use that.
+                ps = mSettings.peekPackageLPr(oldName);
+            }
+            // If there was no original package, see one for the real package name.
+            if (ps == null) {
+                ps = mSettings.peekPackageLPr(pkg.packageName);
+            }
+            // Check to see if this package could be hiding/updating a system
+            // package.  Must look for it either under the original or real
+            // package name depending on our state.
+            updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
+        }
+        // First check if this is a system package that may involve an update
+        if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+            if (ps != null && !ps.codePath.equals(scanFile)) {
+                // The path has changed from what was last scanned...  check the
+                // version of the new path against what we have stored to determine
+                // what to do.
+                if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath);
+                if (pkg.mVersionCode < ps.versionCode) {
+                    // The system package has been updated and the code path does not match
+                    // Ignore entry. Skip it.
+                    Log.i(TAG, "Package " + ps.name + " at " + scanFile
+                            + " ignored: updated version " + ps.versionCode
+                            + " better than this " + pkg.mVersionCode);
+                    if (!updatedPkg.codePath.equals(scanFile)) {
+                        Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg : "
+                                + ps.name + " changing from " + updatedPkg.codePathString
+                                + " to " + scanFile);
+                        updatedPkg.codePath = scanFile;
+                        updatedPkg.codePathString = scanFile.toString();
+                        // This is the point at which we know that the system-disk APK
+                        // for this package has moved during a reboot (e.g. due to an OTA),
+                        // so we need to reevaluate it for privilege policy.
+                        if (locationIsPrivileged(scanFile)) {
+                            updatedPkg.pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
+                        }
+                    }
+                    updatedPkg.pkg = pkg;
+                    mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
+                    return null;
+                } else {
+                    // The current app on the system partion is better than
+                    // what we have updated to on the data partition; switch
+                    // back to the system partition version.
+                    // At this point, its safely assumed that package installation for
+                    // apps in system partition will go through. If not there won't be a working
+                    // version of the app
+                    // writer
+                    synchronized (mPackages) {
+                        // Just remove the loaded entries from package lists.
+                        mPackages.remove(ps.name);
+                    }
+                    Slog.w(TAG, "Package " + ps.name + " at " + scanFile
+                            + "reverting from " + ps.codePathString
+                            + ": new version " + pkg.mVersionCode
+                            + " better than installed " + ps.versionCode);
+
+                    InstallArgs args = createInstallArgs(packageFlagsToInstallFlags(ps),
+                            ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString);
+                    synchronized (mInstallLock) {
+                        args.cleanUpResourcesLI();
+                    }
+                    synchronized (mPackages) {
+                        mSettings.enableSystemPackageLPw(ps.name);
+                    }
+                }
+            }
+        }
+
+        if (updatedPkg != null) {
+            // An updated system app will not have the PARSE_IS_SYSTEM flag set
+            // initially
+            parseFlags |= PackageParser.PARSE_IS_SYSTEM;
+        }
+        // Verify certificates against what was last scanned
+        if (!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)) {
+            Slog.w(TAG, "Failed verifying certificates for package:" + pkg.packageName);
+            return null;
+        }
+
+        /*
+         * A new system app appeared, but we already had a non-system one of the
+         * same name installed earlier.
+         */
+        boolean shouldHideSystemApp = false;
+        if (updatedPkg == null && ps != null
+                && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
+            /*
+             * Check to make sure the signatures match first. If they don't,
+             * wipe the installed application and its data.
+             */
+            if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)
+                    != PackageManager.SIGNATURE_MATCH) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!");
+                deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
+                ps = null;
+            } else {
+                /*
+                 * If the newly-added system app is an older version than the
+                 * already installed version, hide it. It will be scanned later
+                 * and re-added like an update.
+                 */
+                if (pkg.mVersionCode < ps.versionCode) {
+                    shouldHideSystemApp = true;
+                } else {
+                    /*
+                     * The newly found system app is a newer version that the
+                     * one previously installed. Simply remove the
+                     * already-installed application and replace it with our own
+                     * while keeping the application data.
+                     */
+                    Slog.w(TAG, "Package " + ps.name + " at " + scanFile + "reverting from "
+                            + ps.codePathString + ": new version " + pkg.mVersionCode
+                            + " better than installed " + ps.versionCode);
+                    InstallArgs args = createInstallArgs(packageFlagsToInstallFlags(ps),
+                            ps.codePathString, ps.resourcePathString, ps.nativeLibraryPathString);
+                    synchronized (mInstallLock) {
+                        args.cleanUpResourcesLI();
+                    }
+                }
+            }
+        }
+
+        // The apk is forward locked (not public) if its code and resources
+        // are kept in different files. (except for app in either system or
+        // vendor path).
+        // TODO grab this value from PackageSettings
+        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+            if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
+                parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
+            }
+        }
+
+        String codePath = null;
+        String resPath = null;
+        if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
+            if (ps != null && ps.resourcePathString != null) {
+                resPath = ps.resourcePathString;
+            } else {
+                // Should not happen at all. Just log an error.
+                Slog.e(TAG, "Resource path not set for pkg : " + pkg.packageName);
+            }
+        } else {
+            resPath = pkg.mScanPath;
+        }
+
+        codePath = pkg.mScanPath;
+        // Set application objects path explicitly.
+        setApplicationInfoPaths(pkg, codePath, resPath);
+        // Note that we invoke the following method only if we are about to unpack an application
+        PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanMode
+                | SCAN_UPDATE_SIGNATURE, currentTime, user);
+
+        /*
+         * If the system app should be overridden by a previously installed
+         * data, hide the system app now and let the /data/app scan pick it up
+         * again.
+         */
+        if (shouldHideSystemApp) {
+            synchronized (mPackages) {
+                /*
+                 * We have to grant systems permissions before we hide, because
+                 * grantPermissions will assume the package update is trying to
+                 * expand its permissions.
+                 */
+                grantPermissionsLPw(pkg, true);
+                mSettings.disableSystemPackageLPw(pkg.packageName);
+            }
+        }
+
+        return scannedPkg;
+    }
+
+    private static void setApplicationInfoPaths(PackageParser.Package pkg, String destCodePath,
+            String destResPath) {
+        pkg.mPath = pkg.mScanPath = destCodePath;
+        pkg.applicationInfo.sourceDir = destCodePath;
+        pkg.applicationInfo.publicSourceDir = destResPath;
+    }
+
+    private static String fixProcessName(String defProcessName,
+            String processName, int uid) {
+        if (processName == null) {
+            return defProcessName;
+        }
+        return processName;
+    }
+
+    private boolean verifySignaturesLP(PackageSetting pkgSetting,
+            PackageParser.Package pkg) {
+        if (pkgSetting.signatures.mSignatures != null) {
+            // Already existing package. Make sure signatures match
+            if (compareSignatures(pkgSetting.signatures.mSignatures, pkg.mSignatures) !=
+                PackageManager.SIGNATURE_MATCH) {
+                    Slog.e(TAG, "Package " + pkg.packageName
+                            + " signatures do not match the previously installed version; ignoring!");
+                    mLastScanError = PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
+                    return false;
+                }
+        }
+        // Check for shared user signatures
+        if (pkgSetting.sharedUser != null && pkgSetting.sharedUser.signatures.mSignatures != null) {
+            if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+                    pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+                Slog.e(TAG, "Package " + pkg.packageName
+                        + " has no signatures that match those in shared user "
+                        + pkgSetting.sharedUser.name + "; ignoring!");
+                mLastScanError = PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Enforces that only the system UID or root's UID can call a method exposed
+     * via Binder.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system or root
+     */
+    private static final void enforceSystemOrRoot(String message) {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID && uid != 0) {
+            throw new SecurityException(message);
+        }
+    }
+
+    public void performBootDexOpt() {
+        HashSet<PackageParser.Package> pkgs = null;
+        synchronized (mPackages) {
+            pkgs = mDeferredDexOpt;
+            mDeferredDexOpt = null;
+        }
+        if (pkgs != null) {
+            int i = 0;
+            for (PackageParser.Package pkg : pkgs) {
+                if (!isFirstBoot()) {
+                    i++;
+                    try {
+                        ActivityManagerNative.getDefault().showBootMessage(
+                                mContext.getResources().getString(
+                                        com.android.internal.R.string.android_upgrading_apk,
+                                        i, pkgs.size()), true);
+                    } catch (RemoteException e) {
+                    }
+                }
+                PackageParser.Package p = pkg;
+                synchronized (mInstallLock) {
+                    if (!p.mDidDexOpt) {
+                        performDexOptLI(p, false, false, true);
+                    }
+                }
+            }
+        }
+    }
+
+    public boolean performDexOpt(String packageName) {
+        enforceSystemOrRoot("Only the system can request dexopt be performed");
+
+        if (!mNoDexOpt) {
+            return false;
+        }
+
+        PackageParser.Package p;
+        synchronized (mPackages) {
+            p = mPackages.get(packageName);
+            if (p == null || p.mDidDexOpt) {
+                return false;
+            }
+        }
+        synchronized (mInstallLock) {
+            return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED;
+        }
+    }
+
+    private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer,
+            HashSet<String> done) {
+        for (int i=0; i<libs.size(); i++) {
+            PackageParser.Package libPkg;
+            String libName;
+            synchronized (mPackages) {
+                libName = libs.get(i);
+                SharedLibraryEntry lib = mSharedLibraries.get(libName);
+                if (lib != null && lib.apk != null) {
+                    libPkg = mPackages.get(lib.apk);
+                } else {
+                    libPkg = null;
+                }
+            }
+            if (libPkg != null && !done.contains(libName)) {
+                performDexOptLI(libPkg, forceDex, defer, done);
+            }
+        }
+    }
+
+    static final int DEX_OPT_SKIPPED = 0;
+    static final int DEX_OPT_PERFORMED = 1;
+    static final int DEX_OPT_DEFERRED = 2;
+    static final int DEX_OPT_FAILED = -1;
+
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            HashSet<String> done) {
+        boolean performed = false;
+        if (done != null) {
+            done.add(pkg.packageName);
+            if (pkg.usesLibraries != null) {
+                performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done);
+            }
+            if (pkg.usesOptionalLibraries != null) {
+                performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done);
+            }
+        }
+        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            String path = pkg.mScanPath;
+            int ret = 0;
+            try {
+                if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
+                    if (!forceDex && defer) {
+                        if (mDeferredDexOpt == null) {
+                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
+                        }
+                        mDeferredDexOpt.add(pkg);
+                        return DEX_OPT_DEFERRED;
+                    } else {
+                        Log.i(TAG, "Running dexopt on: " + pkg.applicationInfo.packageName);
+                        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+                        ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));
+                        pkg.mDidDexOpt = true;
+                        performed = true;
+                    }
+                }
+            } catch (FileNotFoundException e) {
+                Slog.w(TAG, "Apk not found for dexopt: " + path);
+                ret = -1;
+            } catch (IOException e) {
+                Slog.w(TAG, "IOException reading apk: " + path, e);
+                ret = -1;
+            } catch (dalvik.system.StaleDexCacheError e) {
+                Slog.w(TAG, "StaleDexCacheError when reading apk: " + path, e);
+                ret = -1;
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception when doing dexopt : ", e);
+                ret = -1;
+            }
+            if (ret < 0) {
+                //error from installer
+                return DEX_OPT_FAILED;
+            }
+        }
+
+        return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
+    }
+
+    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
+            boolean inclDependencies) {
+        HashSet<String> done;
+        boolean performed = false;
+        if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
+            done = new HashSet<String>();
+            done.add(pkg.packageName);
+        } else {
+            done = null;
+        }
+        return performDexOptLI(pkg, forceDex, defer, done);
+    }
+
+    private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) {
+        if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.name
+                    + " to " + newPkg.packageName
+                    + ": old package not in system partition");
+            return false;
+        } else if (mPackages.get(oldPkg.name) != null) {
+            Slog.w(TAG, "Unable to update from " + oldPkg.name
+                    + " to " + newPkg.packageName
+                    + ": old package still exists");
+            return false;
+        }
+        return true;
+    }
+
+    File getDataPathForUser(int userId) {
+        return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId);
+    }
+
+    private File getDataPathForPackage(String packageName, int userId) {
+        /*
+         * Until we fully support multiple users, return the directory we
+         * previously would have. The PackageManagerTests will need to be
+         * revised when this is changed back..
+         */
+        if (userId == 0) {
+            return new File(mAppDataDir, packageName);
+        } else {
+            return new File(mUserAppDataDir.getAbsolutePath() + File.separator + userId
+                + File.separator + packageName);
+        }
+    }
+
+    private int createDataDirsLI(String packageName, int uid, String seinfo) {
+        int[] users = sUserManager.getUserIds();
+        int res = mInstaller.install(packageName, uid, uid, seinfo);
+        if (res < 0) {
+            return res;
+        }
+        for (int user : users) {
+            if (user != 0) {
+                res = mInstaller.createUserData(packageName,
+                        UserHandle.getUid(user, uid), user, seinfo);
+                if (res < 0) {
+                    return res;
+                }
+            }
+        }
+        return res;
+    }
+
+    private int removeDataDirsLI(String packageName) {
+        int[] users = sUserManager.getUserIds();
+        int res = 0;
+        for (int user : users) {
+            int resInner = mInstaller.remove(packageName, user);
+            if (resInner < 0) {
+                res = resInner;
+            }
+        }
+
+        final File nativeLibraryFile = new File(mAppLibInstallDir, packageName);
+        NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
+        if (!nativeLibraryFile.delete()) {
+            Slog.w(TAG, "Couldn't delete native library directory " + nativeLibraryFile.getPath());
+        }
+
+        return res;
+    }
+
+    private int addSharedLibraryLPw(final SharedLibraryEntry file, int num,
+            PackageParser.Package changingLib) {
+        if (file.path != null) {
+            mTmpSharedLibraries[num] = file.path;
+            return num+1;
+        }
+        PackageParser.Package p = mPackages.get(file.apk);
+        if (changingLib != null && changingLib.packageName.equals(file.apk)) {
+            // If we are doing this while in the middle of updating a library apk,
+            // then we need to make sure to use that new apk for determining the
+            // dependencies here.  (We haven't yet finished committing the new apk
+            // to the package manager state.)
+            if (p == null || p.packageName.equals(changingLib.packageName)) {
+                p = changingLib;
+            }
+        }
+        if (p != null) {
+            String path = p.mPath;
+            for (int i=0; i<num; i++) {
+                if (mTmpSharedLibraries[i].equals(path)) {
+                    return num;
+                }
+            }
+            mTmpSharedLibraries[num] = p.mPath;
+            return num+1;
+        }
+        return num;
+    }
+
+    private boolean updateSharedLibrariesLPw(PackageParser.Package pkg,
+            PackageParser.Package changingLib) {
+        if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) {
+            if (mTmpSharedLibraries == null ||
+                    mTmpSharedLibraries.length < mSharedLibraries.size()) {
+                mTmpSharedLibraries = new String[mSharedLibraries.size()];
+            }
+            int num = 0;
+            int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i));
+                if (file == null) {
+                    Slog.e(TAG, "Package " + pkg.packageName
+                            + " requires unavailable shared library "
+                            + pkg.usesLibraries.get(i) + "; failing!");
+                    mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
+                    return false;
+                }
+                num = addSharedLibraryLPw(file, num, changingLib);
+            }
+            N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0;
+            for (int i=0; i<N; i++) {
+                final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i));
+                if (file == null) {
+                    Slog.w(TAG, "Package " + pkg.packageName
+                            + " desires unavailable shared library "
+                            + pkg.usesOptionalLibraries.get(i) + "; ignoring!");
+                } else {
+                    num = addSharedLibraryLPw(file, num, changingLib);
+                }
+            }
+            if (num > 0) {
+                pkg.usesLibraryFiles = new String[num];
+                System.arraycopy(mTmpSharedLibraries, 0,
+                        pkg.usesLibraryFiles, 0, num);
+            } else {
+                pkg.usesLibraryFiles = null;
+            }
+        }
+        return true;
+    }
+
+    private static boolean hasString(List<String> list, List<String> which) {
+        if (list == null) {
+            return false;
+        }
+        for (int i=list.size()-1; i>=0; i--) {
+            for (int j=which.size()-1; j>=0; j--) {
+                if (which.get(j).equals(list.get(i))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void updateAllSharedLibrariesLPw() {
+        for (PackageParser.Package pkg : mPackages.values()) {
+            updateSharedLibrariesLPw(pkg, null);
+        }
+    }
+
+    private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw(
+            PackageParser.Package changingPkg) {
+        ArrayList<PackageParser.Package> res = null;
+        for (PackageParser.Package pkg : mPackages.values()) {
+            if (hasString(pkg.usesLibraries, changingPkg.libraryNames)
+                    || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) {
+                if (res == null) {
+                    res = new ArrayList<PackageParser.Package>();
+                }
+                res.add(pkg);
+                updateSharedLibrariesLPw(pkg, changingPkg);
+            }
+        }
+        return res;
+    }
+
+    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
+            int parseFlags, int scanMode, long currentTime, UserHandle user) {
+        File scanFile = new File(pkg.mScanPath);
+        if (scanFile == null || pkg.applicationInfo.sourceDir == null ||
+                pkg.applicationInfo.publicSourceDir == null) {
+            // Bail out. The resource and code paths haven't been set.
+            Slog.w(TAG, " Code and resource paths haven't been set correctly");
+            mLastScanError = PackageManager.INSTALL_FAILED_INVALID_APK;
+            return null;
+        }
+        mScanningPath = scanFile;
+
+        if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
+
+        if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
+            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_PRIVILEGED;
+        }
+
+        if (mCustomResolverComponentName != null &&
+                mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
+            setUpCustomResolverActivity(pkg);
+        }
+
+        if (pkg.packageName.equals("android")) {
+            synchronized (mPackages) {
+                if (mAndroidApplication != null) {
+                    Slog.w(TAG, "*************************************************");
+                    Slog.w(TAG, "Core android package being redefined.  Skipping.");
+                    Slog.w(TAG, " file=" + mScanningPath);
+                    Slog.w(TAG, "*************************************************");
+                    mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
+                    return null;
+                }
+
+                // Set up information for our fall-back user intent resolution activity.
+                mPlatformPackage = pkg;
+                pkg.mVersionCode = mSdkVersion;
+                mAndroidApplication = pkg.applicationInfo;
+
+                if (!mResolverReplaced) {
+                    mResolveActivity.applicationInfo = mAndroidApplication;
+                    mResolveActivity.name = ResolverActivity.class.getName();
+                    mResolveActivity.packageName = mAndroidApplication.packageName;
+                    mResolveActivity.processName = "system:ui";
+                    mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+                    mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
+                    mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert;
+                    mResolveActivity.exported = true;
+                    mResolveActivity.enabled = true;
+                    mResolveInfo.activityInfo = mResolveActivity;
+                    mResolveInfo.priority = 0;
+                    mResolveInfo.preferredOrder = 0;
+                    mResolveInfo.match = 0;
+                    mResolveComponentName = new ComponentName(
+                            mAndroidApplication.packageName, mResolveActivity.name);
+                }
+            }
+        }
+
+        if (DEBUG_PACKAGE_SCANNING) {
+            if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                Log.d(TAG, "Scanning package " + pkg.packageName);
+        }
+
+        if (mPackages.containsKey(pkg.packageName)
+                || mSharedLibraries.containsKey(pkg.packageName)) {
+            Slog.w(TAG, "Application package " + pkg.packageName
+                    + " already installed.  Skipping duplicate.");
+            mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE;
+            return null;
+        }
+
+        // Initialize package source and resource directories
+        File destCodeFile = new File(pkg.applicationInfo.sourceDir);
+        File destResourceFile = new File(pkg.applicationInfo.publicSourceDir);
+
+        SharedUserSetting suid = null;
+        PackageSetting pkgSetting = null;
+
+        if (!isSystemApp(pkg)) {
+            // Only system apps can use these features.
+            pkg.mOriginalPackages = null;
+            pkg.mRealPackage = null;
+            pkg.mAdoptPermissions = null;
+        }
+
+        // writer
+        synchronized (mPackages) {
+            if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                // Check all shared libraries and map to their actual file path.
+                // We only do this here for apps not on a system dir, because those
+                // are the only ones that can fail an install due to this.  We
+                // will take care of the system apps by updating all of their
+                // library paths after the scan is done.
+                if (!updateSharedLibrariesLPw(pkg, null)) {
+                    return null;
+                }
+            }
+
+            if (pkg.mSharedUserId != null) {
+                suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true);
+                if (suid == null) {
+                    Slog.w(TAG, "Creating application package " + pkg.packageName
+                            + " for shared user failed");
+                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                    return null;
+                }
+                if (DEBUG_PACKAGE_SCANNING) {
+                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                        Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId
+                                + "): packages=" + suid.packages);
+                }
+            }
+            
+            // Check if we are renaming from an original package name.
+            PackageSetting origPackage = null;
+            String realName = null;
+            if (pkg.mOriginalPackages != null) {
+                // This package may need to be renamed to a previously
+                // installed name.  Let's check on that...
+                final String renamed = mSettings.mRenamedPackages.get(pkg.mRealPackage);
+                if (pkg.mOriginalPackages.contains(renamed)) {
+                    // This package had originally been installed as the
+                    // original name, and we have already taken care of
+                    // transitioning to the new one.  Just update the new
+                    // one to continue using the old name.
+                    realName = pkg.mRealPackage;
+                    if (!pkg.packageName.equals(renamed)) {
+                        // Callers into this function may have already taken
+                        // care of renaming the package; only do it here if
+                        // it is not already done.
+                        pkg.setPackageName(renamed);
+                    }
+                    
+                } else {
+                    for (int i=pkg.mOriginalPackages.size()-1; i>=0; i--) {
+                        if ((origPackage = mSettings.peekPackageLPr(
+                                pkg.mOriginalPackages.get(i))) != null) {
+                            // We do have the package already installed under its
+                            // original name...  should we use it?
+                            if (!verifyPackageUpdateLPr(origPackage, pkg)) {
+                                // New package is not compatible with original.
+                                origPackage = null;
+                                continue;
+                            } else if (origPackage.sharedUser != null) {
+                                // Make sure uid is compatible between packages.
+                                if (!origPackage.sharedUser.name.equals(pkg.mSharedUserId)) {
+                                    Slog.w(TAG, "Unable to migrate data from " + origPackage.name
+                                            + " to " + pkg.packageName + ": old uid "
+                                            + origPackage.sharedUser.name
+                                            + " differs from " + pkg.mSharedUserId);
+                                    origPackage = null;
+                                    continue;
+                                }
+                            } else {
+                                if (DEBUG_UPGRADE) Log.v(TAG, "Renaming new package "
+                                        + pkg.packageName + " to old name " + origPackage.name);
+                            }
+                            break;
+                        }
+                    }
+                }
+            }
+            
+            if (mTransferedPackages.contains(pkg.packageName)) {
+                Slog.w(TAG, "Package " + pkg.packageName
+                        + " was transferred to another, but its .apk remains");
+            }
+            
+            // Just create the setting, don't add it yet. For already existing packages
+            // the PkgSetting exists already and doesn't have to be created.
+            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
+                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
+                    pkg.applicationInfo.flags, user, false);
+            if (pkgSetting == null) {
+                Slog.w(TAG, "Creating application package " + pkg.packageName + " failed");
+                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                return null;
+            }
+            
+            if (pkgSetting.origPackage != null) {
+                // If we are first transitioning from an original package,
+                // fix up the new package's name now.  We need to do this after
+                // looking up the package under its new name, so getPackageLP
+                // can take care of fiddling things correctly.
+                pkg.setPackageName(origPackage.name);
+                
+                // File a report about this.
+                String msg = "New package " + pkgSetting.realName
+                        + " renamed to replace old package " + pkgSetting.name;
+                reportSettingsProblem(Log.WARN, msg);
+                
+                // Make a note of it.
+                mTransferedPackages.add(origPackage.name);
+                
+                // No longer need to retain this.
+                pkgSetting.origPackage = null;
+            }
+            
+            if (realName != null) {
+                // Make a note of it.
+                mTransferedPackages.add(pkg.packageName);
+            }
+            
+            if (mSettings.isDisabledSystemPackageLPr(pkg.packageName)) {
+                pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+            }
+
+            if (mFoundPolicyFile) {
+                SELinuxMMAC.assignSeinfoValue(pkg);
+            }
+
+            pkg.applicationInfo.uid = pkgSetting.appId;
+            pkg.mExtras = pkgSetting;
+
+            if (!verifySignaturesLP(pkgSetting, pkg)) {
+                if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    return null;
+                }
+                // The signature has changed, but this package is in the system
+                // image...  let's recover!
+                pkgSetting.signatures.mSignatures = pkg.mSignatures;
+                // However...  if this package is part of a shared user, but it
+                // doesn't match the signature of the shared user, let's fail.
+                // What this means is that you can't change the signatures
+                // associated with an overall shared user, which doesn't seem all
+                // that unreasonable.
+                if (pkgSetting.sharedUser != null) {
+                    if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
+                            pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
+                        Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser);
+                        mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+                        return null;
+                    }
+                }
+                // File a report about this.
+                String msg = "System package " + pkg.packageName
+                        + " signature changed; retaining data.";
+                reportSettingsProblem(Log.WARN, msg);
+            }
+
+            // Verify that this new package doesn't have any content providers
+            // that conflict with existing packages.  Only do this if the
+            // package isn't already installed, since we don't want to break
+            // things that are installed.
+            if ((scanMode&SCAN_NEW_INSTALL) != 0) {
+                final int N = pkg.providers.size();
+                int i;
+                for (i=0; i<N; i++) {
+                    PackageParser.Provider p = pkg.providers.get(i);
+                    if (p.info.authority != null) {
+                        String names[] = p.info.authority.split(";");
+                        for (int j = 0; j < names.length; j++) {
+                            if (mProvidersByAuthority.containsKey(names[j])) {
+                                PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
+                                Slog.w(TAG, "Can't install because provider name " + names[j] +
+                                        " (in package " + pkg.applicationInfo.packageName +
+                                        ") is already used by "
+                                        + ((other != null && other.getComponentName() != null)
+                                                ? other.getComponentName().getPackageName() : "?"));
+                                mLastScanError = PackageManager.INSTALL_FAILED_CONFLICTING_PROVIDER;
+                                return null;
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (pkg.mAdoptPermissions != null) {
+                // This package wants to adopt ownership of permissions from
+                // another package.
+                for (int i = pkg.mAdoptPermissions.size() - 1; i >= 0; i--) {
+                    final String origName = pkg.mAdoptPermissions.get(i);
+                    final PackageSetting orig = mSettings.peekPackageLPr(origName);
+                    if (orig != null) {
+                        if (verifyPackageUpdateLPr(orig, pkg)) {
+                            Slog.i(TAG, "Adopting permissions from " + origName + " to "
+                                    + pkg.packageName);
+                            mSettings.transferPermissionsLPw(origName, pkg.packageName);
+                        }
+                    }
+                }
+            }
+        }
+
+        final String pkgName = pkg.packageName;
+        
+        final long scanFileTime = scanFile.lastModified();
+        final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
+        pkg.applicationInfo.processName = fixProcessName(
+                pkg.applicationInfo.packageName,
+                pkg.applicationInfo.processName,
+                pkg.applicationInfo.uid);
+
+        File dataPath;
+        if (mPlatformPackage == pkg) {
+            // The system package is special.
+            dataPath = new File (Environment.getDataDirectory(), "system");
+            pkg.applicationInfo.dataDir = dataPath.getPath();
+        } else {
+            // This is a normal package, need to make its data directory.
+            dataPath = getDataPathForPackage(pkg.packageName, 0);
+
+            boolean uidError = false;
+
+            if (dataPath.exists()) {
+                int currentUid = 0;
+                try {
+                    StructStat stat = Libcore.os.stat(dataPath.getPath());
+                    currentUid = stat.st_uid;
+                } catch (ErrnoException e) {
+                    Slog.e(TAG, "Couldn't stat path " + dataPath.getPath(), e);
+                }
+
+                // If we have mismatched owners for the data path, we have a problem.
+                if (currentUid != pkg.applicationInfo.uid) {
+                    boolean recovered = false;
+                    if (currentUid == 0) {
+                        // The directory somehow became owned by root.  Wow.
+                        // This is probably because the system was stopped while
+                        // installd was in the middle of messing with its libs
+                        // directory.  Ask installd to fix that.
+                        int ret = mInstaller.fixUid(pkgName, pkg.applicationInfo.uid,
+                                pkg.applicationInfo.uid);
+                        if (ret >= 0) {
+                            recovered = true;
+                            String msg = "Package " + pkg.packageName
+                                    + " unexpectedly changed to uid 0; recovered to " +
+                                    + pkg.applicationInfo.uid;
+                            reportSettingsProblem(Log.WARN, msg);
+                        }
+                    }
+                    if (!recovered && ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0
+                            || (scanMode&SCAN_BOOTING) != 0)) {
+                        // If this is a system app, we can at least delete its
+                        // current data so the application will still work.
+                        int ret = removeDataDirsLI(pkgName);
+                        if (ret >= 0) {
+                            // TODO: Kill the processes first
+                            // Old data gone!
+                            String prefix = (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0
+                                    ? "System package " : "Third party package ";
+                            String msg = prefix + pkg.packageName
+                                    + " has changed from uid: "
+                                    + currentUid + " to "
+                                    + pkg.applicationInfo.uid + "; old data erased";
+                            reportSettingsProblem(Log.WARN, msg);
+                            recovered = true;
+
+                            // And now re-install the app.
+                            ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
+                                                   pkg.applicationInfo.seinfo);
+                            if (ret == -1) {
+                                // Ack should not happen!
+                                msg = prefix + pkg.packageName
+                                        + " could not have data directory re-created after delete.";
+                                reportSettingsProblem(Log.WARN, msg);
+                                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                                return null;
+                            }
+                        }
+                        if (!recovered) {
+                            mHasSystemUidErrors = true;
+                        }
+                    } else if (!recovered) {
+                        // If we allow this install to proceed, we will be broken.
+                        // Abort, abort!
+                        mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED;
+                        return null;
+                    }
+                    if (!recovered) {
+                        pkg.applicationInfo.dataDir = "/mismatched_uid/settings_"
+                            + pkg.applicationInfo.uid + "/fs_"
+                            + currentUid;
+                        pkg.applicationInfo.nativeLibraryDir = pkg.applicationInfo.dataDir;
+                        String msg = "Package " + pkg.packageName
+                                + " has mismatched uid: "
+                                + currentUid + " on disk, "
+                                + pkg.applicationInfo.uid + " in settings";
+                        // writer
+                        synchronized (mPackages) {
+                            mSettings.mReadMessages.append(msg);
+                            mSettings.mReadMessages.append('\n');
+                            uidError = true;
+                            if (!pkgSetting.uidError) {
+                                reportSettingsProblem(Log.ERROR, msg);
+                            }
+                        }
+                    }
+                }
+                pkg.applicationInfo.dataDir = dataPath.getPath();
+            } else {
+                if (DEBUG_PACKAGE_SCANNING) {
+                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                        Log.v(TAG, "Want this data dir: " + dataPath);
+                }
+                //invoke installer to do the actual installation
+                int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
+                                           pkg.applicationInfo.seinfo);
+                if (ret < 0) {
+                    // Error from installer
+                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                    return null;
+                }
+
+                if (dataPath.exists()) {
+                    pkg.applicationInfo.dataDir = dataPath.getPath();
+                } else {
+                    Slog.w(TAG, "Unable to create data directory: " + dataPath);
+                    pkg.applicationInfo.dataDir = null;
+                }
+            }
+
+            /*
+             * Set the data dir to the default "/data/data/<package name>/lib"
+             * if we got here without anyone telling us different (e.g., apps
+             * stored on SD card have their native libraries stored in the ASEC
+             * container with the APK).
+             *
+             * This happens during an upgrade from a package settings file that
+             * doesn't have a native library path attribute at all.
+             */
+            if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) {
+                if (pkgSetting.nativeLibraryPathString == null) {
+                    setInternalAppNativeLibraryPath(pkg, pkgSetting);
+                } else {
+                    pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
+                }
+            }
+
+            pkgSetting.uidError = uidError;
+        }
+
+        String path = scanFile.getPath();
+        /* Note: We don't want to unpack the native binaries for
+         *        system applications, unless they have been updated
+         *        (the binaries are already under /system/lib).
+         *        Also, don't unpack libs for apps on the external card
+         *        since they should have their libraries in the ASEC
+         *        container already.
+         *
+         *        In other words, we're going to unpack the binaries
+         *        only for non-system apps and system app upgrades.
+         */
+        if (pkg.applicationInfo.nativeLibraryDir != null) {
+            try {
+                File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
+                final String dataPathString = dataPath.getCanonicalPath();
+
+                if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
+                    /*
+                     * Upgrading from a previous version of the OS sometimes
+                     * leaves native libraries in the /data/data/<app>/lib
+                     * directory for system apps even when they shouldn't be.
+                     * Recent changes in the JNI library search path
+                     * necessitates we remove those to match previous behavior.
+                     */
+                    if (NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryDir)) {
+                        Log.i(TAG, "removed obsolete native libraries for system package "
+                                + path);
+                    }
+                } else {
+                    if (!isForwardLocked(pkg) && !isExternal(pkg)) {
+                        /*
+                         * Update native library dir if it starts with
+                         * /data/data
+                         */
+                        if (nativeLibraryDir.getPath().startsWith(dataPathString)) {
+                            setInternalAppNativeLibraryPath(pkg, pkgSetting);
+                            nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir);
+                        }
+
+                        try {
+                            if (copyNativeLibrariesForInternalApp(scanFile, nativeLibraryDir) != PackageManager.INSTALL_SUCCEEDED) {
+                                Slog.e(TAG, "Unable to copy native libraries");
+                                mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                                return null;
+                            }
+                        } catch (IOException e) {
+                            Slog.e(TAG, "Unable to copy native libraries", e);
+                            mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                            return null;
+                        }
+                    }
+
+                    if (DEBUG_INSTALL) Slog.i(TAG, "Linking native library dir for " + path);
+                    final int[] userIds = sUserManager.getUserIds();
+                    synchronized (mInstallLock) {
+                        for (int userId : userIds) {
+                            if (mInstaller.linkNativeLibraryDirectory(pkg.packageName,
+                                    pkg.applicationInfo.nativeLibraryDir, userId) < 0) {
+                                Slog.w(TAG, "Failed linking native library dir (user=" + userId
+                                        + ")");
+                                mLastScanError = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+                                return null;
+                            }
+                        }
+                    }
+                }
+            } catch (IOException ioe) {
+                Slog.e(TAG, "Unable to get canonical file " + ioe.toString());
+            }
+        }
+        pkg.mScanPath = path;
+
+        if ((scanMode&SCAN_NO_DEX) == 0) {
+            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                    == DEX_OPT_FAILED) {
+                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+                return null;
+            }
+        }
+
+        if (mFactoryTest && pkg.requestedPermissions.contains(
+                android.Manifest.permission.FACTORY_TEST)) {
+            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST;
+        }
+
+        ArrayList<PackageParser.Package> clientLibPkgs = null;
+
+        // writer
+        synchronized (mPackages) {
+            if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+                // Only system apps can add new shared libraries.
+                if (pkg.libraryNames != null) {
+                    for (int i=0; i<pkg.libraryNames.size(); i++) {
+                        String name = pkg.libraryNames.get(i);
+                        boolean allowed = false;
+                        if (isUpdatedSystemApp(pkg)) {
+                            // New library entries can only be added through the
+                            // system image.  This is important to get rid of a lot
+                            // of nasty edge cases: for example if we allowed a non-
+                            // system update of the app to add a library, then uninstalling
+                            // the update would make the library go away, and assumptions
+                            // we made such as through app install filtering would now
+                            // have allowed apps on the device which aren't compatible
+                            // with it.  Better to just have the restriction here, be
+                            // conservative, and create many fewer cases that can negatively
+                            // impact the user experience.
+                            final PackageSetting sysPs = mSettings
+                                    .getDisabledSystemPkgLPr(pkg.packageName);
+                            if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) {
+                                for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) {
+                                    if (name.equals(sysPs.pkg.libraryNames.get(j))) {
+                                        allowed = true;
+                                        allowed = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        } else {
+                            allowed = true;
+                        }
+                        if (allowed) {
+                            if (!mSharedLibraries.containsKey(name)) {
+                                mSharedLibraries.put(name, new SharedLibraryEntry(null,
+                                        pkg.packageName));
+                            } else if (!name.equals(pkg.packageName)) {
+                                Slog.w(TAG, "Package " + pkg.packageName + " library "
+                                        + name + " already exists; skipping");
+                            }
+                        } else {
+                            Slog.w(TAG, "Package " + pkg.packageName + " declares lib "
+                                    + name + " that is not declared on system image; skipping");
+                        }
+                    }
+                    if ((scanMode&SCAN_BOOTING) == 0) {
+                        // If we are not booting, we need to update any applications
+                        // that are clients of our shared library.  If we are booting,
+                        // this will all be done once the scan is complete.
+                        clientLibPkgs = updateAllSharedLibrariesLPw(pkg);
+                    }
+                }
+            }
+        }
+
+        // We also need to dexopt any apps that are dependent on this library.  Note that
+        // if these fail, we should abort the install since installing the library will
+        // result in some apps being broken.
+        if (clientLibPkgs != null) {
+            if ((scanMode&SCAN_NO_DEX) == 0) {
+                for (int i=0; i<clientLibPkgs.size(); i++) {
+                    PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
+                            == DEX_OPT_FAILED) {
+                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
+                        return null;
+                    }
+                }
+            }
+        }
+
+        // Request the ActivityManager to kill the process(only for existing packages)
+        // so that we do not end up in a confused state while the user is still using the older
+        // version of the application while the new one gets installed.
+        if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+            // If the package lives in an asec, tell everyone that the container is going
+            // away so they can clean up any references to its resources (which would prevent
+            // vold from being able to unmount the asec)
+            if (isForwardLocked(pkg) || isExternal(pkg)) {
+                if (DEBUG_INSTALL) {
+                    Slog.i(TAG, "upgrading pkg " + pkg + " is ASEC-hosted -> UNAVAILABLE");
+                }
+                final int[] uidArray = new int[] { pkg.applicationInfo.uid };
+                final ArrayList<String> pkgList = new ArrayList<String>(1);
+                pkgList.add(pkg.applicationInfo.packageName);
+                sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
+            }
+
+            // Post the request that it be killed now that the going-away broadcast is en route
+            killApplication(pkg.applicationInfo.packageName,
+                        pkg.applicationInfo.uid, "update pkg");
+        }
+
+        // Also need to kill any apps that are dependent on the library.
+        if (clientLibPkgs != null) {
+            for (int i=0; i<clientLibPkgs.size(); i++) {
+                PackageParser.Package clientPkg = clientLibPkgs.get(i);
+                killApplication(clientPkg.applicationInfo.packageName,
+                        clientPkg.applicationInfo.uid, "update lib");
+            }
+        }
+
+        // writer
+        synchronized (mPackages) {
+            // We don't expect installation to fail beyond this point,
+            if ((scanMode&SCAN_MONITOR) != 0) {
+                mAppDirs.put(pkg.mPath, pkg);
+            }
+            // Add the new setting to mSettings
+            mSettings.insertPackageSettingLPw(pkgSetting, pkg);
+            // Add the new setting to mPackages
+            mPackages.put(pkg.applicationInfo.packageName, pkg);
+            // Make sure we don't accidentally delete its data.
+            final Iterator<PackageCleanItem> iter = mSettings.mPackagesToBeCleaned.iterator();
+            while (iter.hasNext()) {
+                PackageCleanItem item = iter.next();
+                if (pkgName.equals(item.packageName)) {
+                    iter.remove();
+                }
+            }
+
+            // Take care of first install / last update times.
+            if (currentTime != 0) {
+                if (pkgSetting.firstInstallTime == 0) {
+                    pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
+                } else if ((scanMode&SCAN_UPDATE_TIME) != 0) {
+                    pkgSetting.lastUpdateTime = currentTime;
+                }
+            } else if (pkgSetting.firstInstallTime == 0) {
+                // We need *something*.  Take time time stamp of the file.
+                pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
+            } else if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+                if (scanFileTime != pkgSetting.timeStamp) {
+                    // A package on the system image has changed; consider this
+                    // to be an update.
+                    pkgSetting.lastUpdateTime = scanFileTime;
+                }
+            }
+
+            // Add the package's KeySets to the global KeySetManager
+            KeySetManager ksm = mSettings.mKeySetManager;
+            try {
+                ksm.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys);
+                if (pkg.mKeySetMapping != null) {
+                    for (Map.Entry<String, Set<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) {
+                        if (entry.getValue() != null) {
+                            ksm.addDefinedKeySetToPackage(pkg.packageName,
+                                entry.getValue(), entry.getKey());
+                        }
+                    }
+                }
+            } catch (NullPointerException e) {
+                Slog.e(TAG, "Could not add KeySet to " + pkg.packageName, e);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Could not add KeySet to malformed package" + pkg.packageName, e);
+            }
+
+            int N = pkg.providers.size();
+            StringBuilder r = null;
+            int i;
+            for (i=0; i<N; i++) {
+                PackageParser.Provider p = pkg.providers.get(i);
+                p.info.processName = fixProcessName(pkg.applicationInfo.processName,
+                        p.info.processName, pkg.applicationInfo.uid);
+                mProviders.addProvider(p);
+                p.syncable = p.info.isSyncable;
+                if (p.info.authority != null) {
+                    String names[] = p.info.authority.split(";");
+                    p.info.authority = null;
+                    for (int j = 0; j < names.length; j++) {
+                        if (j == 1 && p.syncable) {
+                            // We only want the first authority for a provider to possibly be
+                            // syncable, so if we already added this provider using a different
+                            // authority clear the syncable flag. We copy the provider before
+                            // changing it because the mProviders object contains a reference
+                            // to a provider that we don't want to change.
+                            // Only do this for the second authority since the resulting provider
+                            // object can be the same for all future authorities for this provider.
+                            p = new PackageParser.Provider(p);
+                            p.syncable = false;
+                        }
+                        if (!mProvidersByAuthority.containsKey(names[j])) {
+                            mProvidersByAuthority.put(names[j], p);
+                            if (p.info.authority == null) {
+                                p.info.authority = names[j];
+                            } else {
+                                p.info.authority = p.info.authority + ";" + names[j];
+                            }
+                            if (DEBUG_PACKAGE_SCANNING) {
+                                if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
+                                    Log.d(TAG, "Registered content provider: " + names[j]
+                                            + ", className = " + p.info.name + ", isSyncable = "
+                                            + p.info.isSyncable);
+                            }
+                        } else {
+                            PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
+                            Slog.w(TAG, "Skipping provider name " + names[j] +
+                                    " (in package " + pkg.applicationInfo.packageName +
+                                    "): name already used by "
+                                    + ((other != null && other.getComponentName() != null)
+                                            ? other.getComponentName().getPackageName() : "?"));
+                        }
+                    }
+                }
+                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(p.info.name);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Providers: " + r);
+            }
+
+            N = pkg.services.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.Service s = pkg.services.get(i);
+                s.info.processName = fixProcessName(pkg.applicationInfo.processName,
+                        s.info.processName, pkg.applicationInfo.uid);
+                mServices.addService(s);
+                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(s.info.name);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Services: " + r);
+            }
+
+            N = pkg.receivers.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.Activity a = pkg.receivers.get(i);
+                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
+                        a.info.processName, pkg.applicationInfo.uid);
+                mReceivers.addActivity(a, "receiver");
+                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(a.info.name);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Receivers: " + r);
+            }
+
+            N = pkg.activities.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.Activity a = pkg.activities.get(i);
+                a.info.processName = fixProcessName(pkg.applicationInfo.processName,
+                        a.info.processName, pkg.applicationInfo.uid);
+                mActivities.addActivity(a, "activity");
+                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(a.info.name);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Activities: " + r);
+            }
+
+            N = pkg.permissionGroups.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
+                PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
+                if (cur == null) {
+                    mPermissionGroups.put(pg.info.name, pg);
+                    if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(pg.info.name);
+                    }
+                } else {
+                    Slog.w(TAG, "Permission group " + pg.info.name + " from package "
+                            + pg.info.packageName + " ignored: original from "
+                            + cur.info.packageName);
+                    if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append("DUP:");
+                        r.append(pg.info.name);
+                    }
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permission Groups: " + r);
+            }
+
+            N = pkg.permissions.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.Permission p = pkg.permissions.get(i);
+                HashMap<String, BasePermission> permissionMap =
+                        p.tree ? mSettings.mPermissionTrees
+                        : mSettings.mPermissions;
+                p.group = mPermissionGroups.get(p.info.group);
+                if (p.info.group == null || p.group != null) {
+                    BasePermission bp = permissionMap.get(p.info.name);
+                    if (bp == null) {
+                        bp = new BasePermission(p.info.name, p.info.packageName,
+                                BasePermission.TYPE_NORMAL);
+                        permissionMap.put(p.info.name, bp);
+                    }
+                    if (bp.perm == null) {
+                        if (bp.sourcePackage != null
+                                && !bp.sourcePackage.equals(p.info.packageName)) {
+                            // If this is a permission that was formerly defined by a non-system
+                            // app, but is now defined by a system app (following an upgrade),
+                            // discard the previous declaration and consider the system's to be
+                            // canonical.
+                            if (isSystemApp(p.owner)) {
+                                Slog.i(TAG, "New decl " + p.owner + " of permission  "
+                                        + p.info.name + " is system");
+                                bp.sourcePackage = null;
+                            }
+                        }
+                        if (bp.sourcePackage == null
+                                || bp.sourcePackage.equals(p.info.packageName)) {
+                            BasePermission tree = findPermissionTreeLP(p.info.name);
+                            if (tree == null
+                                    || tree.sourcePackage.equals(p.info.packageName)) {
+                                bp.packageSetting = pkgSetting;
+                                bp.perm = p;
+                                bp.uid = pkg.applicationInfo.uid;
+                                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                                    if (r == null) {
+                                        r = new StringBuilder(256);
+                                    } else {
+                                        r.append(' ');
+                                    }
+                                    r.append(p.info.name);
+                                }
+                            } else {
+                                Slog.w(TAG, "Permission " + p.info.name + " from package "
+                                        + p.info.packageName + " ignored: base tree "
+                                        + tree.name + " is from package "
+                                        + tree.sourcePackage);
+                            }
+                        } else {
+                            Slog.w(TAG, "Permission " + p.info.name + " from package "
+                                    + p.info.packageName + " ignored: original from "
+                                    + bp.sourcePackage);
+                        }
+                    } else if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append("DUP:");
+                        r.append(p.info.name);
+                    }
+                    if (bp.perm == p) {
+                        bp.protectionLevel = p.info.protectionLevel;
+                    }
+                } else {
+                    Slog.w(TAG, "Permission " + p.info.name + " from package "
+                            + p.info.packageName + " ignored: no group "
+                            + p.group);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permissions: " + r);
+            }
+
+            N = pkg.instrumentation.size();
+            r = null;
+            for (i=0; i<N; i++) {
+                PackageParser.Instrumentation a = pkg.instrumentation.get(i);
+                a.info.packageName = pkg.applicationInfo.packageName;
+                a.info.sourceDir = pkg.applicationInfo.sourceDir;
+                a.info.publicSourceDir = pkg.applicationInfo.publicSourceDir;
+                a.info.dataDir = pkg.applicationInfo.dataDir;
+                a.info.nativeLibraryDir = pkg.applicationInfo.nativeLibraryDir;
+                mInstrumentation.put(a.getComponentName(), a);
+                if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(a.info.name);
+                }
+            }
+            if (r != null) {
+                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Instrumentation: " + r);
+            }
+
+            if (pkg.protectedBroadcasts != null) {
+                N = pkg.protectedBroadcasts.size();
+                for (i=0; i<N; i++) {
+                    mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
+                }
+            }
+
+            pkgSetting.setTimeStamp(scanFileTime);
+        }
+
+        return pkg;
+    }
+
+    private void setUpCustomResolverActivity(PackageParser.Package pkg) {
+        synchronized (mPackages) {
+            mResolverReplaced = true;
+            // Set up information for custom user intent resolution activity.
+            mResolveActivity.applicationInfo = pkg.applicationInfo;
+            mResolveActivity.name = mCustomResolverComponentName.getClassName();
+            mResolveActivity.packageName = pkg.applicationInfo.packageName;
+            mResolveActivity.processName = null;
+            mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+            mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS |
+                    ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
+            mResolveActivity.theme = 0;
+            mResolveActivity.exported = true;
+            mResolveActivity.enabled = true;
+            mResolveInfo.activityInfo = mResolveActivity;
+            mResolveInfo.priority = 0;
+            mResolveInfo.preferredOrder = 0;
+            mResolveInfo.match = 0;
+            mResolveComponentName = mCustomResolverComponentName;
+            Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " +
+                    mResolveComponentName);
+        }
+    }
+
+    private void setInternalAppNativeLibraryPath(PackageParser.Package pkg,
+            PackageSetting pkgSetting) {
+        final String apkLibPath = getApkName(pkgSetting.codePathString);
+        final String nativeLibraryPath = new File(mAppLibInstallDir, apkLibPath).getPath();
+        pkg.applicationInfo.nativeLibraryDir = nativeLibraryPath;
+        pkgSetting.nativeLibraryPathString = nativeLibraryPath;
+    }
+
+    private static int copyNativeLibrariesForInternalApp(File scanFile, final File nativeLibraryDir)
+            throws IOException {
+        if (!nativeLibraryDir.isDirectory()) {
+            nativeLibraryDir.delete();
+
+            if (!nativeLibraryDir.mkdir()) {
+                throw new IOException("Cannot create " + nativeLibraryDir.getPath());
+            }
+
+            try {
+                Libcore.os.chmod(nativeLibraryDir.getPath(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH
+                        | S_IXOTH);
+            } catch (ErrnoException e) {
+                throw new IOException("Cannot chmod native library directory "
+                        + nativeLibraryDir.getPath(), e);
+            }
+        } else if (!SELinux.restorecon(nativeLibraryDir)) {
+            throw new IOException("Cannot set SELinux context for " + nativeLibraryDir.getPath());
+        }
+
+        /*
+         * If this is an internal application or our nativeLibraryPath points to
+         * the app-lib directory, unpack the libraries if necessary.
+         */
+        return NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir);
+    }
+
+    private void killApplication(String pkgName, int appId, String reason) {
+        // Request the ActivityManager to kill the process(only for existing packages)
+        // so that we do not end up in a confused state while the user is still using the older
+        // version of the application while the new one gets installed.
+        IActivityManager am = ActivityManagerNative.getDefault();
+        if (am != null) {
+            try {
+                am.killApplicationWithAppId(pkgName, appId, reason);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void removePackageLI(PackageSetting ps, boolean chatty) {
+        if (DEBUG_INSTALL) {
+            if (chatty)
+                Log.d(TAG, "Removing package " + ps.name);
+        }
+
+        // writer
+        synchronized (mPackages) {
+            mPackages.remove(ps.name);
+            if (ps.codePathString != null) {
+                mAppDirs.remove(ps.codePathString);
+            }
+
+            final PackageParser.Package pkg = ps.pkg;
+            if (pkg != null) {
+                cleanPackageDataStructuresLILPw(pkg, chatty);
+            }
+        }
+    }
+
+    void removeInstalledPackageLI(PackageParser.Package pkg, boolean chatty) {
+        if (DEBUG_INSTALL) {
+            if (chatty)
+                Log.d(TAG, "Removing package " + pkg.applicationInfo.packageName);
+        }
+
+        // writer
+        synchronized (mPackages) {
+            mPackages.remove(pkg.applicationInfo.packageName);
+            if (pkg.mPath != null) {
+                mAppDirs.remove(pkg.mPath);
+            }
+            cleanPackageDataStructuresLILPw(pkg, chatty);
+        }
+    }
+
+    void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) {
+        int N = pkg.providers.size();
+        StringBuilder r = null;
+        int i;
+        for (i=0; i<N; i++) {
+            PackageParser.Provider p = pkg.providers.get(i);
+            mProviders.removeProvider(p);
+            if (p.info.authority == null) {
+
+                /* There was another ContentProvider with this authority when
+                 * this app was installed so this authority is null,
+                 * Ignore it as we don't have to unregister the provider.
+                 */
+                continue;
+            }
+            String names[] = p.info.authority.split(";");
+            for (int j = 0; j < names.length; j++) {
+                if (mProvidersByAuthority.get(names[j]) == p) {
+                    mProvidersByAuthority.remove(names[j]);
+                    if (DEBUG_REMOVE) {
+                        if (chatty)
+                            Log.d(TAG, "Unregistered content provider: " + names[j]
+                                    + ", className = " + p.info.name + ", isSyncable = "
+                                    + p.info.isSyncable);
+                    }
+                }
+            }
+            if (DEBUG_REMOVE && chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(p.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Providers: " + r);
+        }
+
+        N = pkg.services.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Service s = pkg.services.get(i);
+            mServices.removeService(s);
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(s.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Services: " + r);
+        }
+
+        N = pkg.receivers.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Activity a = pkg.receivers.get(i);
+            mReceivers.removeActivity(a, "receiver");
+            if (DEBUG_REMOVE && chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(a.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Receivers: " + r);
+        }
+
+        N = pkg.activities.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Activity a = pkg.activities.get(i);
+            mActivities.removeActivity(a, "activity");
+            if (DEBUG_REMOVE && chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(a.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Activities: " + r);
+        }
+
+        N = pkg.permissions.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Permission p = pkg.permissions.get(i);
+            BasePermission bp = mSettings.mPermissions.get(p.info.name);
+            if (bp == null) {
+                bp = mSettings.mPermissionTrees.get(p.info.name);
+            }
+            if (bp != null && bp.perm == p) {
+                bp.perm = null;
+                if (DEBUG_REMOVE && chatty) {
+                    if (r == null) {
+                        r = new StringBuilder(256);
+                    } else {
+                        r.append(' ');
+                    }
+                    r.append(p.info.name);
+                }
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+        }
+
+        N = pkg.instrumentation.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Instrumentation a = pkg.instrumentation.get(i);
+            mInstrumentation.remove(a.getComponentName());
+            if (DEBUG_REMOVE && chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(a.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Instrumentation: " + r);
+        }
+
+        r = null;
+        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+            // Only system apps can hold shared libraries.
+            if (pkg.libraryNames != null) {
+                for (i=0; i<pkg.libraryNames.size(); i++) {
+                    String name = pkg.libraryNames.get(i);
+                    SharedLibraryEntry cur = mSharedLibraries.get(name);
+                    if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) {
+                        mSharedLibraries.remove(name);
+                        if (DEBUG_REMOVE && chatty) {
+                            if (r == null) {
+                                r = new StringBuilder(256);
+                            } else {
+                                r.append(' ');
+                            }
+                            r.append(name);
+                        }
+                    }
+                }
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Libraries: " + r);
+        }
+    }
+
+    private static final boolean isPackageFilename(String name) {
+        return name != null && name.endsWith(".apk");
+    }
+
+    private static boolean hasPermission(PackageParser.Package pkgInfo, String perm) {
+        for (int i=pkgInfo.permissions.size()-1; i>=0; i--) {
+            if (pkgInfo.permissions.get(i).info.name.equals(perm)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static final int UPDATE_PERMISSIONS_ALL = 1<<0;
+    static final int UPDATE_PERMISSIONS_REPLACE_PKG = 1<<1;
+    static final int UPDATE_PERMISSIONS_REPLACE_ALL = 1<<2;
+
+    private void updatePermissionsLPw(String changingPkg,
+            PackageParser.Package pkgInfo, int flags) {
+        // Make sure there are no dangling permission trees.
+        Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
+        while (it.hasNext()) {
+            final BasePermission bp = it.next();
+            if (bp.packageSetting == null) {
+                // We may not yet have parsed the package, so just see if
+                // we still know about its settings.
+                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+            }
+            if (bp.packageSetting == null) {
+                Slog.w(TAG, "Removing dangling permission tree: " + bp.name
+                        + " from package " + bp.sourcePackage);
+                it.remove();
+            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
+                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
+                    Slog.i(TAG, "Removing old permission tree: " + bp.name
+                            + " from package " + bp.sourcePackage);
+                    flags |= UPDATE_PERMISSIONS_ALL;
+                    it.remove();
+                }
+            }
+        }
+
+        // Make sure all dynamic permissions have been assigned to a package,
+        // and make sure there are no dangling permissions.
+        it = mSettings.mPermissions.values().iterator();
+        while (it.hasNext()) {
+            final BasePermission bp = it.next();
+            if (bp.type == BasePermission.TYPE_DYNAMIC) {
+                if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
+                        + bp.name + " pkg=" + bp.sourcePackage
+                        + " info=" + bp.pendingInfo);
+                if (bp.packageSetting == null && bp.pendingInfo != null) {
+                    final BasePermission tree = findPermissionTreeLP(bp.name);
+                    if (tree != null && tree.perm != null) {
+                        bp.packageSetting = tree.packageSetting;
+                        bp.perm = new PackageParser.Permission(tree.perm.owner,
+                                new PermissionInfo(bp.pendingInfo));
+                        bp.perm.info.packageName = tree.perm.info.packageName;
+                        bp.perm.info.name = bp.name;
+                        bp.uid = tree.uid;
+                    }
+                }
+            }
+            if (bp.packageSetting == null) {
+                // We may not yet have parsed the package, so just see if
+                // we still know about its settings.
+                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+            }
+            if (bp.packageSetting == null) {
+                Slog.w(TAG, "Removing dangling permission: " + bp.name
+                        + " from package " + bp.sourcePackage);
+                it.remove();
+            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
+                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
+                    Slog.i(TAG, "Removing old permission: " + bp.name
+                            + " from package " + bp.sourcePackage);
+                    flags |= UPDATE_PERMISSIONS_ALL;
+                    it.remove();
+                }
+            }
+        }
+
+        // Now update the permissions for all packages, in particular
+        // replace the granted permissions of the system packages.
+        if ((flags&UPDATE_PERMISSIONS_ALL) != 0) {
+            for (PackageParser.Package pkg : mPackages.values()) {
+                if (pkg != pkgInfo) {
+                    grantPermissionsLPw(pkg, (flags&UPDATE_PERMISSIONS_REPLACE_ALL) != 0);
+                }
+            }
+        }
+        
+        if (pkgInfo != null) {
+            grantPermissionsLPw(pkgInfo, (flags&UPDATE_PERMISSIONS_REPLACE_PKG) != 0);
+        }
+    }
+
+    private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace) {
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+        final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+        HashSet<String> origPermissions = gp.grantedPermissions;
+        boolean changedPermission = false;
+
+        if (replace) {
+            ps.permissionsFixed = false;
+            if (gp == ps) {
+                origPermissions = new HashSet<String>(gp.grantedPermissions);
+                gp.grantedPermissions.clear();
+                gp.gids = mGlobalGids;
+            }
+        }
+
+        if (gp.gids == null) {
+            gp.gids = mGlobalGids;
+        }
+
+        final int N = pkg.requestedPermissions.size();
+        for (int i=0; i<N; i++) {
+            final String name = pkg.requestedPermissions.get(i);
+            final boolean required = pkg.requestedPermissionsRequired.get(i);
+            final BasePermission bp = mSettings.mPermissions.get(name);
+            if (DEBUG_INSTALL) {
+                if (gp != ps) {
+                    Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
+                }
+            }
+
+            if (bp == null || bp.packageSetting == null) {
+                Slog.w(TAG, "Unknown permission " + name
+                        + " in package " + pkg.packageName);
+                continue;
+            }
+
+            final String perm = bp.name;
+            boolean allowed;
+            boolean allowedSig = false;
+            final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
+            if (level == PermissionInfo.PROTECTION_NORMAL
+                    || level == PermissionInfo.PROTECTION_DANGEROUS) {
+                // We grant a normal or dangerous permission if any of the following
+                // are true:
+                // 1) The permission is required
+                // 2) The permission is optional, but was granted in the past
+                // 3) The permission is optional, but was requested by an
+                //    app in /system (not /data)
+                //
+                // Otherwise, reject the permission.
+                allowed = (required || origPermissions.contains(perm)
+                        || (isSystemApp(ps) && !isUpdatedSystemApp(ps)));
+            } else if (bp.packageSetting == null) {
+                // This permission is invalid; skip it.
+                allowed = false;
+            } else if (level == PermissionInfo.PROTECTION_SIGNATURE) {
+                allowed = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                if (allowed) {
+                    allowedSig = true;
+                }
+            } else {
+                allowed = false;
+            }
+            if (DEBUG_INSTALL) {
+                if (gp != ps) {
+                    Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
+                }
+            }
+            if (allowed) {
+                if (!isSystemApp(ps) && ps.permissionsFixed) {
+                    // If this is an existing, non-system package, then
+                    // we can't add any new permissions to it.
+                    if (!allowedSig && !gp.grantedPermissions.contains(perm)) {
+                        // Except...  if this is a permission that was added
+                        // to the platform (note: need to only do this when
+                        // updating the platform).
+                        allowed = isNewPlatformPermissionForPackage(perm, pkg);
+                    }
+                }
+                if (allowed) {
+                    if (!gp.grantedPermissions.contains(perm)) {
+                        changedPermission = true;
+                        gp.grantedPermissions.add(perm);
+                        gp.gids = appendInts(gp.gids, bp.gids);
+                    } else if (!ps.haveGids) {
+                        gp.gids = appendInts(gp.gids, bp.gids);
+                    }
+                } else {
+                    Slog.w(TAG, "Not granting permission " + perm
+                            + " to package " + pkg.packageName
+                            + " because it was previously installed without");
+                }
+            } else {
+                if (gp.grantedPermissions.remove(perm)) {
+                    changedPermission = true;
+                    gp.gids = removeInts(gp.gids, bp.gids);
+                    Slog.i(TAG, "Un-granting permission " + perm
+                            + " from package " + pkg.packageName
+                            + " (protectionLevel=" + bp.protectionLevel
+                            + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                            + ")");
+                } else {
+                    Slog.w(TAG, "Not granting permission " + perm
+                            + " to package " + pkg.packageName
+                            + " (protectionLevel=" + bp.protectionLevel
+                            + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
+                            + ")");
+                }
+            }
+        }
+
+        if ((changedPermission || replace) && !ps.permissionsFixed &&
+                !isSystemApp(ps) || isUpdatedSystemApp(ps)){
+            // This is the first that we have heard about this package, so the
+            // permissions we have now selected are fixed until explicitly
+            // changed.
+            ps.permissionsFixed = true;
+        }
+        ps.haveGids = true;
+    }
+
+    private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
+        boolean allowed = false;
+        final int NP = PackageParser.NEW_PERMISSIONS.length;
+        for (int ip=0; ip<NP; ip++) {
+            final PackageParser.NewPermissionInfo npi
+                    = PackageParser.NEW_PERMISSIONS[ip];
+            if (npi.name.equals(perm)
+                    && pkg.applicationInfo.targetSdkVersion < npi.sdkVersion) {
+                allowed = true;
+                Log.i(TAG, "Auto-granting " + perm + " to old pkg "
+                        + pkg.packageName);
+                break;
+            }
+        }
+        return allowed;
+    }
+
+    private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
+                                          BasePermission bp, HashSet<String> origPermissions) {
+        boolean allowed;
+        allowed = (compareSignatures(
+                bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH)
+                || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
+                        == PackageManager.SIGNATURE_MATCH);
+        if (!allowed && (bp.protectionLevel
+                & PermissionInfo.PROTECTION_FLAG_SYSTEM) != 0) {
+            if (isSystemApp(pkg)) {
+                // For updated system applications, a system permission
+                // is granted only if it had been defined by the original application.
+                if (isUpdatedSystemApp(pkg)) {
+                    final PackageSetting sysPs = mSettings
+                            .getDisabledSystemPkgLPr(pkg.packageName);
+                    final GrantedPermissions origGp = sysPs.sharedUser != null
+                            ? sysPs.sharedUser : sysPs;
+
+                    if (origGp.grantedPermissions.contains(perm)) {
+                        // If the original was granted this permission, we take
+                        // that grant decision as read and propagate it to the
+                        // update.
+                        allowed = true;
+                    } else {
+                        // The system apk may have been updated with an older
+                        // version of the one on the data partition, but which
+                        // granted a new system permission that it didn't have
+                        // before.  In this case we do want to allow the app to
+                        // now get the new permission if the ancestral apk is
+                        // privileged to get it.
+                        if (sysPs.pkg != null && sysPs.isPrivileged()) {
+                            for (int j=0;
+                                    j<sysPs.pkg.requestedPermissions.size(); j++) {
+                                if (perm.equals(
+                                        sysPs.pkg.requestedPermissions.get(j))) {
+                                    allowed = true;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    allowed = isPrivilegedApp(pkg);
+                }
+            }
+        }
+        if (!allowed && (bp.protectionLevel
+                & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+            // For development permissions, a development permission
+            // is granted only if it was already granted.
+            allowed = origPermissions.contains(perm);
+        }
+        return allowed;
+    }
+
+    final class ActivityIntentResolver
+            extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
+                boolean defaultOnly, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
+        }
+
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
+                int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            mFlags = flags;
+            return super.queryIntent(intent, resolvedType,
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
+        }
+
+        public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
+                int flags, ArrayList<PackageParser.Activity> packageActivities, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            if (packageActivities == null) {
+                return null;
+            }
+            mFlags = flags;
+            final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0;
+            final int N = packageActivities.size();
+            ArrayList<PackageParser.ActivityIntentInfo[]> listCut =
+                new ArrayList<PackageParser.ActivityIntentInfo[]>(N);
+
+            ArrayList<PackageParser.ActivityIntentInfo> intentFilters;
+            for (int i = 0; i < N; ++i) {
+                intentFilters = packageActivities.get(i).intents;
+                if (intentFilters != null && intentFilters.size() > 0) {
+                    PackageParser.ActivityIntentInfo[] array =
+                            new PackageParser.ActivityIntentInfo[intentFilters.size()];
+                    intentFilters.toArray(array);
+                    listCut.add(array);
+                }
+            }
+            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+        }
+
+        public final void addActivity(PackageParser.Activity a, String type) {
+            final boolean systemApp = isSystemApp(a.info.applicationInfo);
+            mActivities.put(a.getComponentName(), a);
+            if (DEBUG_SHOW_INFO)
+                Log.v(
+                TAG, "  " + type + " " +
+                (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel : a.info.name) + ":");
+            if (DEBUG_SHOW_INFO)
+                Log.v(TAG, "    Class=" + a.info.name);
+            final int NI = a.intents.size();
+            for (int j=0; j<NI; j++) {
+                PackageParser.ActivityIntentInfo intent = a.intents.get(j);
+                if (!systemApp && intent.getPriority() > 0 && "activity".equals(type)) {
+                    intent.setPriority(0);
+                    Log.w(TAG, "Package " + a.info.applicationInfo.packageName + " has activity "
+                            + a.className + " with priority > 0, forcing to 0");
+                }
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                if (!intent.debugCheck()) {
+                    Log.w(TAG, "==> For Activity " + a.info.name);
+                }
+                addFilter(intent);
+            }
+        }
+
+        public final void removeActivity(PackageParser.Activity a, String type) {
+            mActivities.remove(a.getComponentName());
+            if (DEBUG_SHOW_INFO) {
+                Log.v(TAG, "  " + type + " "
+                        + (a.info.nonLocalizedLabel != null ? a.info.nonLocalizedLabel
+                                : a.info.name) + ":");
+                Log.v(TAG, "    Class=" + a.info.name);
+            }
+            final int NI = a.intents.size();
+            for (int j=0; j<NI; j++) {
+                PackageParser.ActivityIntentInfo intent = a.intents.get(j);
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                removeFilter(intent);
+            }
+        }
+
+        @Override
+        protected boolean allowFilterResult(
+                PackageParser.ActivityIntentInfo filter, List<ResolveInfo> dest) {
+            ActivityInfo filterAi = filter.activity.info;
+            for (int i=dest.size()-1; i>=0; i--) {
+                ActivityInfo destAi = dest.get(i).activityInfo;
+                if (destAi.name == filterAi.name
+                        && destAi.packageName == filterAi.packageName) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected ActivityIntentInfo[] newArray(int size) {
+            return new ActivityIntentInfo[size];
+        }
+
+        @Override
+        protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter, int userId) {
+            if (!sUserManager.exists(userId)) return true;
+            PackageParser.Package p = filter.activity.owner;
+            if (p != null) {
+                PackageSetting ps = (PackageSetting)p.mExtras;
+                if (ps != null) {
+                    // System apps are never considered stopped for purposes of
+                    // filtering, because there may be no way for the user to
+                    // actually re-launch them.
+                    return (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0
+                            && ps.getStopped(userId);
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName,
+                PackageParser.ActivityIntentInfo info) {
+            return packageName.equals(info.activity.owner.packageName);
+        }
+        
+        @Override
+        protected ResolveInfo newResult(PackageParser.ActivityIntentInfo info,
+                int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            if (!mSettings.isEnabledLPr(info.activity.info, mFlags, userId)) {
+                return null;
+            }
+            final PackageParser.Activity activity = info.activity;
+            if (mSafeMode && (activity.info.applicationInfo.flags
+                    &ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return null;
+            }
+            PackageSetting ps = (PackageSetting) activity.owner.mExtras;
+            if (ps == null) {
+                return null;
+            }
+            ActivityInfo ai = PackageParser.generateActivityInfo(activity, mFlags,
+                    ps.readUserState(userId), userId);
+            if (ai == null) {
+                return null;
+            }
+            final ResolveInfo res = new ResolveInfo();
+            res.activityInfo = ai;
+            if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
+                res.filter = info;
+            }
+            res.priority = info.getPriority();
+            res.preferredOrder = activity.owner.mPreferredOrder;
+            //System.out.println("Result: " + res.activityInfo.className +
+            //                   " = " + res.priority);
+            res.match = match;
+            res.isDefault = info.hasDefault;
+            res.labelRes = info.labelRes;
+            res.nonLocalizedLabel = info.nonLocalizedLabel;
+            res.icon = info.icon;
+            res.system = isSystemApp(res.activityInfo.applicationInfo);
+            return res;
+        }
+
+        @Override
+        protected void sortResults(List<ResolveInfo> results) {
+            Collections.sort(results, mResolvePrioritySorter);
+        }
+
+        @Override
+        protected void dumpFilter(PrintWriter out, String prefix,
+                PackageParser.ActivityIntentInfo filter) {
+            out.print(prefix); out.print(
+                    Integer.toHexString(System.identityHashCode(filter.activity)));
+                    out.print(' ');
+                    filter.activity.printComponentShortName(out);
+                    out.print(" filter ");
+                    out.println(Integer.toHexString(System.identityHashCode(filter)));
+        }
+
+//        List<ResolveInfo> filterEnabled(List<ResolveInfo> resolveInfoList) {
+//            final Iterator<ResolveInfo> i = resolveInfoList.iterator();
+//            final List<ResolveInfo> retList = Lists.newArrayList();
+//            while (i.hasNext()) {
+//                final ResolveInfo resolveInfo = i.next();
+//                if (isEnabledLP(resolveInfo.activityInfo)) {
+//                    retList.add(resolveInfo);
+//                }
+//            }
+//            return retList;
+//        }
+
+        // Keys are String (activity class name), values are Activity.
+        private final HashMap<ComponentName, PackageParser.Activity> mActivities
+                = new HashMap<ComponentName, PackageParser.Activity>();
+        private int mFlags;
+    }
+
+    private final class ServiceIntentResolver
+            extends IntentResolver<PackageParser.ServiceIntentInfo, ResolveInfo> {
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
+                boolean defaultOnly, int userId) {
+            mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
+        }
+
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
+                int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            mFlags = flags;
+            return super.queryIntent(intent, resolvedType,
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
+        }
+
+        public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
+                int flags, ArrayList<PackageParser.Service> packageServices, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            if (packageServices == null) {
+                return null;
+            }
+            mFlags = flags;
+            final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0;
+            final int N = packageServices.size();
+            ArrayList<PackageParser.ServiceIntentInfo[]> listCut =
+                new ArrayList<PackageParser.ServiceIntentInfo[]>(N);
+
+            ArrayList<PackageParser.ServiceIntentInfo> intentFilters;
+            for (int i = 0; i < N; ++i) {
+                intentFilters = packageServices.get(i).intents;
+                if (intentFilters != null && intentFilters.size() > 0) {
+                    PackageParser.ServiceIntentInfo[] array =
+                            new PackageParser.ServiceIntentInfo[intentFilters.size()];
+                    intentFilters.toArray(array);
+                    listCut.add(array);
+                }
+            }
+            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+        }
+
+        public final void addService(PackageParser.Service s) {
+            mServices.put(s.getComponentName(), s);
+            if (DEBUG_SHOW_INFO) {
+                Log.v(TAG, "  "
+                        + (s.info.nonLocalizedLabel != null
+                        ? s.info.nonLocalizedLabel : s.info.name) + ":");
+                Log.v(TAG, "    Class=" + s.info.name);
+            }
+            final int NI = s.intents.size();
+            int j;
+            for (j=0; j<NI; j++) {
+                PackageParser.ServiceIntentInfo intent = s.intents.get(j);
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                if (!intent.debugCheck()) {
+                    Log.w(TAG, "==> For Service " + s.info.name);
+                }
+                addFilter(intent);
+            }
+        }
+
+        public final void removeService(PackageParser.Service s) {
+            mServices.remove(s.getComponentName());
+            if (DEBUG_SHOW_INFO) {
+                Log.v(TAG, "  " + (s.info.nonLocalizedLabel != null
+                        ? s.info.nonLocalizedLabel : s.info.name) + ":");
+                Log.v(TAG, "    Class=" + s.info.name);
+            }
+            final int NI = s.intents.size();
+            int j;
+            for (j=0; j<NI; j++) {
+                PackageParser.ServiceIntentInfo intent = s.intents.get(j);
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                removeFilter(intent);
+            }
+        }
+
+        @Override
+        protected boolean allowFilterResult(
+                PackageParser.ServiceIntentInfo filter, List<ResolveInfo> dest) {
+            ServiceInfo filterSi = filter.service.info;
+            for (int i=dest.size()-1; i>=0; i--) {
+                ServiceInfo destAi = dest.get(i).serviceInfo;
+                if (destAi.name == filterSi.name
+                        && destAi.packageName == filterSi.packageName) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected PackageParser.ServiceIntentInfo[] newArray(int size) {
+            return new PackageParser.ServiceIntentInfo[size];
+        }
+
+        @Override
+        protected boolean isFilterStopped(PackageParser.ServiceIntentInfo filter, int userId) {
+            if (!sUserManager.exists(userId)) return true;
+            PackageParser.Package p = filter.service.owner;
+            if (p != null) {
+                PackageSetting ps = (PackageSetting)p.mExtras;
+                if (ps != null) {
+                    // System apps are never considered stopped for purposes of
+                    // filtering, because there may be no way for the user to
+                    // actually re-launch them.
+                    return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0
+                            && ps.getStopped(userId);
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName,
+                PackageParser.ServiceIntentInfo info) {
+            return packageName.equals(info.service.owner.packageName);
+        }
+        
+        @Override
+        protected ResolveInfo newResult(PackageParser.ServiceIntentInfo filter,
+                int match, int userId) {
+            if (!sUserManager.exists(userId)) return null;
+            final PackageParser.ServiceIntentInfo info = (PackageParser.ServiceIntentInfo)filter;
+            if (!mSettings.isEnabledLPr(info.service.info, mFlags, userId)) {
+                return null;
+            }
+            final PackageParser.Service service = info.service;
+            if (mSafeMode && (service.info.applicationInfo.flags
+                    &ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return null;
+            }
+            PackageSetting ps = (PackageSetting) service.owner.mExtras;
+            if (ps == null) {
+                return null;
+            }
+            ServiceInfo si = PackageParser.generateServiceInfo(service, mFlags,
+                    ps.readUserState(userId), userId);
+            if (si == null) {
+                return null;
+            }
+            final ResolveInfo res = new ResolveInfo();
+            res.serviceInfo = si;
+            if ((mFlags&PackageManager.GET_RESOLVED_FILTER) != 0) {
+                res.filter = filter;
+            }
+            res.priority = info.getPriority();
+            res.preferredOrder = service.owner.mPreferredOrder;
+            //System.out.println("Result: " + res.activityInfo.className +
+            //                   " = " + res.priority);
+            res.match = match;
+            res.isDefault = info.hasDefault;
+            res.labelRes = info.labelRes;
+            res.nonLocalizedLabel = info.nonLocalizedLabel;
+            res.icon = info.icon;
+            res.system = isSystemApp(res.serviceInfo.applicationInfo);
+            return res;
+        }
+
+        @Override
+        protected void sortResults(List<ResolveInfo> results) {
+            Collections.sort(results, mResolvePrioritySorter);
+        }
+
+        @Override
+        protected void dumpFilter(PrintWriter out, String prefix,
+                PackageParser.ServiceIntentInfo filter) {
+            out.print(prefix); out.print(
+                    Integer.toHexString(System.identityHashCode(filter.service)));
+                    out.print(' ');
+                    filter.service.printComponentShortName(out);
+                    out.print(" filter ");
+                    out.println(Integer.toHexString(System.identityHashCode(filter)));
+        }
+
+//        List<ResolveInfo> filterEnabled(List<ResolveInfo> resolveInfoList) {
+//            final Iterator<ResolveInfo> i = resolveInfoList.iterator();
+//            final List<ResolveInfo> retList = Lists.newArrayList();
+//            while (i.hasNext()) {
+//                final ResolveInfo resolveInfo = (ResolveInfo) i;
+//                if (isEnabledLP(resolveInfo.serviceInfo)) {
+//                    retList.add(resolveInfo);
+//                }
+//            }
+//            return retList;
+//        }
+
+        // Keys are String (activity class name), values are Activity.
+        private final HashMap<ComponentName, PackageParser.Service> mServices
+                = new HashMap<ComponentName, PackageParser.Service>();
+        private int mFlags;
+    };
+
+    private final class ProviderIntentResolver
+            extends IntentResolver<PackageParser.ProviderIntentInfo, ResolveInfo> {
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType,
+                boolean defaultOnly, int userId) {
+            mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0;
+            return super.queryIntent(intent, resolvedType, defaultOnly, userId);
+        }
+
+        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
+                int userId) {
+            if (!sUserManager.exists(userId))
+                return null;
+            mFlags = flags;
+            return super.queryIntent(intent, resolvedType,
+                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId);
+        }
+
+        public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
+                int flags, ArrayList<PackageParser.Provider> packageProviders, int userId) {
+            if (!sUserManager.exists(userId))
+                return null;
+            if (packageProviders == null) {
+                return null;
+            }
+            mFlags = flags;
+            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
+            final int N = packageProviders.size();
+            ArrayList<PackageParser.ProviderIntentInfo[]> listCut =
+                    new ArrayList<PackageParser.ProviderIntentInfo[]>(N);
+
+            ArrayList<PackageParser.ProviderIntentInfo> intentFilters;
+            for (int i = 0; i < N; ++i) {
+                intentFilters = packageProviders.get(i).intents;
+                if (intentFilters != null && intentFilters.size() > 0) {
+                    PackageParser.ProviderIntentInfo[] array =
+                            new PackageParser.ProviderIntentInfo[intentFilters.size()];
+                    intentFilters.toArray(array);
+                    listCut.add(array);
+                }
+            }
+            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
+        }
+
+        public final void addProvider(PackageParser.Provider p) {
+            mProviders.put(p.getComponentName(), p);
+            if (DEBUG_SHOW_INFO) {
+                Log.v(TAG, "  "
+                        + (p.info.nonLocalizedLabel != null
+                                ? p.info.nonLocalizedLabel : p.info.name) + ":");
+                Log.v(TAG, "    Class=" + p.info.name);
+            }
+            final int NI = p.intents.size();
+            int j;
+            for (j = 0; j < NI; j++) {
+                PackageParser.ProviderIntentInfo intent = p.intents.get(j);
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                if (!intent.debugCheck()) {
+                    Log.w(TAG, "==> For Provider " + p.info.name);
+                }
+                addFilter(intent);
+            }
+        }
+
+        public final void removeProvider(PackageParser.Provider p) {
+            mProviders.remove(p.getComponentName());
+            if (DEBUG_SHOW_INFO) {
+                Log.v(TAG, "  " + (p.info.nonLocalizedLabel != null
+                        ? p.info.nonLocalizedLabel : p.info.name) + ":");
+                Log.v(TAG, "    Class=" + p.info.name);
+            }
+            final int NI = p.intents.size();
+            int j;
+            for (j = 0; j < NI; j++) {
+                PackageParser.ProviderIntentInfo intent = p.intents.get(j);
+                if (DEBUG_SHOW_INFO) {
+                    Log.v(TAG, "    IntentFilter:");
+                    intent.dump(new LogPrinter(Log.VERBOSE, TAG), "      ");
+                }
+                removeFilter(intent);
+            }
+        }
+
+        @Override
+        protected boolean allowFilterResult(
+                PackageParser.ProviderIntentInfo filter, List<ResolveInfo> dest) {
+            ProviderInfo filterPi = filter.provider.info;
+            for (int i = dest.size() - 1; i >= 0; i--) {
+                ProviderInfo destPi = dest.get(i).providerInfo;
+                if (destPi.name == filterPi.name
+                        && destPi.packageName == filterPi.packageName) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        protected PackageParser.ProviderIntentInfo[] newArray(int size) {
+            return new PackageParser.ProviderIntentInfo[size];
+        }
+
+        @Override
+        protected boolean isFilterStopped(PackageParser.ProviderIntentInfo filter, int userId) {
+            if (!sUserManager.exists(userId))
+                return true;
+            PackageParser.Package p = filter.provider.owner;
+            if (p != null) {
+                PackageSetting ps = (PackageSetting) p.mExtras;
+                if (ps != null) {
+                    // System apps are never considered stopped for purposes of
+                    // filtering, because there may be no way for the user to
+                    // actually re-launch them.
+                    return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0
+                            && ps.getStopped(userId);
+                }
+            }
+            return false;
+        }
+
+        @Override
+        protected boolean isPackageForFilter(String packageName,
+                PackageParser.ProviderIntentInfo info) {
+            return packageName.equals(info.provider.owner.packageName);
+        }
+
+        @Override
+        protected ResolveInfo newResult(PackageParser.ProviderIntentInfo filter,
+                int match, int userId) {
+            if (!sUserManager.exists(userId))
+                return null;
+            final PackageParser.ProviderIntentInfo info = filter;
+            if (!mSettings.isEnabledLPr(info.provider.info, mFlags, userId)) {
+                return null;
+            }
+            final PackageParser.Provider provider = info.provider;
+            if (mSafeMode && (provider.info.applicationInfo.flags
+                    & ApplicationInfo.FLAG_SYSTEM) == 0) {
+                return null;
+            }
+            PackageSetting ps = (PackageSetting) provider.owner.mExtras;
+            if (ps == null) {
+                return null;
+            }
+            ProviderInfo pi = PackageParser.generateProviderInfo(provider, mFlags,
+                    ps.readUserState(userId), userId);
+            if (pi == null) {
+                return null;
+            }
+            final ResolveInfo res = new ResolveInfo();
+            res.providerInfo = pi;
+            if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
+                res.filter = filter;
+            }
+            res.priority = info.getPriority();
+            res.preferredOrder = provider.owner.mPreferredOrder;
+            res.match = match;
+            res.isDefault = info.hasDefault;
+            res.labelRes = info.labelRes;
+            res.nonLocalizedLabel = info.nonLocalizedLabel;
+            res.icon = info.icon;
+            res.system = isSystemApp(res.providerInfo.applicationInfo);
+            return res;
+        }
+
+        @Override
+        protected void sortResults(List<ResolveInfo> results) {
+            Collections.sort(results, mResolvePrioritySorter);
+        }
+
+        @Override
+        protected void dumpFilter(PrintWriter out, String prefix,
+                PackageParser.ProviderIntentInfo filter) {
+            out.print(prefix);
+            out.print(
+                    Integer.toHexString(System.identityHashCode(filter.provider)));
+            out.print(' ');
+            filter.provider.printComponentShortName(out);
+            out.print(" filter ");
+            out.println(Integer.toHexString(System.identityHashCode(filter)));
+        }
+
+        private final HashMap<ComponentName, PackageParser.Provider> mProviders
+                = new HashMap<ComponentName, PackageParser.Provider>();
+        private int mFlags;
+    };
+
+    private static final Comparator<ResolveInfo> mResolvePrioritySorter =
+            new Comparator<ResolveInfo>() {
+        public int compare(ResolveInfo r1, ResolveInfo r2) {
+            int v1 = r1.priority;
+            int v2 = r2.priority;
+            //System.out.println("Comparing: q1=" + q1 + " q2=" + q2);
+            if (v1 != v2) {
+                return (v1 > v2) ? -1 : 1;
+            }
+            v1 = r1.preferredOrder;
+            v2 = r2.preferredOrder;
+            if (v1 != v2) {
+                return (v1 > v2) ? -1 : 1;
+            }
+            if (r1.isDefault != r2.isDefault) {
+                return r1.isDefault ? -1 : 1;
+            }
+            v1 = r1.match;
+            v2 = r2.match;
+            //System.out.println("Comparing: m1=" + m1 + " m2=" + m2);
+            if (v1 != v2) {
+                return (v1 > v2) ? -1 : 1;
+            }
+            if (r1.system != r2.system) {
+                return r1.system ? -1 : 1;
+            }
+            return 0;
+        }
+    };
+
+    private static final Comparator<ProviderInfo> mProviderInitOrderSorter =
+            new Comparator<ProviderInfo>() {
+        public int compare(ProviderInfo p1, ProviderInfo p2) {
+            final int v1 = p1.initOrder;
+            final int v2 = p2.initOrder;
+            return (v1 > v2) ? -1 : ((v1 < v2) ? 1 : 0);
+        }
+    };
+
+    static final void sendPackageBroadcast(String action, String pkg,
+            Bundle extras, String targetPkg, IIntentReceiver finishedReceiver,
+            int[] userIds) {
+        IActivityManager am = ActivityManagerNative.getDefault();
+        if (am != null) {
+            try {
+                if (userIds == null) {
+                    userIds = am.getRunningUserIds();
+                }
+                for (int id : userIds) {
+                    final Intent intent = new Intent(action,
+                            pkg != null ? Uri.fromParts("package", pkg, null) : null);
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                    if (targetPkg != null) {
+                        intent.setPackage(targetPkg);
+                    }
+                    // Modify the UID when posting to other users
+                    int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+                    if (uid > 0 && UserHandle.getUserId(uid) != id) {
+                        uid = UserHandle.getUid(id, UserHandle.getAppId(uid));
+                        intent.putExtra(Intent.EXTRA_UID, uid);
+                    }
+                    intent.putExtra(Intent.EXTRA_USER_HANDLE, id);
+                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                    if (DEBUG_BROADCASTS) {
+                        RuntimeException here = new RuntimeException("here");
+                        here.fillInStackTrace();
+                        Slog.d(TAG, "Sending to user " + id + ": "
+                                + intent.toShortString(false, true, false, false)
+                                + " " + intent.getExtras(), here);
+                    }
+                    am.broadcastIntent(null, intent, null, finishedReceiver,
+                            0, null, null, null, android.app.AppOpsManager.OP_NONE,
+                            finishedReceiver != null, false, id);
+                }
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    /**
+     * Check if the external storage media is available. This is true if there
+     * is a mounted external storage medium or if the external storage is
+     * emulated.
+     */
+    private boolean isExternalMediaAvailable() {
+        return mMediaMounted || Environment.isExternalStorageEmulated();
+    }
+
+    public PackageCleanItem nextPackageToClean(PackageCleanItem lastPackage) {
+        // writer
+        synchronized (mPackages) {
+            if (!isExternalMediaAvailable()) {
+                // If the external storage is no longer mounted at this point,
+                // the caller may not have been able to delete all of this
+                // packages files and can not delete any more.  Bail.
+                return null;
+            }
+            final ArrayList<PackageCleanItem> pkgs = mSettings.mPackagesToBeCleaned;
+            if (lastPackage != null) {
+                pkgs.remove(lastPackage);
+            }
+            if (pkgs.size() > 0) {
+                return pkgs.get(0);
+            }
+        }
+        return null;
+    }
+
+    void schedulePackageCleaning(String packageName, int userId, boolean andCode) {
+        if (false) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.d(TAG, "Schedule cleaning " + packageName + " user=" + userId
+                    + " andCode=" + andCode, here);
+        }
+        mHandler.sendMessage(mHandler.obtainMessage(START_CLEANING_PACKAGE,
+                userId, andCode ? 1 : 0, packageName));
+    }
+    
+    void startCleaningPackages() {
+        // reader
+        synchronized (mPackages) {
+            if (!isExternalMediaAvailable()) {
+                return;
+            }
+            if (mSettings.mPackagesToBeCleaned.isEmpty()) {
+                return;
+            }
+        }
+        Intent intent = new Intent(PackageManager.ACTION_CLEAN_EXTERNAL_STORAGE);
+        intent.setComponent(DEFAULT_CONTAINER_COMPONENT);
+        IActivityManager am = ActivityManagerNative.getDefault();
+        if (am != null) {
+            try {
+                am.startService(null, intent, null, UserHandle.USER_OWNER);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+    
+    private final class AppDirObserver extends FileObserver {
+        public AppDirObserver(String path, int mask, boolean isrom, boolean isPrivileged) {
+            super(path, mask);
+            mRootDir = path;
+            mIsRom = isrom;
+            mIsPrivileged = isPrivileged;
+        }
+
+        public void onEvent(int event, String path) {
+            String removedPackage = null;
+            int removedAppId = -1;
+            int[] removedUsers = null;
+            String addedPackage = null;
+            int addedAppId = -1;
+            int[] addedUsers = null;
+
+            // TODO post a message to the handler to obtain serial ordering
+            synchronized (mInstallLock) {
+                String fullPathStr = null;
+                File fullPath = null;
+                if (path != null) {
+                    fullPath = new File(mRootDir, path);
+                    fullPathStr = fullPath.getPath();
+                }
+
+                if (DEBUG_APP_DIR_OBSERVER)
+                    Log.v(TAG, "File " + fullPathStr + " changed: " + Integer.toHexString(event));
+
+                if (!isPackageFilename(path)) {
+                    if (DEBUG_APP_DIR_OBSERVER)
+                        Log.v(TAG, "Ignoring change of non-package file: " + fullPathStr);
+                    return;
+                }
+
+                // Ignore packages that are being installed or
+                // have just been installed.
+                if (ignoreCodePath(fullPathStr)) {
+                    return;
+                }
+                PackageParser.Package p = null;
+                PackageSetting ps = null;
+                // reader
+                synchronized (mPackages) {
+                    p = mAppDirs.get(fullPathStr);
+                    if (p != null) {
+                        ps = mSettings.mPackages.get(p.applicationInfo.packageName);
+                        if (ps != null) {
+                            removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
+                        } else {
+                            removedUsers = sUserManager.getUserIds();
+                        }
+                    }
+                    addedUsers = sUserManager.getUserIds();
+                }
+                if ((event&REMOVE_EVENTS) != 0) {
+                    if (ps != null) {
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps);
+                        removePackageLI(ps, true);
+                        removedPackage = ps.name;
+                        removedAppId = ps.appId;
+                    }
+                }
+
+                if ((event&ADD_EVENTS) != 0) {
+                    if (p == null) {
+                        if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath);
+                        int flags = PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK;
+                        if (mIsRom) {
+                            flags |= PackageParser.PARSE_IS_SYSTEM
+                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
+                            if (mIsPrivileged) {
+                                flags |= PackageParser.PARSE_IS_PRIVILEGED;
+                            }
+                        }
+                        p = scanPackageLI(fullPath, flags,
+                                SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME,
+                                System.currentTimeMillis(), UserHandle.ALL);
+                        if (p != null) {
+                            /*
+                             * TODO this seems dangerous as the package may have
+                             * changed since we last acquired the mPackages
+                             * lock.
+                             */
+                            // writer
+                            synchronized (mPackages) {
+                                updatePermissionsLPw(p.packageName, p,
+                                        p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0);
+                            }
+                            addedPackage = p.applicationInfo.packageName;
+                            addedAppId = UserHandle.getAppId(p.applicationInfo.uid);
+                        }
+                    }
+                }
+
+                // reader
+                synchronized (mPackages) {
+                    mSettings.writeLPr();
+                }
+            }
+
+            if (removedPackage != null) {
+                Bundle extras = new Bundle(1);
+                extras.putInt(Intent.EXTRA_UID, removedAppId);
+                extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
+                sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
+                        extras, null, null, removedUsers);
+            }
+            if (addedPackage != null) {
+                Bundle extras = new Bundle(1);
+                extras.putInt(Intent.EXTRA_UID, addedAppId);
+                sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
+                        extras, null, null, addedUsers);
+            }
+        }
+
+        private final String mRootDir;
+        private final boolean mIsRom;
+        private final boolean mIsPrivileged;
+    }
+
+    /* Called when a downloaded package installation has been confirmed by the user */
+    public void installPackage(
+            final Uri packageURI, final IPackageInstallObserver observer, final int flags) {
+        installPackage(packageURI, observer, flags, null);
+    }
+
+    /* Called when a downloaded package installation has been confirmed by the user */
+    public void installPackage(
+            final Uri packageURI, final IPackageInstallObserver observer, final int flags,
+            final String installerPackageName) {
+        installPackageWithVerification(packageURI, observer, flags, installerPackageName, null,
+                null, null);
+    }
+
+    @Override
+    public void installPackageWithVerification(Uri packageURI, IPackageInstallObserver observer,
+            int flags, String installerPackageName, Uri verificationURI,
+            ManifestDigest manifestDigest, ContainerEncryptionParams encryptionParams) {
+        VerificationParams verificationParams = new VerificationParams(verificationURI, null, null,
+                VerificationParams.NO_UID, manifestDigest);
+        installPackageWithVerificationAndEncryption(packageURI, observer, flags,
+                installerPackageName, verificationParams, encryptionParams);
+    }
+
+    public void installPackageWithVerificationAndEncryption(Uri packageURI,
+            IPackageInstallObserver observer, int flags, String installerPackageName,
+            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
+                null);
+
+        final int uid = Binder.getCallingUid();
+        if (isUserRestricted(UserHandle.getUserId(uid), UserManager.DISALLOW_INSTALL_APPS)) {
+            try {
+                observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED);
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
+        UserHandle user;
+        if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
+            user = UserHandle.ALL;
+        } else {
+            user = new UserHandle(UserHandle.getUserId(uid));
+        }
+
+        final int filteredFlags;
+
+        if (uid == Process.SHELL_UID || uid == 0) {
+            if (DEBUG_INSTALL) {
+                Slog.v(TAG, "Install from ADB");
+            }
+            filteredFlags = flags | PackageManager.INSTALL_FROM_ADB;
+        } else {
+            filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB;
+        }
+
+        verificationParams.setInstallerUid(uid);
+
+        final Message msg = mHandler.obtainMessage(INIT_COPY);
+        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
+                verificationParams, encryptionParams, user);
+        mHandler.sendMessage(msg);
+    }
+
+    private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, int userId) {
+        Bundle extras = new Bundle(1);
+        extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId));
+
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
+                packageName, extras, null, null, new int[] {userId});
+        try {
+            IActivityManager am = ActivityManagerNative.getDefault();
+            final boolean isSystem =
+                    isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting);
+            if (isSystem && am.isUserRunning(userId, false)) {
+                // The just-installed/enabled app is bundled on the system, so presumed
+                // to be able to run automatically without needing an explicit launch.
+                // Send it a BOOT_COMPLETED if it would ordinarily have gotten one.
+                Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
+                        .setPackage(packageName);
+                am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null,
+                        android.app.AppOpsManager.OP_NONE, false, false, userId);
+            }
+        } catch (RemoteException e) {
+            // shouldn't happen
+            Slog.w(TAG, "Unable to bootstrap installed package", e);
+        }
+    }
+
+    @Override
+    public boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked,
+            int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "setApplicationBlockedSetting for user " + userId);
+        }
+
+        if (blocked && isPackageDeviceAdmin(packageName, userId)) {
+            Slog.w(TAG, "Not blocking package " + packageName + ": has active device admin");
+            return false;
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean sendAdded = false;
+            boolean sendRemoved = false;
+            // writer
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null) {
+                    return false;
+                }
+                if (pkgSetting.getBlocked(userId) != blocked) {
+                    pkgSetting.setBlocked(blocked, userId);
+                    mSettings.writePackageRestrictionsLPr(userId);
+                    if (blocked) {
+                        sendRemoved = true;
+                    } else {
+                        sendAdded = true;
+                    }
+                }
+            }
+            if (sendAdded) {
+                sendPackageAddedForUser(packageName, pkgSetting, userId);
+                return true;
+            }
+            if (sendRemoved) {
+                killApplication(packageName, UserHandle.getUid(userId, pkgSetting.appId),
+                        "blocking pkg");
+                sendPackageBlockedForUser(packageName, pkgSetting, userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        return false;
+    }
+
+    private void sendPackageBlockedForUser(String packageName, PackageSetting pkgSetting,
+            int userId) {
+        final PackageRemovedInfo info = new PackageRemovedInfo();
+        info.removedPackage = packageName;
+        info.removedUsers = new int[] {userId};
+        info.uid = UserHandle.getUid(userId, pkgSetting.appId);
+        info.sendBroadcast(false, false, false);
+    }
+
+    /**
+     * Returns true if application is not found or there was an error. Otherwise it returns
+     * the blocked state of the package for the given user.
+     */
+    @Override
+    public boolean getApplicationBlockedSettingAsUser(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "getApplicationBlocked for user " + userId);
+        }
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            // writer
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null) {
+                    return true;
+                }
+                return pkgSetting.getBlocked(userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int installExistingPackageAsUser(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
+                null);
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "installExistingPackage for user " + userId);
+        }
+        if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
+            return PackageManager.INSTALL_FAILED_USER_RESTRICTED;
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            boolean sendAdded = false;
+            Bundle extras = new Bundle(1);
+
+            // writer
+            synchronized (mPackages) {
+                pkgSetting = mSettings.mPackages.get(packageName);
+                if (pkgSetting == null) {
+                    return PackageManager.INSTALL_FAILED_INVALID_URI;
+                }
+                if (!pkgSetting.getInstalled(userId)) {
+                    pkgSetting.setInstalled(true, userId);
+                    pkgSetting.setBlocked(false, userId);
+                    mSettings.writePackageRestrictionsLPr(userId);
+                    sendAdded = true;
+                }
+            }
+
+            if (sendAdded) {
+                sendPackageAddedForUser(packageName, pkgSetting, userId);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+
+        return PackageManager.INSTALL_SUCCEEDED;
+    }
+
+    private boolean isUserRestricted(int userId, String restrictionKey) {
+        Bundle restrictions = sUserManager.getUserRestrictions(userId);
+        if (restrictions.getBoolean(restrictionKey, false)) {
+            Log.w(TAG, "User is restricted: " + restrictionKey);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, int verificationCode) throws RemoteException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                "Only package verification agents can verify applications");
+
+        final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
+        final PackageVerificationResponse response = new PackageVerificationResponse(
+                verificationCode, Binder.getCallingUid());
+        msg.arg1 = id;
+        msg.obj = response;
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+            long millisecondsToDelay) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                "Only package verification agents can extend verification timeouts");
+
+        final PackageVerificationState state = mPendingVerification.get(id);
+        final PackageVerificationResponse response = new PackageVerificationResponse(
+                verificationCodeAtTimeout, Binder.getCallingUid());
+
+        if (millisecondsToDelay > PackageManager.MAXIMUM_VERIFICATION_TIMEOUT) {
+            millisecondsToDelay = PackageManager.MAXIMUM_VERIFICATION_TIMEOUT;
+        }
+        if (millisecondsToDelay < 0) {
+            millisecondsToDelay = 0;
+        }
+        if ((verificationCodeAtTimeout != PackageManager.VERIFICATION_ALLOW)
+                && (verificationCodeAtTimeout != PackageManager.VERIFICATION_REJECT)) {
+            verificationCodeAtTimeout = PackageManager.VERIFICATION_REJECT;
+        }
+
+        if ((state != null) && !state.timeoutExtended()) {
+            state.extendTimeout();
+
+            final Message msg = mHandler.obtainMessage(PACKAGE_VERIFIED);
+            msg.arg1 = id;
+            msg.obj = response;
+            mHandler.sendMessageDelayed(msg, millisecondsToDelay);
+        }
+    }
+
+    private void broadcastPackageVerified(int verificationId, Uri packageUri,
+            int verificationCode, UserHandle user) {
+        final Intent intent = new Intent(Intent.ACTION_PACKAGE_VERIFIED);
+        intent.setDataAndType(packageUri, PACKAGE_MIME_TYPE);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        intent.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
+        intent.putExtra(PackageManager.EXTRA_VERIFICATION_RESULT, verificationCode);
+
+        mContext.sendBroadcastAsUser(intent, user,
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
+    }
+
+    private ComponentName matchComponentForVerifier(String packageName,
+            List<ResolveInfo> receivers) {
+        ActivityInfo targetReceiver = null;
+
+        final int NR = receivers.size();
+        for (int i = 0; i < NR; i++) {
+            final ResolveInfo info = receivers.get(i);
+            if (info.activityInfo == null) {
+                continue;
+            }
+
+            if (packageName.equals(info.activityInfo.packageName)) {
+                targetReceiver = info.activityInfo;
+                break;
+            }
+        }
+
+        if (targetReceiver == null) {
+            return null;
+        }
+
+        return new ComponentName(targetReceiver.packageName, targetReceiver.name);
+    }
+
+    private List<ComponentName> matchVerifiers(PackageInfoLite pkgInfo,
+            List<ResolveInfo> receivers, final PackageVerificationState verificationState) {
+        if (pkgInfo.verifiers.length == 0) {
+            return null;
+        }
+
+        final int N = pkgInfo.verifiers.length;
+        final List<ComponentName> sufficientVerifiers = new ArrayList<ComponentName>(N + 1);
+        for (int i = 0; i < N; i++) {
+            final VerifierInfo verifierInfo = pkgInfo.verifiers[i];
+
+            final ComponentName comp = matchComponentForVerifier(verifierInfo.packageName,
+                    receivers);
+            if (comp == null) {
+                continue;
+            }
+
+            final int verifierUid = getUidForVerifier(verifierInfo);
+            if (verifierUid == -1) {
+                continue;
+            }
+
+            if (DEBUG_VERIFY) {
+                Slog.d(TAG, "Added sufficient verifier " + verifierInfo.packageName
+                        + " with the correct signature");
+            }
+            sufficientVerifiers.add(comp);
+            verificationState.addSufficientVerifier(verifierUid);
+        }
+
+        return sufficientVerifiers;
+    }
+
+    private int getUidForVerifier(VerifierInfo verifierInfo) {
+        synchronized (mPackages) {
+            final PackageParser.Package pkg = mPackages.get(verifierInfo.packageName);
+            if (pkg == null) {
+                return -1;
+            } else if (pkg.mSignatures.length != 1) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " has more than one signature; ignoring");
+                return -1;
+            }
+
+            /*
+             * If the public key of the package's signature does not match
+             * our expected public key, then this is a different package and
+             * we should skip.
+             */
+
+            final byte[] expectedPublicKey;
+            try {
+                final Signature verifierSig = pkg.mSignatures[0];
+                final PublicKey publicKey = verifierSig.getPublicKey();
+                expectedPublicKey = publicKey.getEncoded();
+            } catch (CertificateException e) {
+                return -1;
+            }
+
+            final byte[] actualPublicKey = verifierInfo.publicKey.getEncoded();
+
+            if (!Arrays.equals(actualPublicKey, expectedPublicKey)) {
+                Slog.i(TAG, "Verifier package " + verifierInfo.packageName
+                        + " does not have the expected public key; ignoring");
+                return -1;
+            }
+
+            return pkg.applicationInfo.uid;
+        }
+    }
+
+    public void finishPackageInstall(int token) {
+        enforceSystemOrRoot("Only the system is allowed to finish installs");
+
+        if (DEBUG_INSTALL) {
+            Slog.v(TAG, "BM finishing package install for " + token);
+        }
+
+        final Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+        mHandler.sendMessage(msg);
+    }
+
+    /**
+     * Get the verification agent timeout.
+     *
+     * @return verification timeout in milliseconds
+     */
+    private long getVerificationTimeout() {
+        return android.provider.Settings.Global.getLong(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
+                DEFAULT_VERIFICATION_TIMEOUT);
+    }
+
+    /**
+     * Get the default verification agent response code.
+     *
+     * @return default verification response code
+     */
+    private int getDefaultVerificationResponse() {
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+                DEFAULT_VERIFICATION_RESPONSE);
+    }
+
+    /**
+     * Check whether or not package verification has been enabled.
+     *
+     * @return true if verification should be performed
+     */
+    private boolean isVerificationEnabled(int flags) {
+        if (!DEFAULT_VERIFY_ENABLE) {
+            return false;
+        }
+
+        // Check if installing from ADB
+        if ((flags & PackageManager.INSTALL_FROM_ADB) != 0) {
+            // Do not run verification in a test harness environment
+            if (ActivityManager.isRunningInTestHarness()) {
+                return false;
+            }
+            // Check if the developer does not want package verification for ADB installs
+            if (android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                    android.provider.Settings.Global.PACKAGE_VERIFIER_INCLUDE_ADB, 1) == 0) {
+                return false;
+            }
+        }
+
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE, 1) == 1;
+    }
+
+    /**
+     * Get the "allow unknown sources" setting.
+     *
+     * @return the current "allow unknown sources" setting
+     */
+    private int getUnknownSourcesSettings() {
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.INSTALL_NON_MARKET_APPS,
+                -1);
+    }
+
+    public void setInstallerPackageName(String targetPackage, String installerPackageName) {
+        final int uid = Binder.getCallingUid();
+        // writer
+        synchronized (mPackages) {
+            PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage);
+            if (targetPackageSetting == null) {
+                throw new IllegalArgumentException("Unknown target package: " + targetPackage);
+            }
+
+            PackageSetting installerPackageSetting;
+            if (installerPackageName != null) {
+                installerPackageSetting = mSettings.mPackages.get(installerPackageName);
+                if (installerPackageSetting == null) {
+                    throw new IllegalArgumentException("Unknown installer package: "
+                            + installerPackageName);
+                }
+            } else {
+                installerPackageSetting = null;
+            }
+
+            Signature[] callerSignature;
+            Object obj = mSettings.getUserIdLPr(uid);
+            if (obj != null) {
+                if (obj instanceof SharedUserSetting) {
+                    callerSignature = ((SharedUserSetting)obj).signatures.mSignatures;
+                } else if (obj instanceof PackageSetting) {
+                    callerSignature = ((PackageSetting)obj).signatures.mSignatures;
+                } else {
+                    throw new SecurityException("Bad object " + obj + " for uid " + uid);
+                }
+            } else {
+                throw new SecurityException("Unknown calling uid " + uid);
+            }
+
+            // Verify: can't set installerPackageName to a package that is
+            // not signed with the same cert as the caller.
+            if (installerPackageSetting != null) {
+                if (compareSignatures(callerSignature,
+                        installerPackageSetting.signatures.mSignatures)
+                        != PackageManager.SIGNATURE_MATCH) {
+                    throw new SecurityException(
+                            "Caller does not have same cert as new installer package "
+                            + installerPackageName);
+                }
+            }
+
+            // Verify: if target already has an installer package, it must
+            // be signed with the same cert as the caller.
+            if (targetPackageSetting.installerPackageName != null) {
+                PackageSetting setting = mSettings.mPackages.get(
+                        targetPackageSetting.installerPackageName);
+                // If the currently set package isn't valid, then it's always
+                // okay to change it.
+                if (setting != null) {
+                    if (compareSignatures(callerSignature,
+                            setting.signatures.mSignatures)
+                            != PackageManager.SIGNATURE_MATCH) {
+                        throw new SecurityException(
+                                "Caller does not have same cert as old installer package "
+                                + targetPackageSetting.installerPackageName);
+                    }
+                }
+            }
+
+            // Okay!
+            targetPackageSetting.installerPackageName = installerPackageName;
+            scheduleWriteSettingsLocked();
+        }
+    }
+
+    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
+        // Queue up an async operation since the package installation may take a little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                 // Result object to be returned
+                PackageInstalledInfo res = new PackageInstalledInfo();
+                res.returnCode = currentStatus;
+                res.uid = -1;
+                res.pkg = null;
+                res.removedInfo = new PackageRemovedInfo();
+                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
+                    args.doPreInstall(res.returnCode);
+                    synchronized (mInstallLock) {
+                        installPackageLI(args, true, res);
+                    }
+                    args.doPostInstall(res.returnCode, res.uid);
+                }
+
+                // A restore should be performed at this point if (a) the install
+                // succeeded, (b) the operation is not an update, and (c) the new
+                // package has a backupAgent defined.
+                final boolean update = res.removedInfo.removedPackage != null;
+                boolean doRestore = (!update
+                        && res.pkg != null
+                        && res.pkg.applicationInfo.backupAgentName != null);
+
+                // Set up the post-install work request bookkeeping.  This will be used
+                // and cleaned up by the post-install event handling regardless of whether
+                // there's a restore pass performed.  Token values are >= 1.
+                int token;
+                if (mNextInstallToken < 0) mNextInstallToken = 1;
+                token = mNextInstallToken++;
+
+                PostInstallData data = new PostInstallData(args, res);
+                mRunningInstalls.put(token, data);
+                if (DEBUG_INSTALL) Log.v(TAG, "+ starting restore round-trip " + token);
+
+                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
+                    // Pass responsibility to the Backup Manager.  It will perform a
+                    // restore if appropriate, then pass responsibility back to the
+                    // Package Manager to run the post-install observer callbacks
+                    // and broadcasts.
+                    IBackupManager bm = IBackupManager.Stub.asInterface(
+                            ServiceManager.getService(Context.BACKUP_SERVICE));
+                    if (bm != null) {
+                        if (DEBUG_INSTALL) Log.v(TAG, "token " + token
+                                + " to BM for possible restore");
+                        try {
+                            bm.restoreAtInstall(res.pkg.applicationInfo.packageName, token);
+                        } catch (RemoteException e) {
+                            // can't happen; the backup manager is local
+                        } catch (Exception e) {
+                            Slog.e(TAG, "Exception trying to enqueue restore", e);
+                            doRestore = false;
+                        }
+                    } else {
+                        Slog.e(TAG, "Backup Manager not found!");
+                        doRestore = false;
+                    }
+                }
+
+                if (!doRestore) {
+                    // No restore possible, or the Backup Manager was mysteriously not
+                    // available -- just fire the post-install work request directly.
+                    if (DEBUG_INSTALL) Log.v(TAG, "No restore - queue post-install for " + token);
+                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
+                    mHandler.sendMessage(msg);
+                }
+            }
+        });
+    }
+
+    private abstract class HandlerParams {
+        private static final int MAX_RETRIES = 4;
+
+        /**
+         * Number of times startCopy() has been attempted and had a non-fatal
+         * error.
+         */
+        private int mRetries = 0;
+
+        /** User handle for the user requesting the information or installation. */
+        private final UserHandle mUser;
+
+        HandlerParams(UserHandle user) {
+            mUser = user;
+        }
+
+        UserHandle getUser() {
+            return mUser;
+        }
+
+        final boolean startCopy() {
+            boolean res;
+            try {
+                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
+
+                if (++mRetries > MAX_RETRIES) {
+                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
+                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
+                    handleServiceError();
+                    return false;
+                } else {
+                    handleStartCopy();
+                    res = true;
+                }
+            } catch (RemoteException e) {
+                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
+                mHandler.sendEmptyMessage(MCS_RECONNECT);
+                res = false;
+            }
+            handleReturnCode();
+            return res;
+        }
+
+        final void serviceError() {
+            if (DEBUG_INSTALL) Slog.i(TAG, "serviceError");
+            handleServiceError();
+            handleReturnCode();
+        }
+
+        abstract void handleStartCopy() throws RemoteException;
+        abstract void handleServiceError();
+        abstract void handleReturnCode();
+    }
+
+    class MeasureParams extends HandlerParams {
+        private final PackageStats mStats;
+        private boolean mSuccess;
+
+        private final IPackageStatsObserver mObserver;
+
+        public MeasureParams(PackageStats stats, IPackageStatsObserver observer) {
+            super(new UserHandle(stats.userHandle));
+            mObserver = observer;
+            mStats = stats;
+        }
+
+        @Override
+        public String toString() {
+            return "MeasureParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mStats.packageName + "}";
+        }
+
+        @Override
+        void handleStartCopy() throws RemoteException {
+            synchronized (mInstallLock) {
+                mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats);
+            }
+
+            final boolean mounted;
+            if (Environment.isExternalStorageEmulated()) {
+                mounted = true;
+            } else {
+                final String status = Environment.getExternalStorageState();
+                mounted = (Environment.MEDIA_MOUNTED.equals(status)
+                        || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status));
+            }
+
+            if (mounted) {
+                final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
+
+                mStats.externalCacheSize = calculateDirectorySize(mContainerService,
+                        userEnv.buildExternalStorageAppCacheDirs(mStats.packageName));
+
+                mStats.externalDataSize = calculateDirectorySize(mContainerService,
+                        userEnv.buildExternalStorageAppDataDirs(mStats.packageName));
+
+                // Always subtract cache size, since it's a subdirectory
+                mStats.externalDataSize -= mStats.externalCacheSize;
+
+                mStats.externalMediaSize = calculateDirectorySize(mContainerService,
+                        userEnv.buildExternalStorageAppMediaDirs(mStats.packageName));
+
+                mStats.externalObbSize = calculateDirectorySize(mContainerService,
+                        userEnv.buildExternalStorageAppObbDirs(mStats.packageName));
+            }
+        }
+
+        @Override
+        void handleReturnCode() {
+            if (mObserver != null) {
+                try {
+                    mObserver.onGetStatsCompleted(mStats, mSuccess);
+                } catch (RemoteException e) {
+                    Slog.i(TAG, "Observer no longer exists.");
+                }
+            }
+        }
+
+        @Override
+        void handleServiceError() {
+            Slog.e(TAG, "Could not measure application " + mStats.packageName
+                            + " external storage");
+        }
+    }
+
+    private static long calculateDirectorySize(IMediaContainerService mcs, File[] paths)
+            throws RemoteException {
+        long result = 0;
+        for (File path : paths) {
+            result += mcs.calculateDirectorySize(path.getAbsolutePath());
+        }
+        return result;
+    }
+
+    private static void clearDirectory(IMediaContainerService mcs, File[] paths) {
+        for (File path : paths) {
+            try {
+                mcs.clearDirectory(path.getAbsolutePath());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    class InstallParams extends HandlerParams {
+        final IPackageInstallObserver observer;
+        int flags;
+
+        private final Uri mPackageURI;
+        final String installerPackageName;
+        final VerificationParams verificationParams;
+        private InstallArgs mArgs;
+        private int mRet;
+        private File mTempPackage;
+        final ContainerEncryptionParams encryptionParams;
+
+        InstallParams(Uri packageURI,
+                IPackageInstallObserver observer, int flags,
+                String installerPackageName, VerificationParams verificationParams,
+                ContainerEncryptionParams encryptionParams, UserHandle user) {
+            super(user);
+            this.mPackageURI = packageURI;
+            this.flags = flags;
+            this.observer = observer;
+            this.installerPackageName = installerPackageName;
+            this.verificationParams = verificationParams;
+            this.encryptionParams = encryptionParams;
+        }
+
+        @Override
+        public String toString() {
+            return "InstallParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + mPackageURI + "}";
+        }
+
+        public ManifestDigest getManifestDigest() {
+            if (verificationParams == null) {
+                return null;
+            }
+            return verificationParams.getManifestDigest();
+        }
+
+        private int installLocationPolicy(PackageInfoLite pkgLite, int flags) {
+            String packageName = pkgLite.packageName;
+            int installLocation = pkgLite.installLocation;
+            boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+            // reader
+            synchronized (mPackages) {
+                PackageParser.Package pkg = mPackages.get(packageName);
+                if (pkg != null) {
+                    if ((flags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+                        // Check for downgrading.
+                        if ((flags & PackageManager.INSTALL_ALLOW_DOWNGRADE) == 0) {
+                            if (pkgLite.versionCode < pkg.mVersionCode) {
+                                Slog.w(TAG, "Can't install update of " + packageName
+                                        + " update version " + pkgLite.versionCode
+                                        + " is older than installed version "
+                                        + pkg.mVersionCode);
+                                return PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE;
+                            }
+                        }
+                        // Check for updated system application.
+                        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                            if (onSd) {
+                                Slog.w(TAG, "Cannot install update to system app on sdcard");
+                                return PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION;
+                            }
+                            return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                        } else {
+                            if (onSd) {
+                                // Install flag overrides everything.
+                                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                            }
+                            // If current upgrade specifies particular preference
+                            if (installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
+                                // Application explicitly specified internal.
+                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            } else if (installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL) {
+                                // App explictly prefers external. Let policy decide
+                            } else {
+                                // Prefer previous location
+                                if (isExternal(pkg)) {
+                                    return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+                                }
+                                return PackageHelper.RECOMMEND_INSTALL_INTERNAL;
+                            }
+                        }
+                    } else {
+                        // Invalid install. Return error code
+                        return PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS;
+                    }
+                }
+            }
+            // All the special cases have been taken care of.
+            // Return result based on recommended install location.
+            if (onSd) {
+                return PackageHelper.RECOMMEND_INSTALL_EXTERNAL;
+            }
+            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
+         * policy if needed and then create install arguments based
+         * on the install location.
+         */
+        public void handleStartCopy() throws RemoteException {
+            int ret = PackageManager.INSTALL_SUCCEEDED;
+            final boolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+            final boolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0;
+            PackageInfoLite pkgLite = null;
+
+            if (onInt && onSd) {
+                // Check if both bits are set.
+                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
+                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+            } else {
+                final long lowThreshold = getMemoryLowThreshold();
+                if (lowThreshold == 0L) {
+                    Log.w(TAG, "Couldn't get low memory threshold; no free limit imposed");
+                }
+
+                try {
+                    mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+                    final File packageFile;
+                    if (encryptionParams != null || !"file".equals(mPackageURI.getScheme())) {
+                        mTempPackage = createTempPackageFile(mDrmAppPrivateInstallDir);
+                        if (mTempPackage != null) {
+                            ParcelFileDescriptor out;
+                            try {
+                                out = ParcelFileDescriptor.open(mTempPackage,
+                                        ParcelFileDescriptor.MODE_READ_WRITE);
+                            } catch (FileNotFoundException e) {
+                                out = null;
+                                Slog.e(TAG, "Failed to create temporary file for : " + mPackageURI);
+                            }
+
+                            // Make a temporary file for decryption.
+                            ret = mContainerService
+                                    .copyResource(mPackageURI, encryptionParams, out);
+                            IoUtils.closeQuietly(out);
+
+                            packageFile = mTempPackage;
+
+                            FileUtils.setPermissions(packageFile.getAbsolutePath(),
+                                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
+                                            | FileUtils.S_IROTH,
+                                    -1, -1);
+                        } else {
+                            packageFile = null;
+                        }
+                    } else {
+                        packageFile = new File(mPackageURI.getPath());
+                    }
+
+                    if (packageFile != null) {
+                        // Remote call to find out default install location
+                        final String packageFilePath = packageFile.getAbsolutePath();
+                        pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags,
+                                lowThreshold);
+
+                        /*
+                         * If we have too little free space, try to free cache
+                         * before giving up.
+                         */
+                        if (pkgLite.recommendedInstallLocation
+                                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+                            final long size = mContainerService.calculateInstalledSize(
+                                    packageFilePath, isForwardLocked());
+                            if (mInstaller.freeCache(size + lowThreshold) >= 0) {
+                                pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath,
+                                        flags, lowThreshold);
+                            }
+                            /*
+                             * The cache free must have deleted the file we
+                             * downloaded to install.
+                             *
+                             * TODO: fix the "freeCache" call to not delete
+                             *       the file we care about.
+                             */
+                            if (pkgLite.recommendedInstallLocation
+                                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
+                                pkgLite.recommendedInstallLocation
+                                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
+                            }
+                        }
+                    }
+                } finally {
+                    mContext.revokeUriPermission(mPackageURI,
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                }
+            }
+
+            if (ret == PackageManager.INSTALL_SUCCEEDED) {
+                int loc = pkgLite.recommendedInstallLocation;
+                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
+                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
+                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
+                    ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_APK) {
+                    ret = PackageManager.INSTALL_FAILED_INVALID_APK;
+                } else if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
+                    ret = PackageManager.INSTALL_FAILED_INVALID_URI;
+                } else if (loc == PackageHelper.RECOMMEND_MEDIA_UNAVAILABLE) {
+                    ret = PackageManager.INSTALL_FAILED_MEDIA_UNAVAILABLE;
+                } else {
+                    // Override with defaults if needed.
+                    loc = installLocationPolicy(pkgLite, flags);
+                    if (loc == PackageHelper.RECOMMEND_FAILED_VERSION_DOWNGRADE) {
+                        ret = PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
+                    } else if (!onSd && !onInt) {
+                        // Override install location with flags
+                        if (loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) {
+                            // Set the flag to install on external media.
+                            flags |= PackageManager.INSTALL_EXTERNAL;
+                            flags &= ~PackageManager.INSTALL_INTERNAL;
+                        } else {
+                            // Make sure the flag for installing on external
+                            // media is unset
+                            flags |= PackageManager.INSTALL_INTERNAL;
+                            flags &= ~PackageManager.INSTALL_EXTERNAL;
+                        }
+                    }
+                }
+            }
+
+            final InstallArgs args = createInstallArgs(this);
+            mArgs = args;
+
+            if (ret == PackageManager.INSTALL_SUCCEEDED) {
+                 /*
+                 * ADB installs appear as UserHandle.USER_ALL, and can only be performed by
+                 * UserHandle.USER_OWNER, so use the package verifier for UserHandle.USER_OWNER.
+                 */
+                int userIdentifier = getUser().getIdentifier();
+                if (userIdentifier == UserHandle.USER_ALL
+                        && ((flags & PackageManager.INSTALL_FROM_ADB) != 0)) {
+                    userIdentifier = UserHandle.USER_OWNER;
+                }
+
+                /*
+                 * Determine if we have any installed package verifiers. If we
+                 * do, then we'll defer to them to verify the packages.
+                 */
+                final int requiredUid = mRequiredVerifierPackage == null ? -1
+                        : getPackageUid(mRequiredVerifierPackage, userIdentifier);
+                if (requiredUid != -1 && isVerificationEnabled(flags)) {
+                    final Intent verification = new Intent(
+                            Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+                    verification.setDataAndType(getPackageUri(), PACKAGE_MIME_TYPE);
+                    verification.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+                    final List<ResolveInfo> receivers = queryIntentReceivers(verification,
+                            PACKAGE_MIME_TYPE, PackageManager.GET_DISABLED_COMPONENTS,
+                            0 /* TODO: Which userId? */);
+
+                    if (DEBUG_VERIFY) {
+                        Slog.d(TAG, "Found " + receivers.size() + " verifiers for intent "
+                                + verification.toString() + " with " + pkgLite.verifiers.length
+                                + " optional verifiers");
+                    }
+
+                    final int verificationId = mPendingVerificationToken++;
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_ID, verificationId);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE,
+                            installerPackageName);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALL_FLAGS, flags);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME,
+                            pkgLite.packageName);
+
+                    verification.putExtra(PackageManager.EXTRA_VERIFICATION_VERSION_CODE,
+                            pkgLite.versionCode);
+
+                    if (verificationParams != null) {
+                        if (verificationParams.getVerificationURI() != null) {
+                           verification.putExtra(PackageManager.EXTRA_VERIFICATION_URI,
+                                 verificationParams.getVerificationURI());
+                        }
+                        if (verificationParams.getOriginatingURI() != null) {
+                            verification.putExtra(Intent.EXTRA_ORIGINATING_URI,
+                                  verificationParams.getOriginatingURI());
+                        }
+                        if (verificationParams.getReferrer() != null) {
+                            verification.putExtra(Intent.EXTRA_REFERRER,
+                                  verificationParams.getReferrer());
+                        }
+                        if (verificationParams.getOriginatingUid() >= 0) {
+                            verification.putExtra(Intent.EXTRA_ORIGINATING_UID,
+                                  verificationParams.getOriginatingUid());
+                        }
+                        if (verificationParams.getInstallerUid() >= 0) {
+                            verification.putExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID,
+                                  verificationParams.getInstallerUid());
+                        }
+                    }
+
+                    final PackageVerificationState verificationState = new PackageVerificationState(
+                            requiredUid, args);
+
+                    mPendingVerification.append(verificationId, verificationState);
+
+                    final List<ComponentName> sufficientVerifiers = matchVerifiers(pkgLite,
+                            receivers, verificationState);
+
+                    /*
+                     * If any sufficient verifiers were listed in the package
+                     * manifest, attempt to ask them.
+                     */
+                    if (sufficientVerifiers != null) {
+                        final int N = sufficientVerifiers.size();
+                        if (N == 0) {
+                            Slog.i(TAG, "Additional verifiers required, but none installed.");
+                            ret = PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE;
+                        } else {
+                            for (int i = 0; i < N; i++) {
+                                final ComponentName verifierComponent = sufficientVerifiers.get(i);
+
+                                final Intent sufficientIntent = new Intent(verification);
+                                sufficientIntent.setComponent(verifierComponent);
+
+                                mContext.sendBroadcastAsUser(sufficientIntent, getUser());
+                            }
+                        }
+                    }
+
+                    final ComponentName requiredVerifierComponent = matchComponentForVerifier(
+                            mRequiredVerifierPackage, receivers);
+                    if (ret == PackageManager.INSTALL_SUCCEEDED
+                            && mRequiredVerifierPackage != null) {
+                        /*
+                         * Send the intent to the required verification agent,
+                         * but only start the verification timeout after the
+                         * target BroadcastReceivers have run.
+                         */
+                        verification.setComponent(requiredVerifierComponent);
+                        mContext.sendOrderedBroadcastAsUser(verification, getUser(),
+                                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                                new BroadcastReceiver() {
+                                    @Override
+                                    public void onReceive(Context context, Intent intent) {
+                                        final Message msg = mHandler
+                                                .obtainMessage(CHECK_PENDING_VERIFICATION);
+                                        msg.arg1 = verificationId;
+                                        mHandler.sendMessageDelayed(msg, getVerificationTimeout());
+                                    }
+                                }, null, 0, null, null);
+
+                        /*
+                         * We don't want the copy to proceed until verification
+                         * succeeds, so null out this field.
+                         */
+                        mArgs = null;
+                    }
+                } else {
+                    /*
+                     * No package verification is enabled, so immediately start
+                     * the remote call to initiate copy using temporary file.
+                     */
+                    ret = args.copyApk(mContainerService, true);
+                }
+            }
+
+            mRet = ret;
+        }
+
+        @Override
+        void handleReturnCode() {
+            // If mArgs is null, then MCS couldn't be reached. When it
+            // reconnects, it will try again to install. At that point, this
+            // will succeed.
+            if (mArgs != null) {
+                processPendingInstall(mArgs, mRet);
+
+                if (mTempPackage != null) {
+                    if (!mTempPackage.delete()) {
+                        Slog.w(TAG, "Couldn't delete temporary file: " +
+                                mTempPackage.getAbsolutePath());
+                    }
+                }
+            }
+        }
+
+        @Override
+        void handleServiceError() {
+            mArgs = createInstallArgs(this);
+            mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        }
+
+        public boolean isForwardLocked() {
+            return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+        }
+
+        public Uri getPackageUri() {
+            if (mTempPackage != null) {
+                return Uri.fromFile(mTempPackage);
+            } else {
+                return mPackageURI;
+            }
+        }
+    }
+
+    /*
+     * Utility class used in movePackage api.
+     * srcArgs and targetArgs are not set for invalid flags and make
+     * sure to do null checks when invoking methods on them.
+     * We probably want to return ErrorPrams for both failed installs
+     * and moves.
+     */
+    class MoveParams extends HandlerParams {
+        final IPackageMoveObserver observer;
+        final int flags;
+        final String packageName;
+        final InstallArgs srcArgs;
+        final InstallArgs targetArgs;
+        int uid;
+        int mRet;
+
+        MoveParams(InstallArgs srcArgs, IPackageMoveObserver observer, int flags,
+                String packageName, String dataDir, int uid, UserHandle user) {
+            super(user);
+            this.srcArgs = srcArgs;
+            this.observer = observer;
+            this.flags = flags;
+            this.packageName = packageName;
+            this.uid = uid;
+            if (srcArgs != null) {
+                Uri packageUri = Uri.fromFile(new File(srcArgs.getCodePath()));
+                targetArgs = createInstallArgs(packageUri, flags, packageName, dataDir);
+            } else {
+                targetArgs = null;
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "MoveParams{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " " + packageName + "}";
+        }
+
+        public void handleStartCopy() throws RemoteException {
+            mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            // Check for storage space on target medium
+            if (!targetArgs.checkFreeStorage(mContainerService)) {
+                Log.w(TAG, "Insufficient storage to install");
+                return;
+            }
+
+            mRet = srcArgs.doPreCopy();
+            if (mRet != PackageManager.INSTALL_SUCCEEDED) {
+                return;
+            }
+
+            mRet = targetArgs.copyApk(mContainerService, false);
+            if (mRet != PackageManager.INSTALL_SUCCEEDED) {
+                srcArgs.doPostCopy(uid);
+                return;
+            }
+
+            mRet = srcArgs.doPostCopy(uid);
+            if (mRet != PackageManager.INSTALL_SUCCEEDED) {
+                return;
+            }
+
+            mRet = targetArgs.doPreInstall(mRet);
+            if (mRet != PackageManager.INSTALL_SUCCEEDED) {
+                return;
+            }
+
+            if (DEBUG_SD_INSTALL) {
+                StringBuilder builder = new StringBuilder();
+                if (srcArgs != null) {
+                    builder.append("src: ");
+                    builder.append(srcArgs.getCodePath());
+                }
+                if (targetArgs != null) {
+                    builder.append(" target : ");
+                    builder.append(targetArgs.getCodePath());
+                }
+                Log.i(TAG, builder.toString());
+            }
+        }
+
+        @Override
+        void handleReturnCode() {
+            targetArgs.doPostInstall(mRet, uid);
+            int currentStatus = PackageManager.MOVE_FAILED_INTERNAL_ERROR;
+            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
+                currentStatus = PackageManager.MOVE_SUCCEEDED;
+            } else if (mRet == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE){
+                currentStatus = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+            }
+            processPendingMove(this, currentStatus);
+        }
+
+        @Override
+        void handleServiceError() {
+            mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+        }
+    }
+
+    /**
+     * Used during creation of InstallArgs
+     *
+     * @param flags package installation flags
+     * @return true if should be installed on external storage
+     */
+    private static boolean installOnSd(int flags) {
+        if ((flags & PackageManager.INSTALL_INTERNAL) != 0) {
+            return false;
+        }
+        if ((flags & PackageManager.INSTALL_EXTERNAL) != 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Used during creation of InstallArgs
+     *
+     * @param flags package installation flags
+     * @return true if should be installed as forward locked
+     */
+    private static boolean installForwardLocked(int flags) {
+        return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+    }
+
+    private InstallArgs createInstallArgs(InstallParams params) {
+        if (installOnSd(params.flags) || params.isForwardLocked()) {
+            return new AsecInstallArgs(params);
+        } else {
+            return new FileInstallArgs(params);
+        }
+    }
+
+    private InstallArgs createInstallArgs(int flags, String fullCodePath, String fullResourcePath,
+            String nativeLibraryPath) {
+        final boolean isInAsec;
+        if (installOnSd(flags)) {
+            /* Apps on SD card are always in ASEC containers. */
+            isInAsec = true;
+        } else if (installForwardLocked(flags)
+                && !fullCodePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
+            /*
+             * Forward-locked apps are only in ASEC containers if they're the
+             * new style
+             */
+            isInAsec = true;
+        } else {
+            isInAsec = false;
+        }
+
+        if (isInAsec) {
+            return new AsecInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath,
+                    installOnSd(flags), installForwardLocked(flags));
+        } else {
+            return new FileInstallArgs(fullCodePath, fullResourcePath, nativeLibraryPath);
+        }
+    }
+
+    // Used by package mover
+    private InstallArgs createInstallArgs(Uri packageURI, int flags, String pkgName, String dataDir) {
+        if (installOnSd(flags) || installForwardLocked(flags)) {
+            String cid = getNextCodePath(packageURI.getPath(), pkgName, "/"
+                    + AsecInstallArgs.RES_FILE_NAME);
+            return new AsecInstallArgs(packageURI, cid, installOnSd(flags),
+                    installForwardLocked(flags));
+        } else {
+            return new FileInstallArgs(packageURI, pkgName, dataDir);
+        }
+    }
+
+    static abstract class InstallArgs {
+        final IPackageInstallObserver observer;
+        // Always refers to PackageManager flags only
+        final int flags;
+        final Uri packageURI;
+        final String installerPackageName;
+        final ManifestDigest manifestDigest;
+        final UserHandle user;
+
+        InstallArgs(Uri packageURI, IPackageInstallObserver observer, int flags,
+                String installerPackageName, ManifestDigest manifestDigest,
+                UserHandle user) {
+            this.packageURI = packageURI;
+            this.flags = flags;
+            this.observer = observer;
+            this.installerPackageName = installerPackageName;
+            this.manifestDigest = manifestDigest;
+            this.user = user;
+        }
+
+        abstract void createCopyFile();
+        abstract int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException;
+        abstract int doPreInstall(int status);
+        abstract boolean doRename(int status, String pkgName, String oldCodePath);
+
+        abstract int doPostInstall(int status, int uid);
+        abstract String getCodePath();
+        abstract String getResourcePath();
+        abstract String getNativeLibraryPath();
+        // Need installer lock especially for dex file removal.
+        abstract void cleanUpResourcesLI();
+        abstract boolean doPostDeleteLI(boolean delete);
+        abstract boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException;
+
+        /**
+         * Called before the source arguments are copied. This is used mostly
+         * for MoveParams when it needs to read the source file to put it in the
+         * destination.
+         */
+        int doPreCopy() {
+            return PackageManager.INSTALL_SUCCEEDED;
+        }
+
+        /**
+         * Called after the source arguments are copied. This is used mostly for
+         * MoveParams when it needs to read the source file to put it in the
+         * destination.
+         *
+         * @return
+         */
+        int doPostCopy(int uid) {
+            return PackageManager.INSTALL_SUCCEEDED;
+        }
+
+        protected boolean isFwdLocked() {
+            return (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
+        }
+
+        UserHandle getUser() {
+            return user;
+        }
+    }
+
+    class FileInstallArgs extends InstallArgs {
+        File installDir;
+        String codeFileName;
+        String resourceFileName;
+        String libraryPath;
+        boolean created = false;
+
+        FileInstallArgs(InstallParams params) {
+            super(params.getPackageUri(), params.observer, params.flags,
+                    params.installerPackageName, params.getManifestDigest(),
+                    params.getUser());
+        }
+
+        FileInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath) {
+            super(null, null, 0, null, null, null);
+            File codeFile = new File(fullCodePath);
+            installDir = codeFile.getParentFile();
+            codeFileName = fullCodePath;
+            resourceFileName = fullResourcePath;
+            libraryPath = nativeLibraryPath;
+        }
+
+        FileInstallArgs(Uri packageURI, String pkgName, String dataDir) {
+            super(packageURI, null, 0, null, null, null);
+            installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
+            String apkName = getNextCodePath(null, pkgName, ".apk");
+            codeFileName = new File(installDir, apkName + ".apk").getPath();
+            resourceFileName = getResourcePathFromCodePath();
+            libraryPath = new File(mAppLibInstallDir, pkgName).getPath();
+        }
+
+        boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
+            final long lowThreshold;
+
+            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;
+            } else {
+                if (dsm.isMemoryLow()) {
+                    Log.w(TAG, "Memory is reported as being too low; aborting package install");
+                    return false;
+                }
+
+                lowThreshold = dsm.getMemoryLowThreshold();
+            }
+
+            try {
+                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                return imcs.checkInternalFreeStorage(packageURI, isFwdLocked(), lowThreshold);
+            } finally {
+                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            }
+        }
+
+        String getCodePath() {
+            return codeFileName;
+        }
+
+        void createCopyFile() {
+            installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
+            codeFileName = createTempPackageFile(installDir).getPath();
+            resourceFileName = getResourcePathFromCodePath();
+            libraryPath = getLibraryPathFromCodePath();
+            created = true;
+        }
+
+        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
+            if (temp) {
+                // Generate temp file name
+                createCopyFile();
+            }
+            // Get a ParcelFileDescriptor to write to the output file
+            File codeFile = new File(codeFileName);
+            if (!created) {
+                try {
+                    codeFile.createNewFile();
+                    // Set permissions
+                    if (!setPermissions()) {
+                        // Failed setting permissions.
+                        return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                    }
+                } catch (IOException e) {
+                   Slog.w(TAG, "Failed to create file " + codeFile);
+                   return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                }
+            }
+            ParcelFileDescriptor out = null;
+            try {
+                out = ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE);
+            } catch (FileNotFoundException e) {
+                Slog.e(TAG, "Failed to create file descriptor for : " + codeFileName);
+                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            }
+            // Copy the resource now
+            int ret = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            try {
+                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                ret = imcs.copyResource(packageURI, null, out);
+            } finally {
+                IoUtils.closeQuietly(out);
+                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            }
+
+            if (isFwdLocked()) {
+                final File destResourceFile = new File(getResourcePath());
+
+                // Copy the public files
+                try {
+                    PackageHelper.extractPublicFiles(codeFileName, destResourceFile);
+                } catch (IOException e) {
+                    Slog.e(TAG, "Couldn't create a new zip file for the public parts of a"
+                            + " forward-locked app.");
+                    destResourceFile.delete();
+                    return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                }
+            }
+
+            final File nativeLibraryFile = new File(getNativeLibraryPath());
+            Slog.i(TAG, "Copying native libraries to " + nativeLibraryFile.getPath());
+            if (nativeLibraryFile.exists()) {
+                NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
+                nativeLibraryFile.delete();
+            }
+            try {
+                int copyRet = copyNativeLibrariesForInternalApp(codeFile, nativeLibraryFile);
+                if (copyRet != PackageManager.INSTALL_SUCCEEDED) {
+                    return copyRet;
+                }
+            } catch (IOException e) {
+                Slog.e(TAG, "Copying native libraries failed", e);
+                ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
+            }
+
+            return ret;
+        }
+
+        int doPreInstall(int status) {
+            if (status != PackageManager.INSTALL_SUCCEEDED) {
+                cleanUp();
+            }
+            return status;
+        }
+
+        boolean doRename(int status, final String pkgName, String oldCodePath) {
+            if (status != PackageManager.INSTALL_SUCCEEDED) {
+                cleanUp();
+                return false;
+            } else {
+                final File oldCodeFile = new File(getCodePath());
+                final File oldResourceFile = new File(getResourcePath());
+                final File oldLibraryFile = new File(getNativeLibraryPath());
+
+                // Rename APK file based on packageName
+                final String apkName = getNextCodePath(oldCodePath, pkgName, ".apk");
+                final File newCodeFile = new File(installDir, apkName + ".apk");
+                if (!oldCodeFile.renameTo(newCodeFile)) {
+                    return false;
+                }
+                codeFileName = newCodeFile.getPath();
+
+                // Rename public resource file if it's forward-locked.
+                final File newResFile = new File(getResourcePathFromCodePath());
+                if (isFwdLocked() && !oldResourceFile.renameTo(newResFile)) {
+                    return false;
+                }
+                resourceFileName = newResFile.getPath();
+
+                // Rename library path
+                final File newLibraryFile = new File(getLibraryPathFromCodePath());
+                if (newLibraryFile.exists()) {
+                    NativeLibraryHelper.removeNativeBinariesFromDirLI(newLibraryFile);
+                    newLibraryFile.delete();
+                }
+                if (!oldLibraryFile.renameTo(newLibraryFile)) {
+                    Slog.e(TAG, "Cannot rename native library directory "
+                            + oldLibraryFile.getPath() + " to " + newLibraryFile.getPath());
+                    return false;
+                }
+                libraryPath = newLibraryFile.getPath();
+
+                // Attempt to set permissions
+                if (!setPermissions()) {
+                    return false;
+                }
+
+                if (!SELinux.restorecon(newCodeFile)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+
+        int doPostInstall(int status, int uid) {
+            if (status != PackageManager.INSTALL_SUCCEEDED) {
+                cleanUp();
+            }
+            return status;
+        }
+
+        String getResourcePath() {
+            return resourceFileName;
+        }
+
+        private String getResourcePathFromCodePath() {
+            final String codePath = getCodePath();
+            if (isFwdLocked()) {
+                final StringBuilder sb = new StringBuilder();
+
+                sb.append(mAppInstallDir.getPath());
+                sb.append('/');
+                sb.append(getApkName(codePath));
+                sb.append(".zip");
+
+                /*
+                 * If our APK is a temporary file, mark the resource as a
+                 * temporary file as well so it can be cleaned up after
+                 * catastrophic failure.
+                 */
+                if (codePath.endsWith(".tmp")) {
+                    sb.append(".tmp");
+                }
+
+                return sb.toString();
+            } else {
+                return codePath;
+            }
+        }
+
+        private String getLibraryPathFromCodePath() {
+            return new File(mAppLibInstallDir, getApkName(getCodePath())).getPath();
+        }
+
+        @Override
+        String getNativeLibraryPath() {
+            if (libraryPath == null) {
+                libraryPath = getLibraryPathFromCodePath();
+            }
+            return libraryPath;
+        }
+
+        private boolean cleanUp() {
+            boolean ret = true;
+            String sourceDir = getCodePath();
+            String publicSourceDir = getResourcePath();
+            if (sourceDir != null) {
+                File sourceFile = new File(sourceDir);
+                if (!sourceFile.exists()) {
+                    Slog.w(TAG, "Package source " + sourceDir + " does not exist.");
+                    ret = false;
+                }
+                // Delete application's code and resources
+                sourceFile.delete();
+            }
+            if (publicSourceDir != null && !publicSourceDir.equals(sourceDir)) {
+                final File publicSourceFile = new File(publicSourceDir);
+                if (!publicSourceFile.exists()) {
+                    Slog.w(TAG, "Package public source " + publicSourceFile + " does not exist.");
+                }
+                if (publicSourceFile.exists()) {
+                    publicSourceFile.delete();
+                }
+            }
+
+            if (libraryPath != null) {
+                File nativeLibraryFile = new File(libraryPath);
+                NativeLibraryHelper.removeNativeBinariesFromDirLI(nativeLibraryFile);
+                if (!nativeLibraryFile.delete()) {
+                    Slog.w(TAG, "Couldn't delete native library directory " + libraryPath);
+                }
+            }
+
+            return ret;
+        }
+
+        void cleanUpResourcesLI() {
+            String sourceDir = getCodePath();
+            if (cleanUp()) {
+                int retCode = mInstaller.rmdex(sourceDir);
+                if (retCode < 0) {
+                    Slog.w(TAG, "Couldn't remove dex file for package: "
+                            +  " at location "
+                            + sourceDir + ", retcode=" + retCode);
+                    // we don't consider this to be a failure of the core package deletion
+                }
+            }
+        }
+
+        private boolean setPermissions() {
+            // TODO Do this in a more elegant way later on. for now just a hack
+            if (!isFwdLocked()) {
+                final int filePermissions =
+                    FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IRGRP
+                    |FileUtils.S_IROTH;
+                int retCode = FileUtils.setPermissions(getCodePath(), filePermissions, -1, -1);
+                if (retCode != 0) {
+                    Slog.e(TAG, "Couldn't set new package file permissions for " +
+                            getCodePath()
+                            + ". The return code was: " + retCode);
+                    // TODO Define new internal error
+                    return false;
+                }
+                return true;
+            }
+            return true;
+        }
+
+        boolean doPostDeleteLI(boolean delete) {
+            // XXX err, shouldn't we respect the delete flag?
+            cleanUpResourcesLI();
+            return true;
+        }
+    }
+
+    private boolean isAsecExternal(String cid) {
+        final String asecPath = PackageHelper.getSdFilesystem(cid);
+        return !asecPath.startsWith(mAsecInternalPath);
+    }
+
+    /**
+     * Extract the MountService "container ID" from the full code path of an
+     * .apk.
+     */
+    static String cidFromCodePath(String fullCodePath) {
+        int eidx = fullCodePath.lastIndexOf("/");
+        String subStr1 = fullCodePath.substring(0, eidx);
+        int sidx = subStr1.lastIndexOf("/");
+        return subStr1.substring(sidx+1, eidx);
+    }
+
+    class AsecInstallArgs extends InstallArgs {
+        static final String RES_FILE_NAME = "pkg.apk";
+        static final String PUBLIC_RES_FILE_NAME = "res.zip";
+
+        String cid;
+        String packagePath;
+        String resourcePath;
+        String libraryPath;
+
+        AsecInstallArgs(InstallParams params) {
+            super(params.getPackageUri(), params.observer, params.flags,
+                    params.installerPackageName, params.getManifestDigest(),
+                    params.getUser());
+        }
+
+        AsecInstallArgs(String fullCodePath, String fullResourcePath, String nativeLibraryPath,
+                boolean isExternal, boolean isForwardLocked) {
+            super(null, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
+                    | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
+                    null, null, null);
+            // Extract cid from fullCodePath
+            int eidx = fullCodePath.lastIndexOf("/");
+            String subStr1 = fullCodePath.substring(0, eidx);
+            int sidx = subStr1.lastIndexOf("/");
+            cid = subStr1.substring(sidx+1, eidx);
+            setCachePath(subStr1);
+        }
+
+        AsecInstallArgs(String cid, boolean isForwardLocked) {
+            super(null, null, (isAsecExternal(cid) ? PackageManager.INSTALL_EXTERNAL : 0)
+                    | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
+                    null, null, null);
+            this.cid = cid;
+            setCachePath(PackageHelper.getSdDir(cid));
+        }
+
+        AsecInstallArgs(Uri packageURI, String cid, boolean isExternal, boolean isForwardLocked) {
+            super(packageURI, null, (isExternal ? PackageManager.INSTALL_EXTERNAL : 0)
+                    | (isForwardLocked ? PackageManager.INSTALL_FORWARD_LOCK : 0),
+                    null, null, null);
+            this.cid = cid;
+        }
+
+        void createCopyFile() {
+            cid = getTempContainerId();
+        }
+
+        boolean checkFreeStorage(IMediaContainerService imcs) throws RemoteException {
+            try {
+                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                return imcs.checkExternalFreeStorage(packageURI, isFwdLocked());
+            } finally {
+                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            }
+        }
+
+        private final boolean isExternal() {
+            return (flags & PackageManager.INSTALL_EXTERNAL) != 0;
+        }
+
+        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
+            if (temp) {
+                createCopyFile();
+            } else {
+                /*
+                 * Pre-emptively destroy the container since it's destroyed if
+                 * copying fails due to it existing anyway.
+                 */
+                PackageHelper.destroySdDir(cid);
+            }
+
+            final String newCachePath;
+            try {
+                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,
+                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                newCachePath = imcs.copyResourceToContainer(packageURI, cid, getEncryptKey(),
+                        RES_FILE_NAME, PUBLIC_RES_FILE_NAME, isExternal(), isFwdLocked());
+            } finally {
+                mContext.revokeUriPermission(packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            }
+
+            if (newCachePath != null) {
+                setCachePath(newCachePath);
+                return PackageManager.INSTALL_SUCCEEDED;
+            } else {
+                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+            }
+        }
+
+        @Override
+        String getCodePath() {
+            return packagePath;
+        }
+
+        @Override
+        String getResourcePath() {
+            return resourcePath;
+        }
+
+        @Override
+        String getNativeLibraryPath() {
+            return libraryPath;
+        }
+
+        int doPreInstall(int status) {
+            if (status != PackageManager.INSTALL_SUCCEEDED) {
+                // Destroy container
+                PackageHelper.destroySdDir(cid);
+            } else {
+                boolean mounted = PackageHelper.isContainerMounted(cid);
+                if (!mounted) {
+                    String newCachePath = PackageHelper.mountSdDir(cid, getEncryptKey(),
+                            Process.SYSTEM_UID);
+                    if (newCachePath != null) {
+                        setCachePath(newCachePath);
+                    } else {
+                        return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+                    }
+                }
+            }
+            return status;
+        }
+
+        boolean doRename(int status, final String pkgName,
+                String oldCodePath) {
+            String newCacheId = getNextCodePath(oldCodePath, pkgName, "/" + RES_FILE_NAME);
+            String newCachePath = null;
+            if (PackageHelper.isContainerMounted(cid)) {
+                // Unmount the container
+                if (!PackageHelper.unMountSdDir(cid)) {
+                    Slog.i(TAG, "Failed to unmount " + cid + " before renaming");
+                    return false;
+                }
+            }
+            if (!PackageHelper.renameSdDir(cid, newCacheId)) {
+                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
+                        " which might be stale. Will try to clean up.");
+                // Clean up the stale container and proceed to recreate.
+                if (!PackageHelper.destroySdDir(newCacheId)) {
+                    Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
+                    return false;
+                }
+                // Successfully cleaned up stale container. Try to rename again.
+                if (!PackageHelper.renameSdDir(cid, newCacheId)) {
+                    Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
+                            + " inspite of cleaning it up.");
+                    return false;
+                }
+            }
+            if (!PackageHelper.isContainerMounted(newCacheId)) {
+                Slog.w(TAG, "Mounting container " + newCacheId);
+                newCachePath = PackageHelper.mountSdDir(newCacheId,
+                        getEncryptKey(), Process.SYSTEM_UID);
+            } else {
+                newCachePath = PackageHelper.getSdDir(newCacheId);
+            }
+            if (newCachePath == null) {
+                Slog.w(TAG, "Failed to get cache path for  " + newCacheId);
+                return false;
+            }
+            Log.i(TAG, "Succesfully renamed " + cid +
+                    " to " + newCacheId +
+                    " at new path: " + newCachePath);
+            cid = newCacheId;
+            setCachePath(newCachePath);
+            return true;
+        }
+
+        private void setCachePath(String newCachePath) {
+            File cachePath = new File(newCachePath);
+            libraryPath = new File(cachePath, LIB_DIR_NAME).getPath();
+            packagePath = new File(cachePath, RES_FILE_NAME).getPath();
+
+            if (isFwdLocked()) {
+                resourcePath = new File(cachePath, PUBLIC_RES_FILE_NAME).getPath();
+            } else {
+                resourcePath = packagePath;
+            }
+        }
+
+        int doPostInstall(int status, int uid) {
+            if (status != PackageManager.INSTALL_SUCCEEDED) {
+                cleanUp();
+            } else {
+                final int groupOwner;
+                final String protectedFile;
+                if (isFwdLocked()) {
+                    groupOwner = UserHandle.getSharedAppGid(uid);
+                    protectedFile = RES_FILE_NAME;
+                } else {
+                    groupOwner = -1;
+                    protectedFile = null;
+                }
+
+                if (uid < Process.FIRST_APPLICATION_UID
+                        || !PackageHelper.fixSdPermissions(cid, groupOwner, protectedFile)) {
+                    Slog.e(TAG, "Failed to finalize " + cid);
+                    PackageHelper.destroySdDir(cid);
+                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+                }
+
+                boolean mounted = PackageHelper.isContainerMounted(cid);
+                if (!mounted) {
+                    PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid());
+                }
+            }
+            return status;
+        }
+
+        private void cleanUp() {
+            if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp");
+
+            // Destroy secure container
+            PackageHelper.destroySdDir(cid);
+        }
+
+        void cleanUpResourcesLI() {
+            String sourceFile = getCodePath();
+            // Remove dex file
+            int retCode = mInstaller.rmdex(sourceFile);
+            if (retCode < 0) {
+                Slog.w(TAG, "Couldn't remove dex file for package: "
+                        + " at location "
+                        + sourceFile.toString() + ", retcode=" + retCode);
+                // we don't consider this to be a failure of the core package deletion
+            }
+            cleanUp();
+        }
+
+        boolean matchContainer(String app) {
+            if (cid.startsWith(app)) {
+                return true;
+            }
+            return false;
+        }
+
+        String getPackageName() {
+            return getAsecPackageName(cid);
+        }
+
+        boolean doPostDeleteLI(boolean delete) {
+            boolean ret = false;
+            boolean mounted = PackageHelper.isContainerMounted(cid);
+            if (mounted) {
+                // Unmount first
+                ret = PackageHelper.unMountSdDir(cid);
+            }
+            if (ret && delete) {
+                cleanUpResourcesLI();
+            }
+            return ret;
+        }
+
+        @Override
+        int doPreCopy() {
+            if (isFwdLocked()) {
+                if (!PackageHelper.fixSdPermissions(cid,
+                        getPackageUid(DEFAULT_CONTAINER_PACKAGE, 0), RES_FILE_NAME)) {
+                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+                }
+            }
+
+            return PackageManager.INSTALL_SUCCEEDED;
+        }
+
+        @Override
+        int doPostCopy(int uid) {
+            if (isFwdLocked()) {
+                if (uid < Process.FIRST_APPLICATION_UID
+                        || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid),
+                                RES_FILE_NAME)) {
+                    Slog.e(TAG, "Failed to finalize " + cid);
+                    PackageHelper.destroySdDir(cid);
+                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+                }
+            }
+
+            return PackageManager.INSTALL_SUCCEEDED;
+        }
+    };
+
+    static String getAsecPackageName(String packageCid) {
+        int idx = packageCid.lastIndexOf("-");
+        if (idx == -1) {
+            return packageCid;
+        }
+        return packageCid.substring(0, idx);
+    }
+
+    // Utility method used to create code paths based on package name and available index.
+    private static String getNextCodePath(String oldCodePath, String prefix, String suffix) {
+        String idxStr = "";
+        int idx = 1;
+        // Fall back to default value of idx=1 if prefix is not
+        // part of oldCodePath
+        if (oldCodePath != null) {
+            String subStr = oldCodePath;
+            // Drop the suffix right away
+            if (subStr.endsWith(suffix)) {
+                subStr = subStr.substring(0, subStr.length() - suffix.length());
+            }
+            // If oldCodePath already contains prefix find out the
+            // ending index to either increment or decrement.
+            int sidx = subStr.lastIndexOf(prefix);
+            if (sidx != -1) {
+                subStr = subStr.substring(sidx + prefix.length());
+                if (subStr != null) {
+                    if (subStr.startsWith(INSTALL_PACKAGE_SUFFIX)) {
+                        subStr = subStr.substring(INSTALL_PACKAGE_SUFFIX.length());
+                    }
+                    try {
+                        idx = Integer.parseInt(subStr);
+                        if (idx <= 1) {
+                            idx++;
+                        } else {
+                            idx--;
+                        }
+                    } catch(NumberFormatException e) {
+                    }
+                }
+            }
+        }
+        idxStr = INSTALL_PACKAGE_SUFFIX + Integer.toString(idx);
+        return prefix + idxStr;
+    }
+
+    // Utility method used to ignore ADD/REMOVE events
+    // by directory observer.
+    private static boolean ignoreCodePath(String fullPathStr) {
+        String apkName = getApkName(fullPathStr);
+        int idx = apkName.lastIndexOf(INSTALL_PACKAGE_SUFFIX);
+        if (idx != -1 && ((idx+1) < apkName.length())) {
+            // Make sure the package ends with a numeral
+            String version = apkName.substring(idx+1);
+            try {
+                Integer.parseInt(version);
+                return true;
+            } catch (NumberFormatException e) {}
+        }
+        return false;
+    }
+    
+    // Utility method that returns the relative package path with respect
+    // to the installation directory. Like say for /data/data/com.test-1.apk
+    // string com.test-1 is returned.
+    static String getApkName(String codePath) {
+        if (codePath == null) {
+            return null;
+        }
+        int sidx = codePath.lastIndexOf("/");
+        int eidx = codePath.lastIndexOf(".");
+        if (eidx == -1) {
+            eidx = codePath.length();
+        } else if (eidx == 0) {
+            Slog.w(TAG, " Invalid code path, "+ codePath + " Not a valid apk name");
+            return null;
+        }
+        return codePath.substring(sidx+1, eidx);
+    }
+
+    class PackageInstalledInfo {
+        String name;
+        int uid;
+        // The set of users that originally had this package installed.
+        int[] origUsers;
+        // The set of users that now have this package installed.
+        int[] newUsers;
+        PackageParser.Package pkg;
+        int returnCode;
+        PackageRemovedInfo removedInfo;
+    }
+
+    /*
+     * Install a non-existing package.
+     */
+    private void installNewPackageLI(PackageParser.Package pkg,
+            int parseFlags, int scanMode, UserHandle user,
+            String installerPackageName, PackageInstalledInfo res) {
+        // Remember this for later, in case we need to rollback this install
+        String pkgName = pkg.packageName;
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg);
+        boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists();
+        synchronized(mPackages) {
+            if (mSettings.mRenamedPackages.containsKey(pkgName)) {
+                // A package with the same name is already installed, though
+                // it has been renamed to an older name.  The package we
+                // are trying to install should be installed as an update to
+                // the existing one, but that has not been requested, so bail.
+                Slog.w(TAG, "Attempt to re-install " + pkgName
+                        + " without first uninstalling package running as "
+                        + mSettings.mRenamedPackages.get(pkgName));
+                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+                return;
+            }
+            if (mPackages.containsKey(pkgName) || mAppDirs.containsKey(pkg.mPath)) {
+                // Don't allow installation over an existing package with the same name.
+                Slog.w(TAG, "Attempt to re-install " + pkgName
+                        + " without first uninstalling.");
+                res.returnCode = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
+                return;
+            }
+        }
+        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
+                System.currentTimeMillis(), user);
+        if (newPackage == null) {
+            Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
+            if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
+                res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
+            }
+        } else {
+            updateSettingsLI(newPackage,
+                    installerPackageName,
+                    null, null,
+                    res);
+            // delete the partially installed application. the data directory will have to be
+            // restored if it was already existing
+            if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+                // remove package from internal structures.  Note that we want deletePackageX to
+                // delete the package data and cache directories that it created in
+                // scanPackageLocked, unless those directories existed before we even tried to
+                // install.
+                deletePackageLI(pkgName, UserHandle.ALL, false, null, null,
+                        dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
+                                res.removedInfo, true);
+            }
+        }
+    }
+
+    private void replacePackageLI(PackageParser.Package pkg,
+            int parseFlags, int scanMode, UserHandle user,
+            String installerPackageName, PackageInstalledInfo res) {
+
+        PackageParser.Package oldPackage;
+        String pkgName = pkg.packageName;
+        int[] allUsers;
+        boolean[] perUserInstalled;
+
+        // First find the old package info and check signatures
+        synchronized(mPackages) {
+            oldPackage = mPackages.get(pkgName);
+            if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage);
+            if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures)
+                    != PackageManager.SIGNATURE_MATCH) {
+                Slog.w(TAG, "New package has a different signature: " + pkgName);
+                res.returnCode = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
+                return;
+            }
+
+            // In case of rollback, remember per-user/profile install state
+            PackageSetting ps = mSettings.mPackages.get(pkgName);
+            allUsers = sUserManager.getUserIds();
+            perUserInstalled = new boolean[allUsers.length];
+            for (int i = 0; i < allUsers.length; i++) {
+                perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
+            }
+        }
+        boolean sysPkg = (isSystemApp(oldPackage));
+        if (sysPkg) {
+            replaceSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
+                    user, allUsers, perUserInstalled, installerPackageName, res);
+        } else {
+            replaceNonSystemPackageLI(oldPackage, pkg, parseFlags, scanMode,
+                    user, allUsers, perUserInstalled, installerPackageName, res);
+        }
+    }
+
+    private void replaceNonSystemPackageLI(PackageParser.Package deletedPackage,
+            PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
+            int[] allUsers, boolean[] perUserInstalled,
+            String installerPackageName, PackageInstalledInfo res) {
+        PackageParser.Package newPackage = null;
+        String pkgName = deletedPackage.packageName;
+        boolean deletedPkg = true;
+        boolean updatedSettings = false;
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
+                + deletedPackage);
+        long origUpdateTime;
+        if (pkg.mExtras != null) {
+            origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime;
+        } else {
+            origUpdateTime = 0;
+        }
+
+        // First delete the existing package while retaining the data directory
+        if (!deletePackageLI(pkgName, null, true, null, null, PackageManager.DELETE_KEEP_DATA,
+                res.removedInfo, true)) {
+            // If the existing package wasn't successfully deleted
+            res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+            deletedPkg = false;
+        } else {
+            // Successfully deleted the old package. Now proceed with re-installation
+            mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+            newPackage = scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_TIME,
+                    System.currentTimeMillis(), user);
+            if (newPackage == null) {
+                Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
+                if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
+                    res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
+                }
+            } else {
+                updateSettingsLI(newPackage,
+                        installerPackageName,
+                        allUsers, perUserInstalled,
+                        res);
+                updatedSettings = true;
+            }
+        }
+
+        if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+            // remove package from internal structures.  Note that we want deletePackageX to
+            // delete the package data and cache directories that it created in
+            // scanPackageLocked, unless those directories existed before we even tried to
+            // install.
+            if(updatedSettings) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName);
+                deletePackageLI(
+                        pkgName, null, true, allUsers, perUserInstalled,
+                        PackageManager.DELETE_KEEP_DATA,
+                                res.removedInfo, true);
+            }
+            // Since we failed to install the new package we need to restore the old
+            // package that we deleted.
+            if(deletedPkg) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage);
+                File restoreFile = new File(deletedPackage.mPath);
+                // Parse old package
+                boolean oldOnSd = isExternal(deletedPackage);
+                int oldParseFlags  = mDefParseFlags | PackageParser.PARSE_CHATTY |
+                        (isForwardLocked(deletedPackage) ? PackageParser.PARSE_FORWARD_LOCK : 0) |
+                        (oldOnSd ? PackageParser.PARSE_ON_SDCARD : 0);
+                int oldScanMode = (oldOnSd ? 0 : SCAN_MONITOR) | SCAN_UPDATE_SIGNATURE
+                        | SCAN_UPDATE_TIME;
+                if (scanPackageLI(restoreFile, oldParseFlags, oldScanMode,
+                        origUpdateTime, null) == null) {
+                    Slog.e(TAG, "Failed to restore package : " + pkgName + " after failed upgrade");
+                    return;
+                }
+                // Restore of old package succeeded. Update permissions.
+                // writer
+                synchronized (mPackages) {
+                    updatePermissionsLPw(deletedPackage.packageName, deletedPackage,
+                            UPDATE_PERMISSIONS_ALL);
+                    // can downgrade to reader
+                    mSettings.writeLPr();
+                }
+                Slog.i(TAG, "Successfully restored package : " + pkgName + " after failed upgrade");
+            }
+        }
+    }
+
+    private void replaceSystemPackageLI(PackageParser.Package deletedPackage,
+            PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user,
+            int[] allUsers, boolean[] perUserInstalled,
+            String installerPackageName, PackageInstalledInfo res) {
+        if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
+                + ", old=" + deletedPackage);
+        PackageParser.Package newPackage = null;
+        boolean updatedSettings = false;
+        parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING |
+                PackageParser.PARSE_IS_SYSTEM;
+        if ((deletedPackage.applicationInfo.flags&ApplicationInfo.FLAG_PRIVILEGED) != 0) {
+            parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+        }
+        String packageName = deletedPackage.packageName;
+        res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
+        if (packageName == null) {
+            Slog.w(TAG, "Attempt to delete null packageName.");
+            return;
+        }
+        PackageParser.Package oldPkg;
+        PackageSetting oldPkgSetting;
+        // reader
+        synchronized (mPackages) {
+            oldPkg = mPackages.get(packageName);
+            oldPkgSetting = mSettings.mPackages.get(packageName);
+            if((oldPkg == null) || (oldPkg.applicationInfo == null) ||
+                    (oldPkgSetting == null)) {
+                Slog.w(TAG, "Couldn't find package:"+packageName+" information");
+                return;
+            }
+        }
+
+        killApplication(packageName, oldPkg.applicationInfo.uid, "replace sys pkg");
+
+        res.removedInfo.uid = oldPkg.applicationInfo.uid;
+        res.removedInfo.removedPackage = packageName;
+        // Remove existing system package
+        removePackageLI(oldPkgSetting, true);
+        // writer
+        synchronized (mPackages) {
+            if (!mSettings.disableSystemPackageLPw(packageName) && deletedPackage != null) {
+                // We didn't need to disable the .apk as a current system package,
+                // which means we are replacing another update that is already
+                // installed.  We need to make sure to delete the older one's .apk.
+                res.removedInfo.args = createInstallArgs(0,
+                        deletedPackage.applicationInfo.sourceDir,
+                        deletedPackage.applicationInfo.publicSourceDir,
+                        deletedPackage.applicationInfo.nativeLibraryDir);
+            } else {
+                res.removedInfo.args = null;
+            }
+        }
+        
+        // Successfully disabled the old package. Now proceed with re-installation
+        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
+        pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+        newPackage = scanPackageLI(pkg, parseFlags, scanMode, 0, user);
+        if (newPackage == null) {
+            Slog.w(TAG, "Package couldn't be installed in " + pkg.mPath);
+            if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) {
+                res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK;
+            }
+        } else {
+            if (newPackage.mExtras != null) {
+                final PackageSetting newPkgSetting = (PackageSetting)newPackage.mExtras;
+                newPkgSetting.firstInstallTime = oldPkgSetting.firstInstallTime;
+                newPkgSetting.lastUpdateTime = System.currentTimeMillis();
+            }
+            updateSettingsLI(newPackage, installerPackageName, allUsers, perUserInstalled, res);
+            updatedSettings = true;
+        }
+
+        if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
+            // Re installation failed. Restore old information
+            // Remove new pkg information
+            if (newPackage != null) {
+                removeInstalledPackageLI(newPackage, true);
+            }
+            // Add back the old system package
+            scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user);
+            // Restore the old system information in Settings
+            synchronized(mPackages) {
+                if (updatedSettings) {
+                    mSettings.enableSystemPackageLPw(packageName);
+                    mSettings.setInstallerPackageName(packageName,
+                            oldPkgSetting.installerPackageName);
+                }
+                mSettings.writeLPr();
+            }
+        }
+    }
+
+    // Utility method used to move dex files during install.
+    private int moveDexFilesLI(PackageParser.Package newPackage) {
+        int retCode;
+        if ((newPackage.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
+            retCode = mInstaller.movedex(newPackage.mScanPath, newPackage.mPath);
+            if (retCode != 0) {
+                if (mNoDexOpt) {
+                    /*
+                     * If we're in an engineering build, programs are lazily run
+                     * through dexopt. If the .dex file doesn't exist yet, it
+                     * will be created when the program is run next.
+                     */
+                    Slog.i(TAG, "dex file doesn't exist, skipping move: " + newPackage.mPath);
+                } else {
+                    Slog.e(TAG, "Couldn't rename dex file: " + newPackage.mPath);
+                    return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+                }
+            }
+        }
+        return PackageManager.INSTALL_SUCCEEDED;
+    }
+
+    private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
+            int[] allUsers, boolean[] perUserInstalled,
+            PackageInstalledInfo res) {
+        String pkgName = newPackage.packageName;
+        synchronized (mPackages) {
+            //write settings. the installStatus will be incomplete at this stage.
+            //note that the new package setting would have already been
+            //added to mPackages. It hasn't been persisted yet.
+            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
+            mSettings.writeLPr();
+        }
+
+        if ((res.returnCode = moveDexFilesLI(newPackage))
+                != PackageManager.INSTALL_SUCCEEDED) {
+            // Discontinue if moving dex files failed.
+            return;
+        }
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath);
+
+        synchronized (mPackages) {
+            updatePermissionsLPw(newPackage.packageName, newPackage,
+                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
+                            ? UPDATE_PERMISSIONS_ALL : 0));
+            // For system-bundled packages, we assume that installing an upgraded version
+            // of the package implies that the user actually wants to run that new code,
+            // so we enable the package.
+            if (isSystemApp(newPackage)) {
+                // NB: implicit assumption that system package upgrades apply to all users
+                if (DEBUG_INSTALL) {
+                    Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
+                }
+                PackageSetting ps = mSettings.mPackages.get(pkgName);
+                if (ps != null) {
+                    if (res.origUsers != null) {
+                        for (int userHandle : res.origUsers) {
+                            ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT,
+                                    userHandle, installerPackageName);
+                        }
+                    }
+                    // Also convey the prior install/uninstall state
+                    if (allUsers != null && perUserInstalled != null) {
+                        for (int i = 0; i < allUsers.length; i++) {
+                            if (DEBUG_INSTALL) {
+                                Slog.d(TAG, "    user " + allUsers[i]
+                                        + " => " + perUserInstalled[i]);
+                            }
+                            ps.setInstalled(perUserInstalled[i], allUsers[i]);
+                        }
+                        // these install state changes will be persisted in the
+                        // upcoming call to mSettings.writeLPr().
+                    }
+                }
+            }
+            res.name = pkgName;
+            res.uid = newPackage.applicationInfo.uid;
+            res.pkg = newPackage;
+            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_COMPLETE);
+            mSettings.setInstallerPackageName(pkgName, installerPackageName);
+            res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+            //to update install status
+            mSettings.writeLPr();
+        }
+    }
+
+    private void installPackageLI(InstallArgs args,
+            boolean newInstall, PackageInstalledInfo res) {
+        int pFlags = args.flags;
+        String installerPackageName = args.installerPackageName;
+        File tmpPackageFile = new File(args.getCodePath());
+        boolean forwardLocked = ((pFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
+        boolean onSd = ((pFlags & PackageManager.INSTALL_EXTERNAL) != 0);
+        boolean replace = false;
+        int scanMode = (onSd ? 0 : SCAN_MONITOR) | SCAN_FORCE_DEX | SCAN_UPDATE_SIGNATURE
+                | (newInstall ? SCAN_NEW_INSTALL : 0);
+        // Result object to be returned
+        res.returnCode = PackageManager.INSTALL_SUCCEEDED;
+
+        if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile);
+        // Retrieve PackageSettings and parse package
+        int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
+                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
+        PackageParser pp = new PackageParser(tmpPackageFile.getPath());
+        pp.setSeparateProcesses(mSeparateProcesses);
+        final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
+                null, mMetrics, parseFlags);
+        if (pkg == null) {
+            res.returnCode = pp.getParseError();
+            return;
+        }
+        String pkgName = res.name = pkg.packageName;
+        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_TEST_ONLY) != 0) {
+            if ((pFlags&PackageManager.INSTALL_ALLOW_TEST) == 0) {
+                res.returnCode = PackageManager.INSTALL_FAILED_TEST_ONLY;
+                return;
+            }
+        }
+        if (GET_CERTIFICATES && !pp.collectCertificates(pkg, parseFlags)) {
+            res.returnCode = pp.getParseError();
+            return;
+        }
+
+        /* If the installer passed in a manifest digest, compare it now. */
+        if (args.manifestDigest != null) {
+            if (DEBUG_INSTALL) {
+                final String parsedManifest = pkg.manifestDigest == null ? "null"
+                        : pkg.manifestDigest.toString();
+                Slog.d(TAG, "Comparing manifests: " + args.manifestDigest.toString() + " vs. "
+                        + parsedManifest);
+            }
+
+            if (!args.manifestDigest.equals(pkg.manifestDigest)) {
+                res.returnCode = PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
+                return;
+            }
+        } else if (DEBUG_INSTALL) {
+            final String parsedManifest = pkg.manifestDigest == null
+                    ? "null" : pkg.manifestDigest.toString();
+            Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest);
+        }
+
+        // Get rid of all references to package scan path via parser.
+        pp = null;
+        String oldCodePath = null;
+        boolean systemApp = false;
+        synchronized (mPackages) {
+            // Check if installing already existing package
+            if ((pFlags&PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
+                String oldName = mSettings.mRenamedPackages.get(pkgName);
+                if (pkg.mOriginalPackages != null
+                        && pkg.mOriginalPackages.contains(oldName)
+                        && mPackages.containsKey(oldName)) {
+                    // This package is derived from an original package,
+                    // and this device has been updating from that original
+                    // name.  We must continue using the original name, so
+                    // rename the new package here.
+                    pkg.setPackageName(oldName);
+                    pkgName = pkg.packageName;
+                    replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName="
+                            + oldName + " pkgName=" + pkgName);
+                } else if (mPackages.containsKey(pkgName)) {
+                    // This package, under its official name, already exists
+                    // on the device; we should replace it.
+                    replace = true;
+                    if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName);
+                }
+            }
+            PackageSetting ps = mSettings.mPackages.get(pkgName);
+            if (ps != null) {
+                if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
+                oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
+                if (ps.pkg != null && ps.pkg.applicationInfo != null) {
+                    systemApp = (ps.pkg.applicationInfo.flags &
+                            ApplicationInfo.FLAG_SYSTEM) != 0;
+                }
+                res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
+            }
+        }
+
+        if (systemApp && onSd) {
+            // Disable updates to system apps on sdcard
+            Slog.w(TAG, "Cannot install updates to system apps on sdcard");
+            res.returnCode = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
+            return;
+        }
+
+        if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
+            res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
+            return;
+        }
+        // Set application objects path explicitly after the rename
+        setApplicationInfoPaths(pkg, args.getCodePath(), args.getResourcePath());
+        pkg.applicationInfo.nativeLibraryDir = args.getNativeLibraryPath();
+        if (replace) {
+            replacePackageLI(pkg, parseFlags, scanMode, args.user,
+                    installerPackageName, res);
+        } else {
+            installNewPackageLI(pkg, parseFlags, scanMode, args.user,
+                    installerPackageName, res);
+        }
+        synchronized (mPackages) {
+            final PackageSetting ps = mSettings.mPackages.get(pkgName);
+            if (ps != null) {
+                res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
+            }
+        }
+    }
+
+    private static boolean isForwardLocked(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+    }
+
+
+    private boolean isForwardLocked(PackageSetting ps) {
+        return (ps.pkgFlags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0;
+    }
+
+    private static boolean isExternal(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
+    private static boolean isExternal(PackageSetting ps) {
+        return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
+    }
+
+    private static boolean isSystemApp(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    private static boolean isPrivilegedApp(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+    }
+
+    private static boolean isSystemApp(ApplicationInfo info) {
+        return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    private static boolean isSystemApp(PackageSetting ps) {
+        return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    private static boolean isUpdatedSystemApp(PackageSetting ps) {
+        return (ps.pkgFlags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
+    private static boolean isUpdatedSystemApp(PackageParser.Package pkg) {
+        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
+    }
+
+    private int packageFlagsToInstallFlags(PackageSetting ps) {
+        int installFlags = 0;
+        if (isExternal(ps)) {
+            installFlags |= PackageManager.INSTALL_EXTERNAL;
+        }
+        if (isForwardLocked(ps)) {
+            installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+        }
+        return installFlags;
+    }
+
+    private void deleteTempPackageFiles() {
+        final FilenameFilter filter = new FilenameFilter() {
+            public boolean accept(File dir, String name) {
+                return name.startsWith("vmdl") && name.endsWith(".tmp");
+            }
+        };
+        deleteTempPackageFilesInDirectory(mAppInstallDir, filter);
+        deleteTempPackageFilesInDirectory(mDrmAppPrivateInstallDir, filter);
+    }
+
+    private static final void deleteTempPackageFilesInDirectory(File directory,
+            FilenameFilter filter) {
+        final String[] tmpFilesList = directory.list(filter);
+        if (tmpFilesList == null) {
+            return;
+        }
+        for (int i = 0; i < tmpFilesList.length; i++) {
+            final File tmpFile = new File(directory, tmpFilesList[i]);
+            tmpFile.delete();
+        }
+    }
+
+    private File createTempPackageFile(File installDir) {
+        File tmpPackageFile;
+        try {
+            tmpPackageFile = File.createTempFile("vmdl", ".tmp", installDir);
+        } catch (IOException e) {
+            Slog.e(TAG, "Couldn't create temp file for downloaded package file.");
+            return null;
+        }
+        try {
+            FileUtils.setPermissions(
+                    tmpPackageFile.getCanonicalPath(), FileUtils.S_IRUSR|FileUtils.S_IWUSR,
+                    -1, -1);
+            if (!SELinux.restorecon(tmpPackageFile)) {
+                return null;
+            }
+        } catch (IOException e) {
+            Slog.e(TAG, "Trouble getting the canoncical path for a temp file.");
+            return null;
+        }
+        return tmpPackageFile;
+    }
+
+    @Override
+    public void deletePackageAsUser(final String packageName,
+                                    final IPackageDeleteObserver observer,
+                                    final int userId, final int flags) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.DELETE_PACKAGES, null);
+        final int uid = Binder.getCallingUid();
+        if (UserHandle.getUserId(uid) != userId) {
+            mContext.enforceCallingPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                    "deletePackage for user " + userId);
+        }
+        if (isUserRestricted(userId, UserManager.DISALLOW_UNINSTALL_APPS)) {
+            try {
+                observer.packageDeleted(packageName, PackageManager.DELETE_FAILED_USER_RESTRICTED);
+            } catch (RemoteException re) {
+            }
+            return;
+        }
+
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId);
+        // Queue up an async operation since the package deletion may take a little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                final int returnCode = deletePackageX(packageName, userId, flags);
+                if (observer != null) {
+                    try {
+                        observer.packageDeleted(packageName, returnCode);
+                    } catch (RemoteException e) {
+                        Log.i(TAG, "Observer no longer exists.");
+                    } //end catch
+                } //end if
+            } //end run
+        });
+    }
+
+    private boolean isPackageDeviceAdmin(String packageName, int userId) {
+        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
+                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
+        try {
+            if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId)
+                    || dpm.isDeviceOwner(packageName))) {
+                return true;
+            }
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    /**
+     *  This method is an internal method that could be get invoked either
+     *  to delete an installed package or to clean up a failed installation.
+     *  After deleting an installed package, a broadcast is sent to notify any
+     *  listeners that the package has been installed. For cleaning up a failed
+     *  installation, the broadcast is not necessary since the package's
+     *  installation wouldn't have sent the initial broadcast either
+     *  The key steps in deleting a package are
+     *  deleting the package information in internal structures like mPackages,
+     *  deleting the packages base directories through installd
+     *  updating mSettings to reflect current status
+     *  persisting settings for later use
+     *  sending a broadcast if necessary
+     */
+    private int deletePackageX(String packageName, int userId, int flags) {
+        final PackageRemovedInfo info = new PackageRemovedInfo();
+        final boolean res;
+
+        if (isPackageDeviceAdmin(packageName, userId)) {
+            Slog.w(TAG, "Not removing package " + packageName + ": has active device admin");
+            return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER;
+        }
+
+        boolean removedForAllUsers = false;
+        boolean systemUpdate = false;
+
+        // for the uninstall-updates case and restricted profiles, remember the per-
+        // userhandle installed state
+        int[] allUsers;
+        boolean[] perUserInstalled;
+        synchronized (mPackages) {
+            PackageSetting ps = mSettings.mPackages.get(packageName);
+            allUsers = sUserManager.getUserIds();
+            perUserInstalled = new boolean[allUsers.length];
+            for (int i = 0; i < allUsers.length; i++) {
+                perUserInstalled[i] = ps != null ? ps.getInstalled(allUsers[i]) : false;
+            }
+        }
+
+        synchronized (mInstallLock) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId);
+            res = deletePackageLI(packageName,
+                    (flags & PackageManager.DELETE_ALL_USERS) != 0
+                            ? UserHandle.ALL : new UserHandle(userId),
+                    true, allUsers, perUserInstalled,
+                    flags | REMOVE_CHATTY, info, true);
+            systemUpdate = info.isRemovedPackageSystemUpdate;
+            if (res && !systemUpdate && mPackages.get(packageName) == null) {
+                removedForAllUsers = true;
+            }
+            if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate
+                    + " removedForAllUsers=" + removedForAllUsers);
+        }
+
+        if (res) {
+            info.sendBroadcast(true, systemUpdate, removedForAllUsers);
+
+            // If the removed package was a system update, the old system package
+            // was re-enabled; we need to broadcast this information
+            if (systemUpdate) {
+                Bundle extras = new Bundle(1);
+                extras.putInt(Intent.EXTRA_UID, info.removedAppId >= 0
+                        ? info.removedAppId : info.uid);
+                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+
+                sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+                        extras, null, null, null);
+                sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+                        extras, null, null, null);
+                sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
+                        null, packageName, null, null);
+            }
+        }
+        // Force a gc here.
+        Runtime.getRuntime().gc();
+        // Delete the resources here after sending the broadcast to let
+        // other processes clean up before deleting resources.
+        if (info.args != null) {
+            synchronized (mInstallLock) {
+                info.args.doPostDeleteLI(true);
+            }
+        }
+
+        return res ? PackageManager.DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR;
+    }
+
+    static class PackageRemovedInfo {
+        String removedPackage;
+        int uid = -1;
+        int removedAppId = -1;
+        int[] removedUsers = null;
+        boolean isRemovedPackageSystemUpdate = false;
+        // Clean up resources deleted packages.
+        InstallArgs args = null;
+
+        void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers) {
+            Bundle extras = new Bundle(1);
+            extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
+            extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove);
+            if (replacing) {
+                extras.putBoolean(Intent.EXTRA_REPLACING, true);
+            }
+            extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers);
+            if (removedPackage != null) {
+                sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
+                        extras, null, null, removedUsers);
+                if (fullRemove && !replacing) {
+                    sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
+                            extras, null, null, removedUsers);
+                }
+            }
+            if (removedAppId >= 0) {
+                sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null,
+                        removedUsers);
+            }
+        }
+    }
+
+    /*
+     * This method deletes the package from internal data structures. If the DONT_DELETE_DATA
+     * flag is not set, the data directory is removed as well.
+     * make sure this flag is set for partially installed apps. If not its meaningless to
+     * delete a partially installed application.
+     */
+    private void removePackageDataLI(PackageSetting ps,
+            int[] allUserHandles, boolean[] perUserInstalled,
+            PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
+        String packageName = ps.name;
+        if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps);
+        removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
+        // Retrieve object to delete permissions for shared user later on
+        final PackageSetting deletedPs;
+        // reader
+        synchronized (mPackages) {
+            deletedPs = mSettings.mPackages.get(packageName);
+            if (outInfo != null) {
+                outInfo.removedPackage = packageName;
+                outInfo.removedUsers = deletedPs != null
+                        ? deletedPs.queryInstalledUsers(sUserManager.getUserIds(), true)
+                        : null;
+            }
+        }
+        if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
+            removeDataDirsLI(packageName);
+            schedulePackageCleaning(packageName, UserHandle.USER_ALL, true);
+        }
+        // writer
+        synchronized (mPackages) {
+            if (deletedPs != null) {
+                if ((flags&PackageManager.DELETE_KEEP_DATA) == 0) {
+                    if (outInfo != null) {
+                        outInfo.removedAppId = mSettings.removePackageLPw(packageName);
+                    }
+                    if (deletedPs != null) {
+                        updatePermissionsLPw(deletedPs.name, null, 0);
+                        if (deletedPs.sharedUser != null) {
+                            // remove permissions associated with package
+                            mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids);
+                        }
+                    }
+                    clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
+                }
+                // make sure to preserve per-user disabled state if this removal was just
+                // a downgrade of a system app to the factory package
+                if (allUserHandles != null && perUserInstalled != null) {
+                    if (DEBUG_REMOVE) {
+                        Slog.d(TAG, "Propagating install state across downgrade");
+                    }
+                    for (int i = 0; i < allUserHandles.length; i++) {
+                        if (DEBUG_REMOVE) {
+                            Slog.d(TAG, "    user " + allUserHandles[i]
+                                    + " => " + perUserInstalled[i]);
+                        }
+                        ps.setInstalled(perUserInstalled[i], allUserHandles[i]);
+                    }
+                }
+            }
+            // can downgrade to reader
+            if (writeSettings) {
+                // Save settings now
+                mSettings.writeLPr();
+            }
+        }
+        if (outInfo != null) {
+            // A user ID was deleted here. Go through all users and remove it
+            // from KeyStore.
+            removeKeystoreDataIfNeeded(UserHandle.USER_ALL, outInfo.removedAppId);
+        }
+    }
+
+    static boolean locationIsPrivileged(File path) {
+        try {
+            final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
+                    .getCanonicalPath();
+            return path.getCanonicalPath().startsWith(privilegedAppDir);
+        } catch (IOException e) {
+            Slog.e(TAG, "Unable to access code path " + path);
+        }
+        return false;
+    }
+
+    /*
+     * Tries to delete system package.
+     */
+    private boolean deleteSystemPackageLI(PackageSetting newPs,
+            int[] allUserHandles, boolean[] perUserInstalled,
+            int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
+        final boolean applyUserRestrictions
+                = (allUserHandles != null) && (perUserInstalled != null);
+        PackageSetting disabledPs = null;
+        // Confirm if the system package has been updated
+        // An updated system app can be deleted. This will also have to restore
+        // the system pkg from system partition
+        // reader
+        synchronized (mPackages) {
+            disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
+        }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs
+                + " disabledPs=" + disabledPs);
+        if (disabledPs == null) {
+            Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
+            return false;
+        } else if (DEBUG_REMOVE) {
+            Slog.d(TAG, "Deleting system pkg from data partition");
+        }
+        if (DEBUG_REMOVE) {
+            if (applyUserRestrictions) {
+                Slog.d(TAG, "Remembering install states:");
+                for (int i = 0; i < allUserHandles.length; i++) {
+                    Slog.d(TAG, "   u=" + allUserHandles[i] + " inst=" + perUserInstalled[i]);
+                }
+            }
+        }
+        // Delete the updated package
+        outInfo.isRemovedPackageSystemUpdate = true;
+        if (disabledPs.versionCode < newPs.versionCode) {
+            // Delete data for downgrades
+            flags &= ~PackageManager.DELETE_KEEP_DATA;
+        } else {
+            // Preserve data by setting flag
+            flags |= PackageManager.DELETE_KEEP_DATA;
+        }
+        boolean ret = deleteInstalledPackageLI(newPs, true, flags,
+                allUserHandles, perUserInstalled, outInfo, writeSettings);
+        if (!ret) {
+            return false;
+        }
+        // writer
+        synchronized (mPackages) {
+            // Reinstate the old system package
+            mSettings.enableSystemPackageLPw(newPs.name);
+            // Remove any native libraries from the upgraded package.
+            NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
+        }
+        // Install the system package
+        if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
+        int parseFlags = PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM;
+        if (locationIsPrivileged(disabledPs.codePath)) {
+            parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+        }
+        PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
+                parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
+
+        if (newPkg == null) {
+            Slog.w(TAG, "Failed to restore system package:" + newPs.name
+                    + " with error:" + mLastScanError);
+            return false;
+        }
+        // writer
+        synchronized (mPackages) {
+            updatePermissionsLPw(newPkg.packageName, newPkg,
+                    UPDATE_PERMISSIONS_ALL | UPDATE_PERMISSIONS_REPLACE_PKG);
+            if (applyUserRestrictions) {
+                if (DEBUG_REMOVE) {
+                    Slog.d(TAG, "Propagating install state across reinstall");
+                }
+                PackageSetting ps = mSettings.mPackages.get(newPkg.packageName);
+                for (int i = 0; i < allUserHandles.length; i++) {
+                    if (DEBUG_REMOVE) {
+                        Slog.d(TAG, "    user " + allUserHandles[i]
+                                + " => " + perUserInstalled[i]);
+                    }
+                    ps.setInstalled(perUserInstalled[i], allUserHandles[i]);
+                }
+                // Regardless of writeSettings we need to ensure that this restriction
+                // state propagation is persisted
+                mSettings.writeAllUsersPackageRestrictionsLPr();
+            }
+            // can downgrade to reader here
+            if (writeSettings) {
+                mSettings.writeLPr();
+            }
+        }
+        return true;
+    }
+
+    private boolean deleteInstalledPackageLI(PackageSetting ps,
+            boolean deleteCodeAndResources, int flags,
+            int[] allUserHandles, boolean[] perUserInstalled,
+            PackageRemovedInfo outInfo, boolean writeSettings) {
+        if (outInfo != null) {
+            outInfo.uid = ps.appId;
+        }
+
+        // Delete package data from internal structures and also remove data if flag is set
+        removePackageDataLI(ps, allUserHandles, perUserInstalled, outInfo, flags, writeSettings);
+
+        // Delete application code and resources
+        if (deleteCodeAndResources && (outInfo != null)) {
+            outInfo.args = createInstallArgs(packageFlagsToInstallFlags(ps), ps.codePathString,
+                    ps.resourcePathString, ps.nativeLibraryPathString);
+        }
+        return true;
+    }
+
+    /*
+     * This method handles package deletion in general
+     */
+    private boolean deletePackageLI(String packageName, UserHandle user,
+            boolean deleteCodeAndResources, int[] allUserHandles, boolean[] perUserInstalled,
+            int flags, PackageRemovedInfo outInfo,
+            boolean writeSettings) {
+        if (packageName == null) {
+            Slog.w(TAG, "Attempt to delete null packageName.");
+            return false;
+        }
+        if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
+        PackageSetting ps;
+        boolean dataOnly = false;
+        int removeUser = -1;
+        int appId = -1;
+        synchronized (mPackages) {
+            ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
+                Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
+                return false;
+            }
+            if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null
+                    && user.getIdentifier() != UserHandle.USER_ALL) {
+                // The caller is asking that the package only be deleted for a single
+                // user.  To do this, we just mark its uninstalled state and delete
+                // its data.  If this is a system app, we only allow this to happen if
+                // they have set the special DELETE_SYSTEM_APP which requests different
+                // semantics than normal for uninstalling system apps.
+                if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user");
+                ps.setUserState(user.getIdentifier(),
+                        COMPONENT_ENABLED_STATE_DEFAULT,
+                        false, //installed
+                        true,  //stopped
+                        true,  //notLaunched
+                        false, //blocked
+                        null, null, null);
+                if (!isSystemApp(ps)) {
+                    if (ps.isAnyInstalled(sUserManager.getUserIds())) {
+                        // Other user still have this package installed, so all
+                        // we need to do is clear this user's data and save that
+                        // it is uninstalled.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+                        removeUser = user.getIdentifier();
+                        appId = ps.appId;
+                        mSettings.writePackageRestrictionsLPr(removeUser);
+                    } else {
+                        // We need to set it back to 'installed' so the uninstall
+                        // broadcasts will be sent correctly.
+                        if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
+                        ps.setInstalled(true, user.getIdentifier());
+                    }
+                } else {
+                    // This is a system app, so we assume that the
+                    // other users still have this package installed, so all
+                    // we need to do is clear this user's data and save that
+                    // it is uninstalled.
+                    if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
+                    removeUser = user.getIdentifier();
+                    appId = ps.appId;
+                    mSettings.writePackageRestrictionsLPr(removeUser);
+                }
+            }
+        }
+
+        if (removeUser >= 0) {
+            // From above, we determined that we are deleting this only
+            // for a single user.  Continue the work here.
+            if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser);
+            if (outInfo != null) {
+                outInfo.removedPackage = packageName;
+                outInfo.removedAppId = appId;
+                outInfo.removedUsers = new int[] {removeUser};
+            }
+            mInstaller.clearUserData(packageName, removeUser);
+            removeKeystoreDataIfNeeded(removeUser, appId);
+            schedulePackageCleaning(packageName, removeUser, false);
+            return true;
+        }
+
+        if (dataOnly) {
+            // Delete application data first
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only");
+            removePackageDataLI(ps, null, null, outInfo, flags, writeSettings);
+            return true;
+        }
+
+        boolean ret = false;
+        mSettings.mKeySetManager.removeAppKeySetData(packageName);
+        if (isSystemApp(ps)) {
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name);
+            // When an updated system application is deleted we delete the existing resources as well and
+            // fall back to existing code in system partition
+            ret = deleteSystemPackageLI(ps, allUserHandles, perUserInstalled,
+                    flags, outInfo, writeSettings);
+        } else {
+            if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name);
+            // Kill application pre-emptively especially for apps on sd.
+            killApplication(packageName, ps.appId, "uninstall pkg");
+            ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags,
+                    allUserHandles, perUserInstalled,
+                    outInfo, writeSettings);
+        }
+
+        return ret;
+    }
+
+    private final class ClearStorageConnection implements ServiceConnection {
+        IMediaContainerService mContainerService;
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (this) {
+                mContainerService = IMediaContainerService.Stub.asInterface(service);
+                notifyAll();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+    }
+
+    private void clearExternalStorageDataSync(String packageName, int userId, boolean allData) {
+        final boolean mounted;
+        if (Environment.isExternalStorageEmulated()) {
+            mounted = true;
+        } else {
+            final String status = Environment.getExternalStorageState();
+
+            mounted = status.equals(Environment.MEDIA_MOUNTED)
+                    || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
+        }
+
+        if (!mounted) {
+            return;
+        }
+
+        final Intent containerIntent = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+        int[] users;
+        if (userId == UserHandle.USER_ALL) {
+            users = sUserManager.getUserIds();
+        } else {
+            users = new int[] { userId };
+        }
+        final ClearStorageConnection conn = new ClearStorageConnection();
+        if (mContext.bindServiceAsUser(
+                containerIntent, conn, Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
+            try {
+                for (int curUser : users) {
+                    long timeout = SystemClock.uptimeMillis() + 5000;
+                    synchronized (conn) {
+                        long now = SystemClock.uptimeMillis();
+                        while (conn.mContainerService == null && now < timeout) {
+                            try {
+                                conn.wait(timeout - now);
+                            } catch (InterruptedException e) {
+                            }
+                        }
+                    }
+                    if (conn.mContainerService == null) {
+                        return;
+                    }
+
+                    final UserEnvironment userEnv = new UserEnvironment(curUser);
+                    clearDirectory(conn.mContainerService,
+                            userEnv.buildExternalStorageAppCacheDirs(packageName));
+                    if (allData) {
+                        clearDirectory(conn.mContainerService,
+                                userEnv.buildExternalStorageAppDataDirs(packageName));
+                        clearDirectory(conn.mContainerService,
+                                userEnv.buildExternalStorageAppMediaDirs(packageName));
+                    }
+                }
+            } finally {
+                mContext.unbindService(conn);
+            }
+        }
+    }
+
+    @Override
+    public void clearApplicationUserData(final String packageName,
+            final IPackageDataObserver observer, final int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CLEAR_APP_USER_DATA, null);
+        enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "clear application data");
+        // Queue up an async operation since the package deletion may take a little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                final boolean succeeded;
+                synchronized (mInstallLock) {
+                    succeeded = clearApplicationUserDataLI(packageName, userId);
+                }
+                clearExternalStorageDataSync(packageName, userId, true);
+                if (succeeded) {
+                    // invoke DeviceStorageMonitor's update method to clear any notifications
+                    DeviceStorageMonitorInternal
+                            dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+                    if (dsm != null) {
+                        dsm.checkMemory();
+                    }
+                }
+                if(observer != null) {
+                    try {
+                        observer.onRemoveCompleted(packageName, succeeded);
+                    } catch (RemoteException e) {
+                        Log.i(TAG, "Observer no longer exists.");
+                    }
+                } //end if observer
+            } //end run
+        });
+    }
+
+    private boolean clearApplicationUserDataLI(String packageName, int userId) {
+        if (packageName == null) {
+            Slog.w(TAG, "Attempt to delete null packageName.");
+            return false;
+        }
+        PackageParser.Package p;
+        boolean dataOnly = false;
+        final int appId;
+        synchronized (mPackages) {
+            p = mPackages.get(packageName);
+            if (p == null) {
+                dataOnly = true;
+                PackageSetting ps = mSettings.mPackages.get(packageName);
+                if ((ps == null) || (ps.pkg == null)) {
+                    Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
+                    return false;
+                }
+                p = ps.pkg;
+            }
+            if (!dataOnly) {
+                // need to check this only for fully installed applications
+                if (p == null) {
+                    Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
+                    return false;
+                }
+                final ApplicationInfo applicationInfo = p.applicationInfo;
+                if (applicationInfo == null) {
+                    Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+                    return false;
+                }
+            }
+            if (p != null && p.applicationInfo != null) {
+                appId = p.applicationInfo.uid;
+            } else {
+                appId = -1;
+            }
+        }
+        int retCode = mInstaller.clearUserData(packageName, userId);
+        if (retCode < 0) {
+            Slog.w(TAG, "Couldn't remove cache files for package: "
+                    + packageName);
+            return false;
+        }
+        removeKeystoreDataIfNeeded(userId, appId);
+        return true;
+    }
+
+    /**
+     * Remove entries from the keystore daemon. Will only remove it if the
+     * {@code appId} is valid.
+     */
+    private static void removeKeystoreDataIfNeeded(int userId, int appId) {
+        if (appId < 0) {
+            return;
+        }
+
+        final KeyStore keyStore = KeyStore.getInstance();
+        if (keyStore != null) {
+            if (userId == UserHandle.USER_ALL) {
+                for (final int individual : sUserManager.getUserIds()) {
+                    keyStore.clearUid(UserHandle.getUid(individual, appId));
+                }
+            } else {
+                keyStore.clearUid(UserHandle.getUid(userId, appId));
+            }
+        } else {
+            Slog.w(TAG, "Could not contact keystore to clear entries for app id " + appId);
+        }
+    }
+
+    public void deleteApplicationCacheFiles(final String packageName,
+            final IPackageDataObserver observer) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.DELETE_CACHE_FILES, null);
+        // Queue up an async operation since the package deletion may take a little while.
+        final int userId = UserHandle.getCallingUserId();
+        mHandler.post(new Runnable() {
+            public void run() {
+                mHandler.removeCallbacks(this);
+                final boolean succeded;
+                synchronized (mInstallLock) {
+                    succeded = deleteApplicationCacheFilesLI(packageName, userId);
+                }
+                clearExternalStorageDataSync(packageName, userId, false);
+                if(observer != null) {
+                    try {
+                        observer.onRemoveCompleted(packageName, succeded);
+                    } catch (RemoteException e) {
+                        Log.i(TAG, "Observer no longer exists.");
+                    }
+                } //end if observer
+            } //end run
+        });
+    }
+
+    private boolean deleteApplicationCacheFilesLI(String packageName, int userId) {
+        if (packageName == null) {
+            Slog.w(TAG, "Attempt to delete null packageName.");
+            return false;
+        }
+        PackageParser.Package p;
+        synchronized (mPackages) {
+            p = mPackages.get(packageName);
+        }
+        if (p == null) {
+            Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
+            return false;
+        }
+        final ApplicationInfo applicationInfo = p.applicationInfo;
+        if (applicationInfo == null) {
+            Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+            return false;
+        }
+        int retCode = mInstaller.deleteCacheFiles(packageName, userId);
+        if (retCode < 0) {
+            Slog.w(TAG, "Couldn't remove cache files for package: "
+                       + packageName + " u" + userId);
+            return false;
+        }
+        return true;
+    }
+
+    public void getPackageSizeInfo(final String packageName, int userHandle,
+            final IPackageStatsObserver observer) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GET_PACKAGE_SIZE, null);
+
+        PackageStats stats = new PackageStats(packageName, userHandle);
+
+        /*
+         * Queue up an async operation since the package measurement may take a
+         * little while.
+         */
+        Message msg = mHandler.obtainMessage(INIT_COPY);
+        msg.obj = new MeasureParams(stats, observer);
+        mHandler.sendMessage(msg);
+    }
+
+    private boolean getPackageSizeInfoLI(String packageName, int userHandle,
+            PackageStats pStats) {
+        if (packageName == null) {
+            Slog.w(TAG, "Attempt to get size of null packageName.");
+            return false;
+        }
+        PackageParser.Package p;
+        boolean dataOnly = false;
+        String libDirPath = null;
+        String asecPath = null;
+        synchronized (mPackages) {
+            p = mPackages.get(packageName);
+            PackageSetting ps = mSettings.mPackages.get(packageName);
+            if(p == null) {
+                dataOnly = true;
+                if((ps == null) || (ps.pkg == null)) {
+                    Slog.w(TAG, "Package named '" + packageName +"' doesn't exist.");
+                    return false;
+                }
+                p = ps.pkg;
+            }
+            if (ps != null) {
+                libDirPath = ps.nativeLibraryPathString;
+            }
+            if (p != null && (isExternal(p) || isForwardLocked(p))) {
+                String secureContainerId = cidFromCodePath(p.applicationInfo.sourceDir);
+                if (secureContainerId != null) {
+                    asecPath = PackageHelper.getSdFilesystem(secureContainerId);
+                }
+            }
+        }
+        String publicSrcDir = null;
+        if(!dataOnly) {
+            final ApplicationInfo applicationInfo = p.applicationInfo;
+            if (applicationInfo == null) {
+                Slog.w(TAG, "Package " + packageName + " has no applicationInfo.");
+                return false;
+            }
+            if (isForwardLocked(p)) {
+                publicSrcDir = applicationInfo.publicSourceDir;
+            }
+        }
+        int res = mInstaller.getSizeInfo(packageName, userHandle, p.mPath, libDirPath,
+                publicSrcDir, asecPath, pStats);
+        if (res < 0) {
+            return false;
+        }
+
+        // Fix-up for forward-locked applications in ASEC containers.
+        if (!isExternal(p)) {
+            pStats.codeSize += pStats.externalCodeSize;
+            pStats.externalCodeSize = 0L;
+        }
+
+        return true;
+    }
+
+
+    public void addPackageToPreferred(String packageName) {
+        Slog.w(TAG, "addPackageToPreferred: this is now a no-op");
+    }
+
+    public void removePackageFromPreferred(String packageName) {
+        Slog.w(TAG, "removePackageFromPreferred: this is now a no-op");
+    }
+
+    public List<PackageInfo> getPreferredPackages(int flags) {
+        return new ArrayList<PackageInfo>();
+    }
+
+    private int getUidTargetSdkVersionLockedLPr(int uid) {
+        Object obj = mSettings.getUserIdLPr(uid);
+        if (obj instanceof SharedUserSetting) {
+            final SharedUserSetting sus = (SharedUserSetting) obj;
+            int vers = Build.VERSION_CODES.CUR_DEVELOPMENT;
+            final Iterator<PackageSetting> it = sus.packages.iterator();
+            while (it.hasNext()) {
+                final PackageSetting ps = it.next();
+                if (ps.pkg != null) {
+                    int v = ps.pkg.applicationInfo.targetSdkVersion;
+                    if (v < vers) vers = v;
+                }
+            }
+            return vers;
+        } else if (obj instanceof PackageSetting) {
+            final PackageSetting ps = (PackageSetting) obj;
+            if (ps.pkg != null) {
+                return ps.pkg.applicationInfo.targetSdkVersion;
+            }
+        }
+        return Build.VERSION_CODES.CUR_DEVELOPMENT;
+    }
+
+    public void addPreferredActivity(IntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity, int userId) {
+        addPreferredActivityInternal(filter, match, set, activity, true, userId);
+    }
+
+    private void addPreferredActivityInternal(IntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity, boolean always, int userId) {
+        // writer
+        int callingUid = Binder.getCallingUid();
+        enforceCrossUserPermission(callingUid, userId, true, "add preferred activity");
+        if (filter.countActions() == 0) {
+            Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
+            return;
+        }
+        synchronized (mPackages) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (getUidTargetSdkVersionLockedLPr(callingUid)
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring addPreferredActivity() from uid "
+                            + callingUid);
+                    return;
+                }
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+            }
+
+            Slog.i(TAG, "Adding preferred activity " + activity + " for user " + userId + " :");
+            filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+            mSettings.editPreferredActivitiesLPw(userId).addFilter(
+                    new PreferredActivity(filter, match, set, activity, always));
+            mSettings.writePackageRestrictionsLPr(userId);
+        }
+    }
+
+    public void replacePreferredActivity(IntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity) {
+        if (filter.countActions() != 1) {
+            throw new IllegalArgumentException(
+                    "replacePreferredActivity expects filter to have only 1 action.");
+        }
+        if (filter.countDataAuthorities() != 0
+                || filter.countDataPaths() != 0
+                || filter.countDataSchemes() > 1
+                || filter.countDataTypes() != 0) {
+            throw new IllegalArgumentException(
+                    "replacePreferredActivity expects filter to have no data authorities, " +
+                    "paths, or types; and at most one scheme.");
+        }
+        synchronized (mPackages) {
+            if (mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid())
+                        < Build.VERSION_CODES.FROYO) {
+                    Slog.w(TAG, "Ignoring replacePreferredActivity() from uid "
+                            + Binder.getCallingUid());
+                    return;
+                }
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+            }
+
+            final int callingUserId = UserHandle.getCallingUserId();
+            PreferredIntentResolver pir = mSettings.mPreferredActivities.get(callingUserId);
+            if (pir != null) {
+                Intent intent = new Intent(filter.getAction(0)).addCategory(filter.getCategory(0));
+                if (filter.countDataSchemes() == 1) {
+                    Uri.Builder builder = new Uri.Builder();
+                    builder.scheme(filter.getDataScheme(0));
+                    intent.setData(builder.build());
+                }
+                List<PreferredActivity> matches = pir.queryIntent(
+                        intent, null, true, callingUserId);
+                if (DEBUG_PREFERRED) {
+                    Slog.i(TAG, matches.size() + " preferred matches for " + intent);
+                }
+                for (int i = 0; i < matches.size(); i++) {
+                    PreferredActivity pa = matches.get(i);
+                    if (DEBUG_PREFERRED) {
+                        Slog.i(TAG, "Removing preferred activity "
+                                + pa.mPref.mComponent + ":");
+                        filter.dump(new LogPrinter(Log.INFO, TAG), "  ");
+                    }
+                    pir.removeFilter(pa);
+                }
+            }
+            addPreferredActivityInternal(filter, match, set, activity, true, callingUserId);
+        }
+    }
+
+    public void clearPackagePreferredActivities(String packageName) {
+        final int uid = Binder.getCallingUid();
+        // writer
+        synchronized (mPackages) {
+            PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null || pkg.applicationInfo.uid != uid) {
+                if (mContext.checkCallingOrSelfPermission(
+                        android.Manifest.permission.SET_PREFERRED_APPLICATIONS)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    if (getUidTargetSdkVersionLockedLPr(Binder.getCallingUid())
+                            < Build.VERSION_CODES.FROYO) {
+                        Slog.w(TAG, "Ignoring clearPackagePreferredActivities() from uid "
+                                + Binder.getCallingUid());
+                        return;
+                    }
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+                }
+            }
+
+            int user = UserHandle.getCallingUserId();
+            if (clearPackagePreferredActivitiesLPw(packageName, user)) {
+                mSettings.writePackageRestrictionsLPr(user);
+                scheduleWriteSettingsLocked();
+            }
+        }
+    }
+
+    /** This method takes a specific user id as well as UserHandle.USER_ALL. */
+    boolean clearPackagePreferredActivitiesLPw(String packageName, int userId) {
+        ArrayList<PreferredActivity> removed = null;
+        boolean changed = false;
+        for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
+            final int thisUserId = mSettings.mPreferredActivities.keyAt(i);
+            PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
+            if (userId != UserHandle.USER_ALL && userId != thisUserId) {
+                continue;
+            }
+            Iterator<PreferredActivity> it = pir.filterIterator();
+            while (it.hasNext()) {
+                PreferredActivity pa = it.next();
+                // Mark entry for removal only if it matches the package name
+                // and the entry is of type "always".
+                if (packageName == null ||
+                        (pa.mPref.mComponent.getPackageName().equals(packageName)
+                                && pa.mPref.mAlways)) {
+                    if (removed == null) {
+                        removed = new ArrayList<PreferredActivity>();
+                    }
+                    removed.add(pa);
+                }
+            }
+            if (removed != null) {
+                for (int j=0; j<removed.size(); j++) {
+                    PreferredActivity pa = removed.get(j);
+                    pir.removeFilter(pa);
+                }
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    public void resetPreferredActivities(int userId) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.SET_PREFERRED_APPLICATIONS, null);
+        // writer
+        synchronized (mPackages) {
+            int user = UserHandle.getCallingUserId();
+            clearPackagePreferredActivitiesLPw(null, user);
+            mSettings.readDefaultPreferredAppsLPw(this, user);
+            mSettings.writePackageRestrictionsLPr(user);
+            scheduleWriteSettingsLocked();
+        }
+    }
+
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+            List<ComponentName> outActivities, String packageName) {
+
+        int num = 0;
+        final int userId = UserHandle.getCallingUserId();
+        // reader
+        synchronized (mPackages) {
+            PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId);
+            if (pir != null) {
+                final Iterator<PreferredActivity> it = pir.filterIterator();
+                while (it.hasNext()) {
+                    final PreferredActivity pa = it.next();
+                    if (packageName == null
+                            || (pa.mPref.mComponent.getPackageName().equals(packageName)
+                                    && pa.mPref.mAlways)) {
+                        if (outFilters != null) {
+                            outFilters.add(new IntentFilter(pa));
+                        }
+                        if (outActivities != null) {
+                            outActivities.add(pa.mPref.mComponent);
+                        }
+                    }
+                }
+            }
+        }
+
+        return num;
+    }
+
+    @Override
+    public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory(Intent.CATEGORY_HOME);
+
+        final int callingUserId = UserHandle.getCallingUserId();
+        List<ResolveInfo> list = queryIntentActivities(intent, null,
+                PackageManager.GET_META_DATA, callingUserId);
+        ResolveInfo preferred = findPreferredActivity(intent, null, 0, list, 0,
+                true, false, false, callingUserId);
+
+        allHomeCandidates.clear();
+        if (list != null) {
+            for (ResolveInfo ri : list) {
+                allHomeCandidates.add(ri);
+            }
+        }
+        return (preferred == null || preferred.activityInfo == null)
+                ? null
+                : new ComponentName(preferred.activityInfo.packageName,
+                        preferred.activityInfo.name);
+    }
+
+    @Override
+    public void setApplicationEnabledSetting(String appPackageName,
+            int newState, int flags, int userId, String callingPackage) {
+        if (!sUserManager.exists(userId)) return;
+        if (callingPackage == null) {
+            callingPackage = Integer.toString(Binder.getCallingUid());
+        }
+        setEnabledSetting(appPackageName, null, newState, flags, userId, callingPackage);
+    }
+
+    @Override
+    public void setComponentEnabledSetting(ComponentName componentName,
+            int newState, int flags, int userId) {
+        if (!sUserManager.exists(userId)) return;
+        setEnabledSetting(componentName.getPackageName(),
+                componentName.getClassName(), newState, flags, userId, null);
+    }
+
+    private void setEnabledSetting(final String packageName, String className, int newState,
+            final int flags, int userId, String callingPackage) {
+        if (!(newState == COMPONENT_ENABLED_STATE_DEFAULT
+              || newState == COMPONENT_ENABLED_STATE_ENABLED
+              || newState == COMPONENT_ENABLED_STATE_DISABLED
+              || newState == COMPONENT_ENABLED_STATE_DISABLED_USER
+              || newState == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
+            throw new IllegalArgumentException("Invalid new component state: "
+                    + newState);
+        }
+        PackageSetting pkgSetting;
+        final int uid = Binder.getCallingUid();
+        final int permission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+        enforceCrossUserPermission(uid, userId, false, "set enabled");
+        final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+        boolean sendNow = false;
+        boolean isApp = (className == null);
+        String componentName = isApp ? packageName : className;
+        int packageUid = -1;
+        ArrayList<String> components;
+
+        // writer
+        synchronized (mPackages) {
+            pkgSetting = mSettings.mPackages.get(packageName);
+            if (pkgSetting == null) {
+                if (className == null) {
+                    throw new IllegalArgumentException(
+                            "Unknown package: " + packageName);
+                }
+                throw new IllegalArgumentException(
+                        "Unknown component: " + packageName
+                        + "/" + className);
+            }
+            // Allow root and verify that userId is not being specified by a different user
+            if (!allowedByPermission && !UserHandle.isSameApp(uid, pkgSetting.appId)) {
+                throw new SecurityException(
+                        "Permission Denial: attempt to change component state from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + uid + ", package uid=" + pkgSetting.appId);
+            }
+            if (className == null) {
+                // We're dealing with an application/package level state change
+                if (pkgSetting.getEnabled(userId) == newState) {
+                    // Nothing to do
+                    return;
+                }
+                if (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                    || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+                    // Don't care about who enables an app.
+                    callingPackage = null;
+                }
+                pkgSetting.setEnabled(newState, userId, callingPackage);
+                // pkgSetting.pkg.mSetEnabled = newState;
+            } else {
+                // We're dealing with a component level state change
+                // First, verify that this is a valid class name.
+                PackageParser.Package pkg = pkgSetting.pkg;
+                if (pkg == null || !pkg.hasComponentClassName(className)) {
+                    if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
+                        throw new IllegalArgumentException("Component class " + className
+                                + " does not exist in " + packageName);
+                    } else {
+                        Slog.w(TAG, "Failed setComponentEnabledSetting: component class "
+                                + className + " does not exist in " + packageName);
+                    }
+                }
+                switch (newState) {
+                case COMPONENT_ENABLED_STATE_ENABLED:
+                    if (!pkgSetting.enableComponentLPw(className, userId)) {
+                        return;
+                    }
+                    break;
+                case COMPONENT_ENABLED_STATE_DISABLED:
+                    if (!pkgSetting.disableComponentLPw(className, userId)) {
+                        return;
+                    }
+                    break;
+                case COMPONENT_ENABLED_STATE_DEFAULT:
+                    if (!pkgSetting.restoreComponentLPw(className, userId)) {
+                        return;
+                    }
+                    break;
+                default:
+                    Slog.e(TAG, "Invalid new component state: " + newState);
+                    return;
+                }
+            }
+            mSettings.writePackageRestrictionsLPr(userId);
+            components = mPendingBroadcasts.get(userId, packageName);
+            final boolean newPackage = components == null;
+            if (newPackage) {
+                components = new ArrayList<String>();
+            }
+            if (!components.contains(componentName)) {
+                components.add(componentName);
+            }
+            if ((flags&PackageManager.DONT_KILL_APP) == 0) {
+                sendNow = true;
+                // Purge entry from pending broadcast list if another one exists already
+                // since we are sending one right away.
+                mPendingBroadcasts.remove(userId, packageName);
+            } else {
+                if (newPackage) {
+                    mPendingBroadcasts.put(userId, packageName, components);
+                }
+                if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
+                    // Schedule a message
+                    mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+                }
+            }
+        }
+
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            if (sendNow) {
+                packageUid = UserHandle.getUid(userId, pkgSetting.appId);
+                sendPackageChangedBroadcast(packageName,
+                        (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
+    private void sendPackageChangedBroadcast(String packageName,
+            boolean killFlag, ArrayList<String> componentNames, int packageUid) {
+        if (DEBUG_INSTALL)
+            Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+                    + componentNames);
+        Bundle extras = new Bundle(4);
+        extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0));
+        String nameList[] = new String[componentNames.size()];
+        componentNames.toArray(nameList);
+        extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
+        extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
+        extras.putInt(Intent.EXTRA_UID, packageUid);
+        sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED,  packageName, extras, null, null,
+                new int[] {UserHandle.getUserId(packageUid)});
+    }
+
+    public void setPackageStoppedState(String packageName, boolean stopped, int userId) {
+        if (!sUserManager.exists(userId)) return;
+        final int uid = Binder.getCallingUid();
+        final int permission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+        final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
+        enforceCrossUserPermission(uid, userId, true, "stop package");
+        // writer
+        synchronized (mPackages) {
+            if (mSettings.setPackageStoppedStateLPw(packageName, stopped, allowedByPermission,
+                    uid, userId)) {
+                scheduleWritePackageRestrictionsLocked(userId);
+            }
+        }
+    }
+
+    public String getInstallerPackageName(String packageName) {
+        // reader
+        synchronized (mPackages) {
+            return mSettings.getInstallerPackageNameLPr(packageName);
+        }
+    }
+
+    @Override
+    public int getApplicationEnabledSetting(String packageName, int userId) {
+        if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
+        int uid = Binder.getCallingUid();
+        enforceCrossUserPermission(uid, userId, false, "get enabled");
+        // reader
+        synchronized (mPackages) {
+            return mSettings.getApplicationEnabledSettingLPr(packageName, userId);
+        }
+    }
+
+    @Override
+    public int getComponentEnabledSetting(ComponentName componentName, int userId) {
+        if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
+        int uid = Binder.getCallingUid();
+        enforceCrossUserPermission(uid, userId, false, "get component enabled");
+        // reader
+        synchronized (mPackages) {
+            return mSettings.getComponentEnabledSettingLPr(componentName, userId);
+        }
+    }
+
+    public void enterSafeMode() {
+        enforceSystemOrRoot("Only the system can request entering safe mode");
+
+        if (!mSystemReady) {
+            mSafeMode = true;
+        }
+    }
+
+    public void systemReady() {
+        mSystemReady = true;
+
+        // Read the compatibilty setting when the system is ready.
+        boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
+                mContext.getContentResolver(),
+                android.provider.Settings.Global.COMPATIBILITY_MODE, 1) == 1;
+        PackageParser.setCompatibilityModeEnabled(compatibilityModeEnabled);
+        if (DEBUG_SETTINGS) {
+            Log.d(TAG, "compatibility mode:" + compatibilityModeEnabled);
+        }
+
+        synchronized (mPackages) {
+            // Verify that all of the preferred activity components actually
+            // exist.  It is possible for applications to be updated and at
+            // that point remove a previously declared activity component that
+            // had been set as a preferred activity.  We try to clean this up
+            // the next time we encounter that preferred activity, but it is
+            // possible for the user flow to never be able to return to that
+            // situation so here we do a sanity check to make sure we haven't
+            // left any junk around.
+            ArrayList<PreferredActivity> removed = new ArrayList<PreferredActivity>();
+            for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
+                PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
+                removed.clear();
+                for (PreferredActivity pa : pir.filterSet()) {
+                    if (mActivities.mActivities.get(pa.mPref.mComponent) == null) {
+                        removed.add(pa);
+                    }
+                }
+                if (removed.size() > 0) {
+                    for (int j=0; j<removed.size(); j++) {
+                        PreferredActivity pa = removed.get(i);
+                        Slog.w(TAG, "Removing dangling preferred activity: "
+                                + pa.mPref.mComponent);
+                        pir.removeFilter(pa);
+                    }
+                    mSettings.writePackageRestrictionsLPr(
+                            mSettings.mPreferredActivities.keyAt(i));
+                }
+            }
+        }
+        sUserManager.systemReady();
+    }
+
+    public boolean isSafeMode() {
+        return mSafeMode;
+    }
+
+    public boolean hasSystemUidErrors() {
+        return mHasSystemUidErrors;
+    }
+
+    static String arrayToString(int[] array) {
+        StringBuffer buf = new StringBuffer(128);
+        buf.append('[');
+        if (array != null) {
+            for (int i=0; i<array.length; i++) {
+                if (i > 0) buf.append(", ");
+                buf.append(array[i]);
+            }
+        }
+        buf.append(']');
+        return buf.toString();
+    }
+
+    static class DumpState {
+        public static final int DUMP_LIBS = 1 << 0;
+
+        public static final int DUMP_FEATURES = 1 << 1;
+
+        public static final int DUMP_RESOLVERS = 1 << 2;
+
+        public static final int DUMP_PERMISSIONS = 1 << 3;
+
+        public static final int DUMP_PACKAGES = 1 << 4;
+
+        public static final int DUMP_SHARED_USERS = 1 << 5;
+
+        public static final int DUMP_MESSAGES = 1 << 6;
+
+        public static final int DUMP_PROVIDERS = 1 << 7;
+
+        public static final int DUMP_VERIFIERS = 1 << 8;
+
+        public static final int DUMP_PREFERRED = 1 << 9;
+
+        public static final int DUMP_PREFERRED_XML = 1 << 10;
+
+        public static final int DUMP_KEYSETS = 1 << 11;
+
+        public static final int OPTION_SHOW_FILTERS = 1 << 0;
+
+        private int mTypes;
+
+        private int mOptions;
+
+        private boolean mTitlePrinted;
+
+        private SharedUserSetting mSharedUser;
+
+        public boolean isDumping(int type) {
+            if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
+                return true;
+            }
+
+            return (mTypes & type) != 0;
+        }
+
+        public void setDump(int type) {
+            mTypes |= type;
+        }
+
+        public boolean isOptionEnabled(int option) {
+            return (mOptions & option) != 0;
+        }
+
+        public void setOptionEnabled(int option) {
+            mOptions |= option;
+        }
+
+        public boolean onTitlePrinted() {
+            final boolean printed = mTitlePrinted;
+            mTitlePrinted = true;
+            return printed;
+        }
+
+        public boolean getTitlePrinted() {
+            return mTitlePrinted;
+        }
+
+        public void setTitlePrinted(boolean enabled) {
+            mTitlePrinted = enabled;
+        }
+
+        public SharedUserSetting getSharedUser() {
+            return mSharedUser;
+        }
+
+        public void setSharedUser(SharedUserSetting user) {
+            mSharedUser = user;
+        }
+    }
+
+    @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 ActivityManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        DumpState dumpState = new DumpState();
+        boolean fullPreferred = false;
+        
+        String packageName = null;
+        
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                // Right now we only know how to print all.
+            } else if ("-h".equals(opt)) {
+                pw.println("Package manager dump options:");
+                pw.println("  [-h] [-f] [cmd] ...");
+                pw.println("    -f: print details of intent filters");
+                pw.println("    -h: print this help");
+                pw.println("  cmd may be one of:");
+                pw.println("    l[ibraries]: list known shared libraries");
+                pw.println("    f[ibraries]: list device features");
+                pw.println("    r[esolvers]: dump intent resolvers");
+                pw.println("    perm[issions]: dump permissions");
+                pw.println("    pref[erred]: print preferred package settings");
+                pw.println("    preferred-xml [--full]: print preferred package settings as xml");
+                pw.println("    prov[iders]: dump content providers");
+                pw.println("    p[ackages]: dump installed packages");
+                pw.println("    s[hared-users]: dump shared user IDs");
+                pw.println("    m[essages]: print collected runtime messages");
+                pw.println("    v[erifiers]: print package verifier info");
+                pw.println("    <package.name>: info about given package");
+                pw.println("    k[eysets]: print known keysets");
+                return;
+            } else if ("-f".equals(opt)) {
+                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+        
+        // Is the caller requesting to dump a particular piece of data?
+        if (opti < args.length) {
+            String cmd = args[opti];
+            opti++;
+            // Is this a package name?
+            if ("android".equals(cmd) || cmd.contains(".")) {
+                packageName = cmd;
+                // When dumping a single package, we always dump all of its
+                // filter information since the amount of data will be reasonable.
+                dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS);
+            } else if ("l".equals(cmd) || "libraries".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_LIBS);
+            } else if ("f".equals(cmd) || "features".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_FEATURES);
+            } else if ("r".equals(cmd) || "resolvers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_RESOLVERS);
+            } else if ("perm".equals(cmd) || "permissions".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PERMISSIONS);
+            } else if ("pref".equals(cmd) || "preferred".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PREFERRED);
+            } else if ("preferred-xml".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PREFERRED_XML);
+                if (opti < args.length && "--full".equals(args[opti])) {
+                    fullPreferred = true;
+                    opti++;
+                }
+            } else if ("p".equals(cmd) || "packages".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PACKAGES);
+            } else if ("s".equals(cmd) || "shared-users".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_SHARED_USERS);
+            } else if ("prov".equals(cmd) || "providers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_PROVIDERS);
+            } else if ("m".equals(cmd) || "messages".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_MESSAGES);
+            } else if ("v".equals(cmd) || "verifiers".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_VERIFIERS);
+            } else if ("k".equals(cmd) || "keysets".equals(cmd)) {
+                dumpState.setDump(DumpState.DUMP_KEYSETS);
+            }
+        }
+
+        // reader
+        synchronized (mPackages) {
+            if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Verifiers:");
+                pw.print("  Required: ");
+                pw.print(mRequiredVerifierPackage);
+                pw.print(" (uid=");
+                pw.print(getPackageUid(mRequiredVerifierPackage, 0));
+                pw.println(")");
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) {
+                boolean printedHeader = false;
+                final Iterator<String> it = mSharedLibraries.keySet().iterator();
+                while (it.hasNext()) {
+                    String name = it.next();
+                    SharedLibraryEntry ent = mSharedLibraries.get(name);
+                    if (!printedHeader) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("Libraries:");
+                        printedHeader = true;
+                    }
+                    pw.print("  ");
+                    pw.print(name);
+                    pw.print(" -> ");
+                    if (ent.path != null) {
+                        pw.print("(jar) ");
+                        pw.print(ent.path);
+                    } else {
+                        pw.print("(apk) ");
+                        pw.print(ent.apk);
+                    }
+                    pw.println();
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Features:");
+                Iterator<String> it = mAvailableFeatures.keySet().iterator();
+                while (it.hasNext()) {
+                    String name = it.next();
+                    pw.print("  ");
+                    pw.println(name);
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_RESOLVERS)) {
+                if (mActivities.dump(pw, dumpState.getTitlePrinted() ? "\nActivity Resolver Table:"
+                        : "Activity Resolver Table:", "  ", packageName,
+                        dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) {
+                    dumpState.setTitlePrinted(true);
+                }
+                if (mReceivers.dump(pw, dumpState.getTitlePrinted() ? "\nReceiver Resolver Table:"
+                        : "Receiver Resolver Table:", "  ", packageName,
+                        dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) {
+                    dumpState.setTitlePrinted(true);
+                }
+                if (mServices.dump(pw, dumpState.getTitlePrinted() ? "\nService Resolver Table:"
+                        : "Service Resolver Table:", "  ", packageName,
+                        dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) {
+                    dumpState.setTitlePrinted(true);
+                }
+                if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:"
+                        : "Provider Resolver Table:", "  ", packageName,
+                        dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) {
+                    dumpState.setTitlePrinted(true);
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) {
+                for (int i=0; i<mSettings.mPreferredActivities.size(); i++) {
+                    PreferredIntentResolver pir = mSettings.mPreferredActivities.valueAt(i);
+                    int user = mSettings.mPreferredActivities.keyAt(i);
+                    if (pir.dump(pw,
+                            dumpState.getTitlePrinted()
+                                ? "\nPreferred Activities User " + user + ":"
+                                : "Preferred Activities User " + user + ":", "  ",
+                            packageName, true)) {
+                        dumpState.setTitlePrinted(true);
+                    }
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) {
+                pw.flush();
+                FileOutputStream fout = new FileOutputStream(fd);
+                BufferedOutputStream str = new BufferedOutputStream(fout);
+                XmlSerializer serializer = new FastXmlSerializer();
+                try {
+                    serializer.setOutput(str, "utf-8");
+                    serializer.startDocument(null, true);
+                    serializer.setFeature(
+                            "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+                    mSettings.writePreferredActivitiesLPr(serializer, 0, fullPreferred);
+                    serializer.endDocument();
+                    serializer.flush();
+                } catch (IllegalArgumentException e) {
+                    pw.println("Failed writing: " + e);
+                } catch (IllegalStateException e) {
+                    pw.println("Failed writing: " + e);
+                } catch (IOException e) {
+                    pw.println("Failed writing: " + e);
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) {
+                mSettings.dumpPermissionsLPr(pw, packageName, dumpState);
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) {
+                boolean printedSomething = false;
+                for (PackageParser.Provider p : mProviders.mProviders.values()) {
+                    if (packageName != null && !packageName.equals(p.info.packageName)) {
+                        continue;
+                    }
+                    if (!printedSomething) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("Registered ContentProviders:");
+                        printedSomething = true;
+                    }
+                    pw.print("  "); p.printComponentShortName(pw); pw.println(":");
+                    pw.print("    "); pw.println(p.toString());
+                }
+                printedSomething = false;
+                for (Map.Entry<String, PackageParser.Provider> entry :
+                        mProvidersByAuthority.entrySet()) {
+                    PackageParser.Provider p = entry.getValue();
+                    if (packageName != null && !packageName.equals(p.info.packageName)) {
+                        continue;
+                    }
+                    if (!printedSomething) {
+                        if (dumpState.onTitlePrinted())
+                            pw.println();
+                        pw.println("ContentProvider Authorities:");
+                        printedSomething = true;
+                    }
+                    pw.print("  ["); pw.print(entry.getKey()); pw.println("]:");
+                    pw.print("    "); pw.println(p.toString());
+                    if (p.info != null && p.info.applicationInfo != null) {
+                        final String appInfo = p.info.applicationInfo.toString();
+                        pw.print("      applicationInfo="); pw.println(appInfo);
+                    }
+                }
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_KEYSETS)) {
+                mSettings.mKeySetManager.dump(pw, packageName, dumpState);
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) {
+                mSettings.dumpPackagesLPr(pw, packageName, dumpState);
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) {
+                mSettings.dumpSharedUsersLPr(pw, packageName, dumpState);
+            }
+
+            if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                mSettings.dumpReadMessagesLPr(pw, dumpState);
+
+                pw.println();
+                pw.println("Package warning messages:");
+                final File fname = getSettingsProblemFile();
+                FileInputStream in = null;
+                try {
+                    in = new FileInputStream(fname);
+                    final int avail = in.available();
+                    final byte[] data = new byte[avail];
+                    in.read(data);
+                    pw.print(new String(data));
+                } catch (FileNotFoundException e) {
+                } catch (IOException e) {
+                } finally {
+                    if (in != null) {
+                        try {
+                            in.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    // ------- apps on sdcard specific code -------
+    static final boolean DEBUG_SD_INSTALL = false;
+
+    private static final String SD_ENCRYPTION_KEYSTORE_NAME = "AppsOnSD";
+
+    private static final String SD_ENCRYPTION_ALGORITHM = "AES";
+
+    private boolean mMediaMounted = false;
+
+    private String getEncryptKey() {
+        try {
+            String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(
+                    SD_ENCRYPTION_KEYSTORE_NAME);
+            if (sdEncKey == null) {
+                sdEncKey = SystemKeyStore.getInstance().generateNewKeyHexString(128,
+                        SD_ENCRYPTION_ALGORITHM, SD_ENCRYPTION_KEYSTORE_NAME);
+                if (sdEncKey == null) {
+                    Slog.e(TAG, "Failed to create encryption keys");
+                    return null;
+                }
+            }
+            return sdEncKey;
+        } catch (NoSuchAlgorithmException nsae) {
+            Slog.e(TAG, "Failed to create encryption keys with exception: " + nsae);
+            return null;
+        } catch (IOException ioe) {
+            Slog.e(TAG, "Failed to retrieve encryption keys with exception: " + ioe);
+            return null;
+        }
+
+    }
+
+    /* package */static String getTempContainerId() {
+        int tmpIdx = 1;
+        String list[] = PackageHelper.getSecureContainerList();
+        if (list != null) {
+            for (final String name : list) {
+                // Ignore null and non-temporary container entries
+                if (name == null || !name.startsWith(mTempContainerPrefix)) {
+                    continue;
+                }
+
+                String subStr = name.substring(mTempContainerPrefix.length());
+                try {
+                    int cid = Integer.parseInt(subStr);
+                    if (cid >= tmpIdx) {
+                        tmpIdx = cid + 1;
+                    }
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        return mTempContainerPrefix + tmpIdx;
+    }
+
+    /*
+     * Update media status on PackageManager.
+     */
+    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
+        int callingUid = Binder.getCallingUid();
+        if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
+            throw new SecurityException("Media status can only be updated by the system");
+        }
+        // reader; this apparently protects mMediaMounted, but should probably
+        // be a different lock in that case.
+        synchronized (mPackages) {
+            Log.i(TAG, "Updating external media status from "
+                    + (mMediaMounted ? "mounted" : "unmounted") + " to "
+                    + (mediaStatus ? "mounted" : "unmounted"));
+            if (DEBUG_SD_INSTALL)
+                Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus
+                        + ", mMediaMounted=" + mMediaMounted);
+            if (mediaStatus == mMediaMounted) {
+                final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1
+                        : 0, -1);
+                mHandler.sendMessage(msg);
+                return;
+            }
+            mMediaMounted = mediaStatus;
+        }
+        // Queue up an async operation since the package installation may take a
+        // little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                updateExternalMediaStatusInner(mediaStatus, reportStatus, true);
+            }
+        });
+    }
+
+    /**
+     * Called by MountService when the initial ASECs to scan are available.
+     * Should block until all the ASEC containers are finished being scanned.
+     */
+    public void scanAvailableAsecs() {
+        updateExternalMediaStatusInner(true, false, false);
+    }
+
+    /*
+     * Collect information of applications on external media, map them against
+     * existing containers and update information based on current mount status.
+     * Please note that we always have to report status if reportStatus has been
+     * set to true especially when unloading packages.
+     */
+    private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus,
+            boolean externalStorage) {
+        // Collection of uids
+        int uidArr[] = null;
+        // Collection of stale containers
+        HashSet<String> removeCids = new HashSet<String>();
+        // Collection of packages on external media with valid containers.
+        HashMap<AsecInstallArgs, String> processCids = new HashMap<AsecInstallArgs, String>();
+        // Get list of secure containers.
+        final String list[] = PackageHelper.getSecureContainerList();
+        if (list == null || list.length == 0) {
+            Log.i(TAG, "No secure containers on sdcard");
+        } else {
+            // Process list of secure containers and categorize them
+            // as active or stale based on their package internal state.
+            int uidList[] = new int[list.length];
+            int num = 0;
+            // reader
+            synchronized (mPackages) {
+                for (String cid : list) {
+                    if (DEBUG_SD_INSTALL)
+                        Log.i(TAG, "Processing container " + cid);
+                    String pkgName = getAsecPackageName(cid);
+                    if (pkgName == null) {
+                        if (DEBUG_SD_INSTALL)
+                            Log.i(TAG, "Container : " + cid + " stale");
+                        removeCids.add(cid);
+                        continue;
+                    }
+                    if (DEBUG_SD_INSTALL)
+                        Log.i(TAG, "Looking for pkg : " + pkgName);
+
+                    final PackageSetting ps = mSettings.mPackages.get(pkgName);
+                    if (ps == null) {
+                        Log.i(TAG, "Deleting container with no matching settings " + cid);
+                        removeCids.add(cid);
+                        continue;
+                    }
+
+                    /*
+                     * Skip packages that are not external if we're unmounting
+                     * external storage.
+                     */
+                    if (externalStorage && !isMounted && !isExternal(ps)) {
+                        continue;
+                    }
+
+                    final AsecInstallArgs args = new AsecInstallArgs(cid, isForwardLocked(ps));
+                    // The package status is changed only if the code path
+                    // matches between settings and the container id.
+                    if (ps.codePathString != null && ps.codePathString.equals(args.getCodePath())) {
+                        if (DEBUG_SD_INSTALL) {
+                            Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName
+                                    + " at code path: " + ps.codePathString);
+                        }
+
+                        // We do have a valid package installed on sdcard
+                        processCids.put(args, ps.codePathString);
+                        final int uid = ps.appId;
+                        if (uid != -1) {
+                            uidList[num++] = uid;
+                        }
+                    } else {
+                        Log.i(TAG, "Deleting stale container for " + cid);
+                        removeCids.add(cid);
+                    }
+                }
+            }
+
+            if (num > 0) {
+                // Sort uid list
+                Arrays.sort(uidList, 0, num);
+                // Throw away duplicates
+                uidArr = new int[num];
+                uidArr[0] = uidList[0];
+                int di = 0;
+                for (int i = 1; i < num; i++) {
+                    if (uidList[i - 1] != uidList[i]) {
+                        uidArr[di++] = uidList[i];
+                    }
+                }
+            }
+        }
+        // Process packages with valid entries.
+        if (isMounted) {
+            if (DEBUG_SD_INSTALL)
+                Log.i(TAG, "Loading packages");
+            loadMediaPackages(processCids, uidArr, removeCids);
+            startCleaningPackages();
+        } else {
+            if (DEBUG_SD_INSTALL)
+                Log.i(TAG, "Unloading packages");
+            unloadMediaPackages(processCids, uidArr, reportStatus);
+        }
+    }
+
+   private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
+           ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) {
+        int size = pkgList.size();
+        if (size > 0) {
+            // Send broadcasts here
+            Bundle extras = new Bundle();
+            extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgList
+                    .toArray(new String[size]));
+            if (uidArr != null) {
+                extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr);
+            }
+            if (replacing && !mediaStatus) {
+                extras.putBoolean(Intent.EXTRA_REPLACING, replacing);
+            }
+            String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
+                    : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
+            sendPackageBroadcast(action, null, extras, null, finishedReceiver, null);
+        }
+    }
+
+   /*
+     * Look at potentially valid container ids from processCids If package
+     * information doesn't match the one on record or package scanning fails,
+     * the cid is added to list of removeCids. We currently don't delete stale
+     * containers.
+     */
+   private void loadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[],
+            HashSet<String> removeCids) {
+        ArrayList<String> pkgList = new ArrayList<String>();
+        Set<AsecInstallArgs> keys = processCids.keySet();
+        boolean doGc = false;
+        for (AsecInstallArgs args : keys) {
+            String codePath = processCids.get(args);
+            if (DEBUG_SD_INSTALL)
+                Log.i(TAG, "Loading container : " + args.cid);
+            int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
+            try {
+                // Make sure there are no container errors first.
+                if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
+                    Slog.e(TAG, "Failed to mount cid : " + args.cid
+                            + " when installing from sdcard");
+                    continue;
+                }
+                // Check code path here.
+                if (codePath == null || !codePath.equals(args.getCodePath())) {
+                    Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()
+                            + " does not match one in settings " + codePath);
+                    continue;
+                }
+                // Parse package
+                int parseFlags = mDefParseFlags;
+                if (args.isExternal()) {
+                    parseFlags |= PackageParser.PARSE_ON_SDCARD;
+                }
+                if (args.isFwdLocked()) {
+                    parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
+                }
+
+                doGc = true;
+                synchronized (mInstallLock) {
+                    final PackageParser.Package pkg = scanPackageLI(new File(codePath), parseFlags,
+                            0, 0, null);
+                    // Scan the package
+                    if (pkg != null) {
+                        /*
+                         * TODO why is the lock being held? doPostInstall is
+                         * called in other places without the lock. This needs
+                         * to be straightened out.
+                         */
+                        // writer
+                        synchronized (mPackages) {
+                            retCode = PackageManager.INSTALL_SUCCEEDED;
+                            pkgList.add(pkg.packageName);
+                            // Post process args
+                            args.doPostInstall(PackageManager.INSTALL_SUCCEEDED,
+                                    pkg.applicationInfo.uid);
+                        }
+                    } else {
+                        Slog.i(TAG, "Failed to install pkg from  " + codePath + " from sdcard");
+                    }
+                }
+
+            } finally {
+                if (retCode != PackageManager.INSTALL_SUCCEEDED) {
+                    // Don't destroy container here. Wait till gc clears things
+                    // up.
+                    removeCids.add(args.cid);
+                }
+            }
+        }
+        // writer
+        synchronized (mPackages) {
+            // If the platform SDK has changed since the last time we booted,
+            // we need to re-grant app permission to catch any new ones that
+            // appear. This is really a hack, and means that apps can in some
+            // cases get permissions that the user didn't initially explicitly
+            // allow... it would be nice to have some better way to handle
+            // this situation.
+            final boolean regrantPermissions = mSettings.mExternalSdkPlatform != mSdkVersion;
+            if (regrantPermissions)
+                Slog.i(TAG, "Platform changed from " + mSettings.mExternalSdkPlatform + " to "
+                        + mSdkVersion + "; regranting permissions for external storage");
+            mSettings.mExternalSdkPlatform = mSdkVersion;
+
+            // Make sure group IDs have been assigned, and any permission
+            // changes in other apps are accounted for
+            updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
+                    | (regrantPermissions
+                            ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
+                            : 0));
+            // can downgrade to reader
+            // Persist settings
+            mSettings.writeLPr();
+        }
+        // Send a broadcast to let everyone know we are done processing
+        if (pkgList.size() > 0) {
+            sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
+        }
+        // Force gc to avoid any stale parser references that we might have.
+        if (doGc) {
+            Runtime.getRuntime().gc();
+        }
+        // List stale containers and destroy stale temporary containers.
+        if (removeCids != null) {
+            for (String cid : removeCids) {
+                if (cid.startsWith(mTempContainerPrefix)) {
+                    Log.i(TAG, "Destroying stale temporary container " + cid);
+                    PackageHelper.destroySdDir(cid);
+                } else {
+                    Log.w(TAG, "Container " + cid + " is stale");
+               }
+           }
+        }
+    }
+
+   /*
+     * Utility method to unload a list of specified containers
+     */
+    private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) {
+        // Just unmount all valid containers.
+        for (AsecInstallArgs arg : cidArgs) {
+            synchronized (mInstallLock) {
+                arg.doPostDeleteLI(false);
+           }
+       }
+   }
+
+    /*
+     * Unload packages mounted on external media. This involves deleting package
+     * data from internal structures, sending broadcasts about diabled packages,
+     * gc'ing to free up references, unmounting all secure containers
+     * corresponding to packages on external media, and posting a
+     * UPDATED_MEDIA_STATUS message if status has been requested. Please note
+     * that we always have to post this message if status has been requested no
+     * matter what.
+     */
+    private void unloadMediaPackages(HashMap<AsecInstallArgs, String> processCids, int uidArr[],
+            final boolean reportStatus) {
+        if (DEBUG_SD_INSTALL)
+            Log.i(TAG, "unloading media packages");
+        ArrayList<String> pkgList = new ArrayList<String>();
+        ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>();
+        final Set<AsecInstallArgs> keys = processCids.keySet();
+        for (AsecInstallArgs args : keys) {
+            String pkgName = args.getPackageName();
+            if (DEBUG_SD_INSTALL)
+                Log.i(TAG, "Trying to unload pkg : " + pkgName);
+            // Delete package internally
+            PackageRemovedInfo outInfo = new PackageRemovedInfo();
+            synchronized (mInstallLock) {
+                boolean res = deletePackageLI(pkgName, null, false, null, null,
+                        PackageManager.DELETE_KEEP_DATA, outInfo, false);
+                if (res) {
+                    pkgList.add(pkgName);
+                } else {
+                    Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
+                    failedList.add(args);
+                }
+            }
+        }
+
+        // reader
+        synchronized (mPackages) {
+            // We didn't update the settings after removing each package;
+            // write them now for all packages.
+            mSettings.writeLPr();
+        }
+
+        // We have to absolutely send UPDATED_MEDIA_STATUS only
+        // after confirming that all the receivers processed the ordered
+        // broadcast when packages get disabled, force a gc to clean things up.
+        // and unload all the containers.
+        if (pkgList.size() > 0) {
+            sendResourcesChangedBroadcast(false, false, pkgList, uidArr,
+                    new IIntentReceiver.Stub() {
+                public void performReceive(Intent intent, int resultCode, String data,
+                        Bundle extras, boolean ordered, boolean sticky,
+                        int sendingUser) throws RemoteException {
+                    Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS,
+                            reportStatus ? 1 : 0, 1, keys);
+                    mHandler.sendMessage(msg);
+                }
+            });
+        } else {
+            Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1,
+                    keys);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    /** Binder call */
+    @Override
+    public void movePackage(final String packageName, final IPackageMoveObserver observer,
+            final int flags) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null);
+        UserHandle user = new UserHandle(UserHandle.getCallingUserId());
+        int returnCode = PackageManager.MOVE_SUCCEEDED;
+        int currFlags = 0;
+        int newFlags = 0;
+        // reader
+        synchronized (mPackages) {
+            PackageParser.Package pkg = mPackages.get(packageName);
+            if (pkg == null) {
+                returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+            } else {
+                // Disable moving fwd locked apps and system packages
+                if (pkg.applicationInfo != null && isSystemApp(pkg)) {
+                    Slog.w(TAG, "Cannot move system application");
+                    returnCode = PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
+                } else if (pkg.mOperationPending) {
+                    Slog.w(TAG, "Attempt to move package which has pending operations");
+                    returnCode = PackageManager.MOVE_FAILED_OPERATION_PENDING;
+                } else {
+                    // Find install location first
+                    if ((flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0
+                            && (flags & PackageManager.MOVE_INTERNAL) != 0) {
+                        Slog.w(TAG, "Ambigous flags specified for move location.");
+                        returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+                    } else {
+                        newFlags = (flags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0 ? PackageManager.INSTALL_EXTERNAL
+                                : PackageManager.INSTALL_INTERNAL;
+                        currFlags = isExternal(pkg) ? PackageManager.INSTALL_EXTERNAL
+                                : PackageManager.INSTALL_INTERNAL;
+
+                        if (newFlags == currFlags) {
+                            Slog.w(TAG, "No move required. Trying to move to same location");
+                            returnCode = PackageManager.MOVE_FAILED_INVALID_LOCATION;
+                        } else {
+                            if (isForwardLocked(pkg)) {
+                                currFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+                                newFlags |= PackageManager.INSTALL_FORWARD_LOCK;
+                            }
+                        }
+                    }
+                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                        pkg.mOperationPending = true;
+                    }
+                }
+            }
+
+            /*
+             * TODO this next block probably shouldn't be inside the lock. We
+             * can't guarantee these won't change after this is fired off
+             * anyway.
+             */
+            if (returnCode != PackageManager.MOVE_SUCCEEDED) {
+                processPendingMove(new MoveParams(null, observer, 0, packageName,
+                        null, -1, user),
+                        returnCode);
+            } else {
+                Message msg = mHandler.obtainMessage(INIT_COPY);
+                InstallArgs srcArgs = createInstallArgs(currFlags, pkg.applicationInfo.sourceDir,
+                        pkg.applicationInfo.publicSourceDir, pkg.applicationInfo.nativeLibraryDir);
+                MoveParams mp = new MoveParams(srcArgs, observer, newFlags, packageName,
+                        pkg.applicationInfo.dataDir, pkg.applicationInfo.uid, user);
+                msg.obj = mp;
+                mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    private void processPendingMove(final MoveParams mp, final int currentStatus) {
+        // Queue up an async operation since the package deletion may take a
+        // little while.
+        mHandler.post(new Runnable() {
+            public void run() {
+                // TODO fix this; this does nothing.
+                mHandler.removeCallbacks(this);
+                int returnCode = currentStatus;
+                if (currentStatus == PackageManager.MOVE_SUCCEEDED) {
+                    int uidArr[] = null;
+                    ArrayList<String> pkgList = null;
+                    synchronized (mPackages) {
+                        PackageParser.Package pkg = mPackages.get(mp.packageName);
+                        if (pkg == null) {
+                            Slog.w(TAG, " Package " + mp.packageName
+                                    + " doesn't exist. Aborting move");
+                            returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+                        } else if (!mp.srcArgs.getCodePath().equals(pkg.applicationInfo.sourceDir)) {
+                            Slog.w(TAG, "Package " + mp.packageName + " code path changed from "
+                                    + mp.srcArgs.getCodePath() + " to "
+                                    + pkg.applicationInfo.sourceDir
+                                    + " Aborting move and returning error");
+                            returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR;
+                        } else {
+                            uidArr = new int[] {
+                                pkg.applicationInfo.uid
+                            };
+                            pkgList = new ArrayList<String>();
+                            pkgList.add(mp.packageName);
+                        }
+                    }
+                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                        // Send resources unavailable broadcast
+                        sendResourcesChangedBroadcast(false, true, pkgList, uidArr, null);
+                        // Update package code and resource paths
+                        synchronized (mInstallLock) {
+                            synchronized (mPackages) {
+                                PackageParser.Package pkg = mPackages.get(mp.packageName);
+                                // Recheck for package again.
+                                if (pkg == null) {
+                                    Slog.w(TAG, " Package " + mp.packageName
+                                            + " doesn't exist. Aborting move");
+                                    returnCode = PackageManager.MOVE_FAILED_DOESNT_EXIST;
+                                } else if (!mp.srcArgs.getCodePath().equals(
+                                        pkg.applicationInfo.sourceDir)) {
+                                    Slog.w(TAG, "Package " + mp.packageName
+                                            + " code path changed from " + mp.srcArgs.getCodePath()
+                                            + " to " + pkg.applicationInfo.sourceDir
+                                            + " Aborting move and returning error");
+                                    returnCode = PackageManager.MOVE_FAILED_INTERNAL_ERROR;
+                                } else {
+                                    final String oldCodePath = pkg.mPath;
+                                    final String newCodePath = mp.targetArgs.getCodePath();
+                                    final String newResPath = mp.targetArgs.getResourcePath();
+                                    final String newNativePath = mp.targetArgs
+                                            .getNativeLibraryPath();
+
+                                    final File newNativeDir = new File(newNativePath);
+
+                                    if (!isForwardLocked(pkg) && !isExternal(pkg)) {
+                                        NativeLibraryHelper.copyNativeBinariesIfNeededLI(
+                                                new File(newCodePath), newNativeDir);
+                                    }
+                                    final int[] users = sUserManager.getUserIds();
+                                    for (int user : users) {
+                                        if (mInstaller.linkNativeLibraryDirectory(pkg.packageName,
+                                                newNativePath, user) < 0) {
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                                        pkg.mPath = newCodePath;
+                                        // Move dex files around
+                                        if (moveDexFilesLI(pkg) != PackageManager.INSTALL_SUCCEEDED) {
+                                            // Moving of dex files failed. Set
+                                            // error code and abort move.
+                                            pkg.mPath = pkg.mScanPath;
+                                            returnCode = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                                        }
+                                    }
+
+                                    if (returnCode == PackageManager.MOVE_SUCCEEDED) {
+                                        pkg.mScanPath = newCodePath;
+                                        pkg.applicationInfo.sourceDir = newCodePath;
+                                        pkg.applicationInfo.publicSourceDir = newResPath;
+                                        pkg.applicationInfo.nativeLibraryDir = newNativePath;
+                                        PackageSetting ps = (PackageSetting) pkg.mExtras;
+                                        ps.codePath = new File(pkg.applicationInfo.sourceDir);
+                                        ps.codePathString = ps.codePath.getPath();
+                                        ps.resourcePath = new File(
+                                                pkg.applicationInfo.publicSourceDir);
+                                        ps.resourcePathString = ps.resourcePath.getPath();
+                                        ps.nativeLibraryPathString = newNativePath;
+                                        // Set the application info flag
+                                        // correctly.
+                                        if ((mp.flags & PackageManager.INSTALL_EXTERNAL) != 0) {
+                                            pkg.applicationInfo.flags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+                                        } else {
+                                            pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE;
+                                        }
+                                        ps.setFlags(pkg.applicationInfo.flags);
+                                        mAppDirs.remove(oldCodePath);
+                                        mAppDirs.put(newCodePath, pkg);
+                                        // Persist settings
+                                        mSettings.writeLPr();
+                                    }
+                                }
+                            }
+                        }
+                        // Send resources available broadcast
+                        sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
+                    }
+                }
+                if (returnCode != PackageManager.MOVE_SUCCEEDED) {
+                    // Clean up failed installation
+                    if (mp.targetArgs != null) {
+                        mp.targetArgs.doPostInstall(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
+                                -1);
+                    }
+                } else {
+                    // Force a gc to clear things up.
+                    Runtime.getRuntime().gc();
+                    // Delete older code
+                    synchronized (mInstallLock) {
+                        mp.srcArgs.doPostDeleteLI(true);
+                    }
+                }
+
+                // Allow more operations on this file if we didn't fail because
+                // an operation was already pending for this package.
+                if (returnCode != PackageManager.MOVE_FAILED_OPERATION_PENDING) {
+                    synchronized (mPackages) {
+                        PackageParser.Package pkg = mPackages.get(mp.packageName);
+                        if (pkg != null) {
+                            pkg.mOperationPending = false;
+                       }
+                   }
+                }
+
+                IPackageMoveObserver observer = mp.observer;
+                if (observer != null) {
+                    try {
+                        observer.packageMoved(mp.packageName, returnCode);
+                    } catch (RemoteException e) {
+                        Log.i(TAG, "Observer no longer exists.");
+                    }
+                }
+            }
+        });
+    }
+
+    public boolean setInstallLocation(int loc) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
+                null);
+        if (getInstallLocation() == loc) {
+            return true;
+        }
+        if (loc == PackageHelper.APP_INSTALL_AUTO || loc == PackageHelper.APP_INSTALL_INTERNAL
+                || loc == PackageHelper.APP_INSTALL_EXTERNAL) {
+            android.provider.Settings.Global.putInt(mContext.getContentResolver(),
+                    android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION, loc);
+            return true;
+        }
+        return false;
+   }
+
+    public int getInstallLocation() {
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.DEFAULT_INSTALL_LOCATION,
+                PackageHelper.APP_INSTALL_AUTO);
+    }
+
+    /** Called by UserManagerService */
+    void cleanUpUserLILPw(int userHandle) {
+        mDirtyUsers.remove(userHandle);
+        mSettings.removeUserLPr(userHandle);
+        mPendingBroadcasts.remove(userHandle);
+        if (mInstaller != null) {
+            // Technically, we shouldn't be doing this with the package lock
+            // held.  However, this is very rare, and there is already so much
+            // other disk I/O going on, that we'll let it slide for now.
+            mInstaller.removeUserDataDirs(userHandle);
+        }
+    }
+
+    /** Called by UserManagerService */
+    void createNewUserLILPw(int userHandle, File path) {
+        if (mInstaller != null) {
+            mSettings.createNewUserLILPw(this, mInstaller, userHandle, path);
+        }
+    }
+
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                "Only package verification agents can read the verifier device identity");
+
+        synchronized (mPackages) {
+            return mSettings.getVerifierDeviceIdentityLPw();
+        }
+    }
+
+    @Override
+    public void setPermissionEnforced(String permission, boolean enforced) {
+        mContext.enforceCallingOrSelfPermission(GRANT_REVOKE_PERMISSIONS, null);
+        if (READ_EXTERNAL_STORAGE.equals(permission)) {
+            synchronized (mPackages) {
+                if (mSettings.mReadExternalStorageEnforced == null
+                        || mSettings.mReadExternalStorageEnforced != enforced) {
+                    mSettings.mReadExternalStorageEnforced = enforced;
+                    mSettings.writeLPr();
+                }
+            }
+            // kill any non-foreground processes so we restart them and
+            // grant/revoke the GID.
+            final IActivityManager am = ActivityManagerNative.getDefault();
+            if (am != null) {
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    am.killProcessesBelowForeground("setPermissionEnforcement");
+                } catch (RemoteException e) {
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
+        } else {
+            throw new IllegalArgumentException("No selective enforcement for " + permission);
+        }
+    }
+
+    @Override
+    @Deprecated
+    public boolean isPermissionEnforced(String permission) {
+        return true;
+    }
+
+    @Override
+    public boolean isStorageLow() {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final DeviceStorageMonitorInternal
+                    dsm = LocalServices.getService(DeviceStorageMonitorInternal.class);
+            if (dsm != null) {
+                return dsm.isMemoryLow();
+            } else {
+                return false;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
new file mode 100644
index 0000000..b447861
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageParser;
+
+import java.io.File;
+
+/**
+ * Settings data for a particular package we know about.
+ */
+final class PackageSetting extends PackageSettingBase {
+    int appId;
+    PackageParser.Package pkg;
+    SharedUserSetting sharedUser;
+
+    PackageSetting(String name, String realName, File codePath, File resourcePath,
+            String nativeLibraryPathString, int pVersionCode, int pkgFlags) {
+        super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode,
+                pkgFlags);
+    }
+
+    /**
+     * New instance of PackageSetting replicating the original settings.
+     * Note that it keeps the same PackageParser.Package instance.
+     */
+    PackageSetting(PackageSetting orig) {
+        super(orig);
+
+        appId = orig.appId;
+        pkg = orig.pkg;
+        sharedUser = orig.sharedUser;
+    }
+
+    @Override
+    public String toString() {
+        return "PackageSetting{"
+            + Integer.toHexString(System.identityHashCode(this))
+            + " " + name + "/" + appId + "}";
+    }
+
+    public int[] getGids() {
+        return sharedUser != null ? sharedUser.gids : gids;
+    }
+
+    public boolean isPrivileged() {
+        return (pkgFlags & ApplicationInfo.FLAG_PRIVILEGED) != 0;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
new file mode 100644
index 0000000..7747c8f
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageUserState;
+import android.content.pm.UserInfo;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Settings base class for pending and resolved classes.
+ */
+class PackageSettingBase extends GrantedPermissions {
+    /**
+     * Indicates the state of installation. Used by PackageManager to figure out
+     * incomplete installations. Say a package is being installed (the state is
+     * set to PKG_INSTALL_INCOMPLETE) and remains so till the package
+     * installation is successful or unsuccessful in which case the
+     * PackageManager will no longer maintain state information associated with
+     * the package. If some exception(like device freeze or battery being pulled
+     * out) occurs during installation of a package, the PackageManager needs
+     * this information to clean up the previously failed installation.
+     */
+    static final int PKG_INSTALL_COMPLETE = 1;
+    static final int PKG_INSTALL_INCOMPLETE = 0;
+
+    final String name;
+    final String realName;
+    File codePath;
+    String codePathString;
+    File resourcePath;
+    String resourcePathString;
+    String nativeLibraryPathString;
+    long timeStamp;
+    long firstInstallTime;
+    long lastUpdateTime;
+    int versionCode;
+
+    boolean uidError;
+
+    PackageSignatures signatures = new PackageSignatures();
+
+    boolean permissionsFixed;
+    boolean haveGids;
+
+    PackageKeySetData keySetData = new PackageKeySetData();
+
+    private static final PackageUserState DEFAULT_USER_STATE = new PackageUserState();
+
+    // Whether this package is currently stopped, thus can not be
+    // started until explicitly launched by the user.
+    private final SparseArray<PackageUserState> userState = new SparseArray<PackageUserState>();
+
+    int installStatus = PKG_INSTALL_COMPLETE;
+
+    PackageSettingBase origPackage;
+
+    /* package name of the app that installed this package */
+    String installerPackageName;
+    PackageSettingBase(String name, String realName, File codePath, File resourcePath,
+            String nativeLibraryPathString, int pVersionCode, int pkgFlags) {
+        super(pkgFlags);
+        this.name = name;
+        this.realName = realName;
+        init(codePath, resourcePath, nativeLibraryPathString, pVersionCode);
+    }
+
+    /**
+     * New instance of PackageSetting with one-level-deep cloning.
+     */
+    @SuppressWarnings("unchecked")
+    PackageSettingBase(PackageSettingBase base) {
+        super(base);
+
+        name = base.name;
+        realName = base.realName;
+        codePath = base.codePath;
+        codePathString = base.codePathString;
+        resourcePath = base.resourcePath;
+        resourcePathString = base.resourcePathString;
+        nativeLibraryPathString = base.nativeLibraryPathString;
+        timeStamp = base.timeStamp;
+        firstInstallTime = base.firstInstallTime;
+        lastUpdateTime = base.lastUpdateTime;
+        versionCode = base.versionCode;
+
+        uidError = base.uidError;
+
+        signatures = new PackageSignatures(base.signatures);
+
+        permissionsFixed = base.permissionsFixed;
+        haveGids = base.haveGids;
+        userState.clear();
+        for (int i=0; i<base.userState.size(); i++) {
+            userState.put(base.userState.keyAt(i),
+                    new PackageUserState(base.userState.valueAt(i)));
+        }
+        installStatus = base.installStatus;
+
+        origPackage = base.origPackage;
+
+        installerPackageName = base.installerPackageName;
+
+        keySetData = new PackageKeySetData(base.keySetData);
+
+    }
+
+    void init(File codePath, File resourcePath, String nativeLibraryPathString,
+            int pVersionCode) {
+        this.codePath = codePath;
+        this.codePathString = codePath.toString();
+        this.resourcePath = resourcePath;
+        this.resourcePathString = resourcePath.toString();
+        this.nativeLibraryPathString = nativeLibraryPathString;
+        this.versionCode = pVersionCode;
+    }
+
+    public void setInstallerPackageName(String packageName) {
+        installerPackageName = packageName;
+    }
+
+    String getInstallerPackageName() {
+        return installerPackageName;
+    }
+
+    public void setInstallStatus(int newStatus) {
+        installStatus = newStatus;
+    }
+
+    public int getInstallStatus() {
+        return installStatus;
+    }
+
+    public void setTimeStamp(long newStamp) {
+        timeStamp = newStamp;
+    }
+
+    /**
+     * Make a shallow copy of this package settings.
+     */
+    public void copyFrom(PackageSettingBase base) {
+        grantedPermissions = base.grantedPermissions;
+        gids = base.gids;
+
+        timeStamp = base.timeStamp;
+        firstInstallTime = base.firstInstallTime;
+        lastUpdateTime = base.lastUpdateTime;
+        signatures = base.signatures;
+        permissionsFixed = base.permissionsFixed;
+        haveGids = base.haveGids;
+        userState.clear();
+        for (int i=0; i<base.userState.size(); i++) {
+            userState.put(base.userState.keyAt(i), base.userState.valueAt(i));
+        }
+        installStatus = base.installStatus;
+        keySetData = base.keySetData;
+    }
+
+    private PackageUserState modifyUserState(int userId) {
+        PackageUserState state = userState.get(userId);
+        if (state == null) {
+            state = new PackageUserState();
+            userState.put(userId, state);
+        }
+        return state;
+    }
+
+    public PackageUserState readUserState(int userId) {
+        PackageUserState state = userState.get(userId);
+        if (state != null) {
+            return state;
+        }
+        return DEFAULT_USER_STATE;
+    }
+
+    void setEnabled(int state, int userId, String callingPackage) {
+        PackageUserState st = modifyUserState(userId);
+        st.enabled = state;
+        st.lastDisableAppCaller = callingPackage;
+    }
+
+    int getEnabled(int userId) {
+        return readUserState(userId).enabled;
+    }
+
+    String getLastDisabledAppCaller(int userId) {
+        return readUserState(userId).lastDisableAppCaller;
+    }
+
+    void setInstalled(boolean inst, int userId) {
+        modifyUserState(userId).installed = inst;
+    }
+
+    boolean getInstalled(int userId) {
+        return readUserState(userId).installed;
+    }
+
+    boolean isAnyInstalled(int[] users) {
+        for (int user: users) {
+            if (readUserState(user).installed) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    int[] queryInstalledUsers(int[] users, boolean installed) {
+        int num = 0;
+        for (int user : users) {
+            if (getInstalled(user) == installed) {
+                num++;
+            }
+        }
+        int[] res = new int[num];
+        num = 0;
+        for (int user : users) {
+            if (getInstalled(user) == installed) {
+                res[num] = user;
+                num++;
+            }
+        }
+        return res;
+    }
+
+    boolean getStopped(int userId) {
+        return readUserState(userId).stopped;
+    }
+
+    void setStopped(boolean stop, int userId) {
+        modifyUserState(userId).stopped = stop;
+    }
+
+    boolean getNotLaunched(int userId) {
+        return readUserState(userId).notLaunched;
+    }
+
+    void setNotLaunched(boolean stop, int userId) {
+        modifyUserState(userId).notLaunched = stop;
+    }
+
+    boolean getBlocked(int userId) {
+        return readUserState(userId).blocked;
+    }
+
+    void setBlocked(boolean blocked, int userId) {
+        modifyUserState(userId).blocked = blocked;
+    }
+
+    void setUserState(int userId, int enabled, boolean installed, boolean stopped,
+            boolean notLaunched, boolean blocked,
+            String lastDisableAppCaller, HashSet<String> enabledComponents,
+            HashSet<String> disabledComponents) {
+        PackageUserState state = modifyUserState(userId);
+        state.enabled = enabled;
+        state.installed = installed;
+        state.stopped = stopped;
+        state.notLaunched = notLaunched;
+        state.blocked = blocked;
+        state.lastDisableAppCaller = lastDisableAppCaller;
+        state.enabledComponents = enabledComponents;
+        state.disabledComponents = disabledComponents;
+    }
+
+    HashSet<String> getEnabledComponents(int userId) {
+        return readUserState(userId).enabledComponents;
+    }
+
+    HashSet<String> getDisabledComponents(int userId) {
+        return readUserState(userId).disabledComponents;
+    }
+
+    void setEnabledComponents(HashSet<String> components, int userId) {
+        modifyUserState(userId).enabledComponents = components;
+    }
+
+    void setDisabledComponents(HashSet<String> components, int userId) {
+        modifyUserState(userId).disabledComponents = components;
+    }
+
+    void setEnabledComponentsCopy(HashSet<String> components, int userId) {
+        modifyUserState(userId).enabledComponents = components != null
+                ? new HashSet<String>(components) : null;
+    }
+
+    void setDisabledComponentsCopy(HashSet<String> components, int userId) {
+        modifyUserState(userId).disabledComponents = components != null
+                ? new HashSet<String>(components) : null;
+    }
+
+    PackageUserState modifyUserStateComponents(int userId, boolean disabled, boolean enabled) {
+        PackageUserState state = modifyUserState(userId);
+        if (disabled && state.disabledComponents == null) {
+            state.disabledComponents = new HashSet<String>(1);
+        }
+        if (enabled && state.enabledComponents == null) {
+            state.enabledComponents = new HashSet<String>(1);
+        }
+        return state;
+    }
+
+    void addDisabledComponent(String componentClassName, int userId) {
+        modifyUserStateComponents(userId, true, false).disabledComponents.add(componentClassName);
+    }
+
+    void addEnabledComponent(String componentClassName, int userId) {
+        modifyUserStateComponents(userId, false, true).enabledComponents.add(componentClassName);
+    }
+
+    boolean enableComponentLPw(String componentClassName, int userId) {
+        PackageUserState state = modifyUserStateComponents(userId, false, true);
+        boolean changed = state.disabledComponents != null
+                ? state.disabledComponents.remove(componentClassName) : false;
+        changed |= state.enabledComponents.add(componentClassName);
+        return changed;
+    }
+
+    boolean disableComponentLPw(String componentClassName, int userId) {
+        PackageUserState state = modifyUserStateComponents(userId, true, false);
+        boolean changed = state.enabledComponents != null
+                ? state.enabledComponents.remove(componentClassName) : false;
+        changed |= state.disabledComponents.add(componentClassName);
+        return changed;
+    }
+
+    boolean restoreComponentLPw(String componentClassName, int userId) {
+        PackageUserState state = modifyUserStateComponents(userId, true, true);
+        boolean changed = state.disabledComponents != null
+                ? state.disabledComponents.remove(componentClassName) : false;
+        changed |= state.enabledComponents != null
+                ? state.enabledComponents.remove(componentClassName) : false;
+        return changed;
+    }
+
+    int getCurrentEnabledStateLPr(String componentName, int userId) {
+        PackageUserState state = readUserState(userId);
+        if (state.enabledComponents != null && state.enabledComponents.contains(componentName)) {
+            return COMPONENT_ENABLED_STATE_ENABLED;
+        } else if (state.disabledComponents != null
+                && state.disabledComponents.contains(componentName)) {
+            return COMPONENT_ENABLED_STATE_DISABLED;
+        } else {
+            return COMPONENT_ENABLED_STATE_DEFAULT;
+        }
+    }
+
+    void removeUser(int userId) {
+        userState.delete(userId);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageSignatures.java b/services/core/java/com/android/server/pm/PackageSignatures.java
new file mode 100644
index 0000000..9a20be7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageSignatures.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.pm.Signature;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+class PackageSignatures {
+    Signature[] mSignatures;
+
+    PackageSignatures(PackageSignatures orig) {
+        if (orig != null && orig.mSignatures != null) {
+            mSignatures = orig.mSignatures.clone();
+        }
+    }
+
+    PackageSignatures(Signature[] sigs) {
+        assignSignatures(sigs);
+    }
+
+    PackageSignatures() {
+    }
+
+    void writeXml(XmlSerializer serializer, String tagName,
+            ArrayList<Signature> pastSignatures) throws IOException {
+        if (mSignatures == null) {
+            return;
+        }
+        serializer.startTag(null, tagName);
+        serializer.attribute(null, "count",
+                Integer.toString(mSignatures.length));
+        for (int i=0; i<mSignatures.length; i++) {
+            serializer.startTag(null, "cert");
+            final Signature sig = mSignatures[i];
+            final int sigHash = sig.hashCode();
+            final int numPast = pastSignatures.size();
+            int j;
+            for (j=0; j<numPast; j++) {
+                Signature pastSig = pastSignatures.get(j);
+                if (pastSig.hashCode() == sigHash && pastSig.equals(sig)) {
+                    serializer.attribute(null, "index", Integer.toString(j));
+                    break;
+                }
+            }
+            if (j >= numPast) {
+                pastSignatures.add(sig);
+                serializer.attribute(null, "index", Integer.toString(numPast));
+                serializer.attribute(null, "key", sig.toCharsString());
+            }
+            serializer.endTag(null, "cert");
+        }
+        serializer.endTag(null, tagName);
+    }
+
+    void readXml(XmlPullParser parser, ArrayList<Signature> pastSignatures)
+            throws IOException, XmlPullParserException {
+        String countStr = parser.getAttributeValue(null, "count");
+        if (countStr == null) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: <signatures> has"
+                       + " no count at " + parser.getPositionDescription());
+            XmlUtils.skipCurrentTag(parser);
+        }
+        final int count = Integer.parseInt(countStr);
+        mSignatures = new Signature[count];
+        int pos = 0;
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("cert")) {
+                if (pos < count) {
+                    String index = parser.getAttributeValue(null, "index");
+                    if (index != null) {
+                        try {
+                            int idx = Integer.parseInt(index);
+                            String key = parser.getAttributeValue(null, "key");
+                            if (key == null) {
+                                if (idx >= 0 && idx < pastSignatures.size()) {
+                                    Signature sig = pastSignatures.get(idx);
+                                    if (sig != null) {
+                                        mSignatures[pos] = pastSignatures.get(idx);
+                                        pos++;
+                                    } else {
+                                        PackageManagerService.reportSettingsProblem(Log.WARN,
+                                                "Error in package manager settings: <cert> "
+                                                   + "index " + index + " is not defined at "
+                                                   + parser.getPositionDescription());
+                                    }
+                                } else {
+                                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                                            "Error in package manager settings: <cert> "
+                                               + "index " + index + " is out of bounds at "
+                                               + parser.getPositionDescription());
+                                }
+                            } else {
+                                while (pastSignatures.size() <= idx) {
+                                    pastSignatures.add(null);
+                                }
+                                Signature sig = new Signature(key);
+                                pastSignatures.set(idx, sig);
+                                mSignatures[pos] = sig;
+                                pos++;
+                            }
+                        } catch (NumberFormatException e) {
+                            PackageManagerService.reportSettingsProblem(Log.WARN,
+                                    "Error in package manager settings: <cert> "
+                                       + "index " + index + " is not a number at "
+                                       + parser.getPositionDescription());
+                        } catch (IllegalArgumentException e) {
+                            PackageManagerService.reportSettingsProblem(Log.WARN,
+                                    "Error in package manager settings: <cert> "
+                                       + "index " + index + " has an invalid signature at "
+                                       + parser.getPositionDescription() + ": "
+                                       + e.getMessage());
+                        }
+                    } else {
+                        PackageManagerService.reportSettingsProblem(Log.WARN,
+                                "Error in package manager settings: <cert> has"
+                                   + " no index at " + parser.getPositionDescription());
+                    }
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: too "
+                               + "many <cert> tags, expected " + count
+                               + " at " + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <cert>: "
+                        + parser.getName());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+
+        if (pos < count) {
+            // Should never happen -- there is an error in the written
+            // settings -- but if it does we don't want to generate
+            // a bad array.
+            Signature[] newSigs = new Signature[pos];
+            System.arraycopy(mSignatures, 0, newSigs, 0, pos);
+            mSignatures = newSigs;
+        }
+    }
+
+    void assignSignatures(Signature[] sigs) {
+        if (sigs == null) {
+            mSignatures = null;
+            return;
+        }
+        mSignatures = new Signature[sigs.length];
+        for (int i=0; i<sigs.length; i++) {
+            mSignatures[i] = sigs[i];
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer buf = new StringBuffer(128);
+        buf.append("PackageSignatures{");
+        buf.append(Integer.toHexString(System.identityHashCode(this)));
+        buf.append(" [");
+        if (mSignatures != null) {
+            for (int i=0; i<mSignatures.length; i++) {
+                if (i > 0) buf.append(", ");
+                buf.append(Integer.toHexString(
+                        System.identityHashCode(mSignatures[i])));
+            }
+        }
+        buf.append("]}");
+        return buf.toString();
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/PackageVerificationResponse.java b/services/core/java/com/android/server/pm/PackageVerificationResponse.java
new file mode 100644
index 0000000..b2ae0dd
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageVerificationResponse.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+public class PackageVerificationResponse {
+    public final int code;
+
+    public final int callerUid;
+
+    public PackageVerificationResponse(int code, int callerUid) {
+        this.code = code;
+        this.callerUid = callerUid;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageVerificationState.java b/services/core/java/com/android/server/pm/PackageVerificationState.java
new file mode 100644
index 0000000..3214e88
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PackageVerificationState.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.pm.PackageManagerService.InstallArgs;
+
+import android.content.pm.PackageManager;
+import android.util.SparseBooleanArray;
+
+/**
+ * Tracks the package verification state for a particular package. Each package
+ * verification has a required verifier and zero or more sufficient verifiers.
+ * Only one of the sufficient verifier list must return affirmative to allow the
+ * package to be considered verified. If there are zero sufficient verifiers,
+ * then package verification is considered complete.
+ */
+class PackageVerificationState {
+    private final InstallArgs mArgs;
+
+    private final SparseBooleanArray mSufficientVerifierUids;
+
+    private final int mRequiredVerifierUid;
+
+    private boolean mSufficientVerificationComplete;
+
+    private boolean mSufficientVerificationPassed;
+
+    private boolean mRequiredVerificationComplete;
+
+    private boolean mRequiredVerificationPassed;
+
+    private boolean mExtendedTimeout;
+
+    /**
+     * Create a new package verification state where {@code requiredVerifierUid}
+     * is the user ID for the package that must reply affirmative before things
+     * can continue.
+     *
+     * @param requiredVerifierUid user ID of required package verifier
+     * @param args
+     */
+    public PackageVerificationState(int requiredVerifierUid, InstallArgs args) {
+        mRequiredVerifierUid = requiredVerifierUid;
+        mArgs = args;
+        mSufficientVerifierUids = new SparseBooleanArray();
+        mExtendedTimeout = false;
+    }
+
+    public InstallArgs getInstallArgs() {
+        return mArgs;
+    }
+
+    /**
+     * Add a verifier which is added to our sufficient list.
+     *
+     * @param uid user ID of sufficient verifier
+     */
+    public void addSufficientVerifier(int uid) {
+        mSufficientVerifierUids.put(uid, true);
+    }
+
+    /**
+     * Should be called when a verification is received from an agent so the
+     * state of the package verification can be tracked.
+     *
+     * @param uid user ID of the verifying agent
+     * @return {@code true} if the verifying agent actually exists in our list
+     */
+    public boolean setVerifierResponse(int uid, int code) {
+        if (uid == mRequiredVerifierUid) {
+            mRequiredVerificationComplete = true;
+            switch (code) {
+                case PackageManager.VERIFICATION_ALLOW_WITHOUT_SUFFICIENT:
+                    mSufficientVerifierUids.clear();
+                    // fall through
+                case PackageManager.VERIFICATION_ALLOW:
+                    mRequiredVerificationPassed = true;
+                    break;
+                default:
+                    mRequiredVerificationPassed = false;
+            }
+            return true;
+        } else {
+            if (mSufficientVerifierUids.get(uid)) {
+                if (code == PackageManager.VERIFICATION_ALLOW) {
+                    mSufficientVerificationComplete = true;
+                    mSufficientVerificationPassed = true;
+                }
+
+                mSufficientVerifierUids.delete(uid);
+                if (mSufficientVerifierUids.size() == 0) {
+                    mSufficientVerificationComplete = true;
+                }
+
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns whether verification is considered complete. This means that the
+     * required verifier and at least one of the sufficient verifiers has
+     * returned a positive verification.
+     *
+     * @return {@code true} when verification is considered complete
+     */
+    public boolean isVerificationComplete() {
+        if (!mRequiredVerificationComplete) {
+            return false;
+        }
+
+        if (mSufficientVerifierUids.size() == 0) {
+            return true;
+        }
+
+        return mSufficientVerificationComplete;
+    }
+
+    /**
+     * Returns whether installation should be allowed. This should only be
+     * called after {@link #isVerificationComplete()} returns {@code true}.
+     *
+     * @return {@code true} if installation should be allowed
+     */
+    public boolean isInstallAllowed() {
+        if (!mRequiredVerificationPassed) {
+            return false;
+        }
+
+        if (mSufficientVerificationComplete) {
+            return mSufficientVerificationPassed;
+        }
+
+        return true;
+    }
+
+    /**
+     * Extend the timeout for this Package to be verified.
+     */
+    public void extendTimeout() {
+        if (!mExtendedTimeout) {
+            mExtendedTimeout = true;
+        }
+    }
+
+    /**
+     * Returns whether the timeout was extended for verification.
+     *
+     * @return {@code true} if a timeout was already extended.
+     */
+    public boolean timeoutExtended() {
+        return mExtendedTimeout;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PendingPackage.java b/services/core/java/com/android/server/pm/PendingPackage.java
new file mode 100644
index 0000000..c17cc46
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PendingPackage.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.io.File;
+
+final class PendingPackage extends PackageSettingBase {
+    final int sharedId;
+
+    PendingPackage(String name, String realName, File codePath, File resourcePath,
+            String nativeLibraryPathString, int sharedId, int pVersionCode, int pkgFlags) {
+        super(name, realName, codePath, resourcePath, nativeLibraryPathString, pVersionCode,
+                pkgFlags);
+        this.sharedId = sharedId;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PreferredActivity.java b/services/core/java/com/android/server/pm/PreferredActivity.java
new file mode 100644
index 0000000..8916926
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PreferredActivity.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import java.io.IOException;
+
+class PreferredActivity extends IntentFilter implements PreferredComponent.Callbacks {
+    private static final String TAG = "PreferredActivity";
+
+    private static final boolean DEBUG_FILTERS = false;
+
+    final PreferredComponent mPref;
+
+    PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity,
+            boolean always) {
+        super(filter);
+        mPref = new PreferredComponent(this, match, set, activity, always);
+    }
+
+    PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException {
+        mPref = new PreferredComponent(this, parser);
+    }
+
+    public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
+        mPref.writeToXml(serializer, full);
+        serializer.startTag(null, "filter");
+            super.writeToXml(serializer);
+        serializer.endTag(null, "filter");
+    }
+
+    public boolean onReadTag(String tagName, XmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        if (tagName.equals("filter")) {
+            if (DEBUG_FILTERS) {
+                Log.i(TAG, "Starting to parse filter...");
+            }
+            readFromXml(parser);
+            if (DEBUG_FILTERS) {
+                Log.i(TAG, "Finished filter: depth=" + parser.getDepth() + " tag="
+                        + parser.getName());
+            }
+        } else {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Unknown element under <preferred-activities>: " + parser.getName());
+            XmlUtils.skipCurrentTag(parser);
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "PreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this))
+                + " " + mPref.mComponent.flattenToShortString() + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PreferredComponent.java b/services/core/java/com/android/server/pm/PreferredComponent.java
new file mode 100644
index 0000000..f437372
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PreferredComponent.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+public class PreferredComponent {
+    private static final String TAG_SET = "set";
+    private static final String ATTR_ALWAYS = "always"; // boolean
+    private static final String ATTR_MATCH = "match"; // number
+    private static final String ATTR_NAME = "name"; // component name
+    private static final String ATTR_SET = "set"; // number
+
+    public final int mMatch;
+    public final ComponentName mComponent;
+    // Whether this is to be the one that's always chosen. If false, it's the most recently chosen.
+    public boolean mAlways;
+
+    private final String[] mSetPackages;
+    private final String[] mSetClasses;
+    private final String[] mSetComponents;
+    private final String mShortComponent;
+    private String mParseError;
+
+    private final Callbacks mCallbacks;
+
+    public interface Callbacks {
+        public boolean onReadTag(String tagName, XmlPullParser parser)
+                throws XmlPullParserException, IOException;
+    }
+
+    public PreferredComponent(Callbacks callbacks, int match, ComponentName[] set,
+            ComponentName component, boolean always) {
+        mCallbacks = callbacks;
+        mMatch = match&IntentFilter.MATCH_CATEGORY_MASK;
+        mComponent = component;
+        mAlways = always;
+        mShortComponent = component.flattenToShortString();
+        mParseError = null;
+        if (set != null) {
+            final int N = set.length;
+            String[] myPackages = new String[N];
+            String[] myClasses = new String[N];
+            String[] myComponents = new String[N];
+            for (int i=0; i<N; i++) {
+                ComponentName cn = set[i];
+                if (cn == null) {
+                    mSetPackages = null;
+                    mSetClasses = null;
+                    mSetComponents = null;
+                    return;
+                }
+                myPackages[i] = cn.getPackageName().intern();
+                myClasses[i] = cn.getClassName().intern();
+                myComponents[i] = cn.flattenToShortString();
+            }
+            mSetPackages = myPackages;
+            mSetClasses = myClasses;
+            mSetComponents = myComponents;
+        } else {
+            mSetPackages = null;
+            mSetClasses = null;
+            mSetComponents = null;
+        }
+    }
+
+    public PreferredComponent(Callbacks callbacks, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        mCallbacks = callbacks;
+        mShortComponent = parser.getAttributeValue(null, ATTR_NAME);
+        mComponent = ComponentName.unflattenFromString(mShortComponent);
+        if (mComponent == null) {
+            mParseError = "Bad activity name " + mShortComponent;
+        }
+        String matchStr = parser.getAttributeValue(null, ATTR_MATCH);
+        mMatch = matchStr != null ? Integer.parseInt(matchStr, 16) : 0;
+        String setCountStr = parser.getAttributeValue(null, ATTR_SET);
+        int setCount = setCountStr != null ? Integer.parseInt(setCountStr) : 0;
+        String alwaysStr = parser.getAttributeValue(null, ATTR_ALWAYS);
+        mAlways = alwaysStr != null ? Boolean.parseBoolean(alwaysStr) : true;
+
+        String[] myPackages = setCount > 0 ? new String[setCount] : null;
+        String[] myClasses = setCount > 0 ? new String[setCount] : null;
+        String[] myComponents = setCount > 0 ? new String[setCount] : null;
+
+        int setPos = 0;
+
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                       || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            //Log.i(TAG, "Parse outerDepth=" + outerDepth + " depth="
+            //        + parser.getDepth() + " tag=" + tagName);
+            if (tagName.equals(TAG_SET)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name == null) {
+                    if (mParseError == null) {
+                        mParseError = "No name in set tag in preferred activity "
+                            + mShortComponent;
+                    }
+                } else if (setPos >= setCount) {
+                    if (mParseError == null) {
+                        mParseError = "Too many set tags in preferred activity "
+                            + mShortComponent;
+                    }
+                } else {
+                    ComponentName cn = ComponentName.unflattenFromString(name);
+                    if (cn == null) {
+                        if (mParseError == null) {
+                            mParseError = "Bad set name " + name + " in preferred activity "
+                                + mShortComponent;
+                        }
+                    } else {
+                        myPackages[setPos] = cn.getPackageName();
+                        myClasses[setPos] = cn.getClassName();
+                        myComponents[setPos] = name;
+                        setPos++;
+                    }
+                }
+                XmlUtils.skipCurrentTag(parser);
+            } else if (!mCallbacks.onReadTag(tagName, parser)) {
+                Slog.w("PreferredComponent", "Unknown element: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        if (setPos != setCount) {
+            if (mParseError == null) {
+                mParseError = "Not enough set tags (expected " + setCount
+                    + " but found " + setPos + ") in " + mShortComponent;
+            }
+        }
+
+        mSetPackages = myPackages;
+        mSetClasses = myClasses;
+        mSetComponents = myComponents;
+    }
+
+    public String getParseError() {
+        return mParseError;
+    }
+
+    public void writeToXml(XmlSerializer serializer, boolean full) throws IOException {
+        final int NS = mSetClasses != null ? mSetClasses.length : 0;
+        serializer.attribute(null, ATTR_NAME, mShortComponent);
+        if (full) {
+            if (mMatch != 0) {
+                serializer.attribute(null, ATTR_MATCH, Integer.toHexString(mMatch));
+            }
+            serializer.attribute(null, ATTR_ALWAYS, Boolean.toString(mAlways));
+            serializer.attribute(null, ATTR_SET, Integer.toString(NS));
+            for (int s=0; s<NS; s++) {
+                serializer.startTag(null, TAG_SET);
+                serializer.attribute(null, ATTR_NAME, mSetComponents[s]);
+                serializer.endTag(null, TAG_SET);
+            }
+        }
+    }
+
+    public boolean sameSet(List<ResolveInfo> query, int priority) {
+        if (mSetPackages == null) return false;
+        final int NQ = query.size();
+        final int NS = mSetPackages.length;
+        int numMatch = 0;
+        for (int i=0; i<NQ; i++) {
+            ResolveInfo ri = query.get(i);
+            if (ri.priority != priority) continue;
+            ActivityInfo ai = ri.activityInfo;
+            boolean good = false;
+            for (int j=0; j<NS; j++) {
+                if (mSetPackages[j].equals(ai.packageName)
+                        && mSetClasses[j].equals(ai.name)) {
+                    numMatch++;
+                    good = true;
+                    break;
+                }
+            }
+            if (!good) return false;
+        }
+        return numMatch == NS;
+    }
+
+    public void dump(PrintWriter out, String prefix, Object ident) {
+        out.print(prefix); out.print(
+                Integer.toHexString(System.identityHashCode(ident)));
+                out.print(' ');
+                out.println(mShortComponent);
+        out.print(prefix); out.print(" mMatch=0x");
+                out.print(Integer.toHexString(mMatch));
+                out.print(" mAlways="); out.println(mAlways);
+        if (mSetComponents != null) {
+            out.print(prefix); out.println("  Selected from:");
+            for (int i=0; i<mSetComponents.length; i++) {
+                out.print(prefix); out.print("    ");
+                        out.println(mSetComponents[i]);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PreferredIntentResolver.java b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
new file mode 100644
index 0000000..bce24d7
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PreferredIntentResolver.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.io.PrintWriter;
+
+import com.android.server.IntentResolver;
+
+public class PreferredIntentResolver
+        extends IntentResolver<PreferredActivity, PreferredActivity> {
+    @Override
+    protected PreferredActivity[] newArray(int size) {
+        return new PreferredActivity[size];
+    }
+
+    @Override
+    protected boolean isPackageForFilter(String packageName, PreferredActivity filter) {
+        return packageName.equals(filter.mPref.mComponent.getPackageName());
+    }
+
+    @Override
+    protected void dumpFilter(PrintWriter out, String prefix,
+            PreferredActivity filter) {
+        filter.mPref.dump(out, prefix, filter);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
new file mode 100644
index 0000000..1d68afa
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageParser;
+import android.content.pm.Signature;
+import android.os.Environment;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+/**
+ * Centralized access to SELinux MMAC (middleware MAC) implementation.
+ * {@hide}
+ */
+public final class SELinuxMMAC {
+
+    private static final String TAG = "SELinuxMMAC";
+
+    private static final boolean DEBUG_POLICY = false;
+    private static final boolean DEBUG_POLICY_INSTALL = DEBUG_POLICY || false;
+
+    // Signature seinfo values read from policy.
+    private static HashMap<Signature, Policy> sSigSeinfo =
+        new HashMap<Signature, Policy>();
+
+    // Default seinfo read from policy.
+    private static String sDefaultSeinfo = null;
+
+    // Locations of potential install policy files.
+    private static final File[] INSTALL_POLICY_FILE = {
+        new File(Environment.getDataDirectory(), "security/mac_permissions.xml"),
+        new File(Environment.getRootDirectory(), "etc/security/mac_permissions.xml"),
+        null};
+
+    // Signature policy stanzas
+    static class Policy {
+        private String seinfo;
+        private final HashMap<String, String> pkgMap;
+
+        Policy() {
+            seinfo = null;
+            pkgMap = new HashMap<String, String>();
+        }
+
+        void putSeinfo(String seinfoValue) {
+            seinfo = seinfoValue;
+        }
+
+        void putPkg(String pkg, String seinfoValue) {
+            pkgMap.put(pkg, seinfoValue);
+        }
+
+        // Valid policy stanza means there exists a global
+        // seinfo value or at least one package policy.
+        boolean isValid() {
+            return (seinfo != null) || (!pkgMap.isEmpty());
+        }
+
+        String checkPolicy(String pkgName) {
+            // Check for package name seinfo value first.
+            String seinfoValue = pkgMap.get(pkgName);
+            if (seinfoValue != null) {
+                return seinfoValue;
+            }
+
+            // Return the global seinfo value.
+            return seinfo;
+        }
+    }
+
+    private static void flushInstallPolicy() {
+        sSigSeinfo.clear();
+        sDefaultSeinfo = null;
+    }
+
+    /**
+     * Parses an MMAC install policy from a predefined list of locations.
+     * @param none
+     * @return boolean indicating whether an install policy was correctly parsed.
+     */
+    public static boolean readInstallPolicy() {
+
+        return readInstallPolicy(INSTALL_POLICY_FILE);
+    }
+
+    /**
+     * Parses an MMAC install policy given as an argument.
+     * @param File object representing the path of the policy.
+     * @return boolean indicating whether the install policy was correctly parsed.
+     */
+    public static boolean readInstallPolicy(File policyFile) {
+
+        return readInstallPolicy(new File[]{policyFile,null});
+    }
+
+    private static boolean readInstallPolicy(File[] policyFiles) {
+        // Temp structures to hold the rules while we parse the xml file.
+        // We add all the rules together once we know there's no structural problems.
+        HashMap<Signature, Policy> sigSeinfo = new HashMap<Signature, Policy>();
+        String defaultSeinfo = null;
+
+        FileReader policyFile = null;
+        int i = 0;
+        while (policyFile == null && policyFiles != null && policyFiles[i] != null) {
+            try {
+                policyFile = new FileReader(policyFiles[i]);
+                break;
+            } catch (FileNotFoundException e) {
+                Slog.d(TAG,"Couldn't find install policy " + policyFiles[i].getPath());
+            }
+            i++;
+        }
+
+        if (policyFile == null) {
+            Slog.d(TAG, "No policy file found. All seinfo values will be null.");
+            return false;
+        }
+
+        Slog.d(TAG, "Using install policy file " + policyFiles[i].getPath());
+
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(policyFile);
+
+            XmlUtils.beginDocument(parser, "policy");
+            while (true) {
+                XmlUtils.nextElement(parser);
+                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+                    break;
+                }
+
+                String tagName = parser.getName();
+                if ("signer".equals(tagName)) {
+                    String cert = parser.getAttributeValue(null, "signature");
+                    if (cert == null) {
+                        Slog.w(TAG, "<signer> without signature at "
+                               + parser.getPositionDescription());
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    Signature signature;
+                    try {
+                        signature = new Signature(cert);
+                    } catch (IllegalArgumentException e) {
+                        Slog.w(TAG, "<signer> with bad signature at "
+                               + parser.getPositionDescription(), e);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    Policy policy = readPolicyTags(parser);
+                    if (policy.isValid()) {
+                        sigSeinfo.put(signature, policy);
+                    }
+                } else if ("default".equals(tagName)) {
+                    // Value is null if default tag is absent or seinfo tag is malformed.
+                    defaultSeinfo = readSeinfoTag(parser);
+                    if (DEBUG_POLICY_INSTALL)
+                        Slog.i(TAG, "<default> tag assigned seinfo=" + defaultSeinfo);
+
+                } else {
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+        } catch (XmlPullParserException e) {
+            // An error outside of a stanza means a structural problem
+            // with the xml file. So ignore it.
+            Slog.w(TAG, "Got exception parsing ", e);
+            return false;
+        } catch (IOException e) {
+            Slog.w(TAG, "Got exception parsing ", e);
+            return false;
+        } finally {
+            try {
+                policyFile.close();
+            } catch (IOException e) {
+                //omit
+            }
+        }
+
+        flushInstallPolicy();
+        sSigSeinfo = sigSeinfo;
+        sDefaultSeinfo = defaultSeinfo;
+
+        return true;
+    }
+
+    private static Policy readPolicyTags(XmlPullParser parser) throws
+            IOException, XmlPullParserException {
+
+        int type;
+        int outerDepth = parser.getDepth();
+        Policy policy = new Policy();
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                   || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if ("seinfo".equals(tagName)) {
+                String seinfo = parseSeinfo(parser);
+                if (seinfo != null) {
+                    policy.putSeinfo(seinfo);
+                }
+                XmlUtils.skipCurrentTag(parser);
+            } else if ("package".equals(tagName)) {
+                String pkg = parser.getAttributeValue(null, "name");
+                if (!validatePackageName(pkg)) {
+                    Slog.w(TAG, "<package> without valid name at "
+                           + parser.getPositionDescription());
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+                String seinfo = readSeinfoTag(parser);
+                if (seinfo != null) {
+                    policy.putPkg(pkg, seinfo);
+                }
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        return policy;
+    }
+
+    private static String readSeinfoTag(XmlPullParser parser) throws
+            IOException, XmlPullParserException {
+
+        int type;
+        int outerDepth = parser.getDepth();
+        String seinfo = null;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG
+                   || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if ("seinfo".equals(tagName)) {
+                seinfo = parseSeinfo(parser);
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+        return seinfo;
+    }
+
+    private static String parseSeinfo(XmlPullParser parser) {
+
+        String seinfoValue = parser.getAttributeValue(null, "value");
+        if (!validateValue(seinfoValue)) {
+            Slog.w(TAG, "<seinfo> without valid value at "
+                   + parser.getPositionDescription());
+            seinfoValue = null;
+        }
+        return seinfoValue;
+    }
+
+    /**
+     * General validation routine for package names.
+     * Returns a boolean indicating if the passed string
+     * is a valid android package name.
+     */
+    private static boolean validatePackageName(String name) {
+        if (name == null)
+            return false;
+
+        final int N = name.length();
+        boolean hasSep = false;
+        boolean front = true;
+        for (int i=0; i<N; i++) {
+            final char c = name.charAt(i);
+            if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                front = false;
+                continue;
+            }
+            if (!front) {
+                if ((c >= '0' && c <= '9') || c == '_') {
+                    continue;
+                }
+            }
+            if (c == '.') {
+                hasSep = true;
+                front = true;
+                continue;
+            }
+            return false;
+        }
+        return hasSep;
+    }
+
+    /**
+     * General validation routine for tag values.
+     * Returns a boolean indicating if the passed string
+     * contains only letters or underscores.
+     */
+    private static boolean validateValue(String name) {
+        if (name == null)
+            return false;
+
+        final int N = name.length();
+        if (N == 0)
+            return false;
+
+        for (int i = 0; i < N; i++) {
+            final char c = name.charAt(i);
+            if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c != '_')) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Labels a package based on an seinfo tag from install policy.
+     * The label is attached to the ApplicationInfo instance of the package.
+     * @param PackageParser.Package object representing the package
+     *         to labeled.
+     * @return boolean which determines whether a non null seinfo label
+     *         was assigned to the package. A null value simply meaning that
+     *         no policy matched.
+     */
+    public static boolean assignSeinfoValue(PackageParser.Package pkg) {
+
+        /*
+         * Non system installed apps should be treated the same. This
+         * means that any post-loaded apk will be assigned the default
+         * tag, if one exists in the policy, else null, without respect
+         * to the signing key.
+         */
+        if (((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) ||
+            ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0)) {
+
+            // We just want one of the signatures to match.
+            for (Signature s : pkg.mSignatures) {
+                if (s == null)
+                    continue;
+
+                Policy policy = sSigSeinfo.get(s);
+                if (policy != null) {
+                    String seinfo = policy.checkPolicy(pkg.packageName);
+                    if (seinfo != null) {
+                        pkg.applicationInfo.seinfo = seinfo;
+                        if (DEBUG_POLICY_INSTALL)
+                            Slog.i(TAG, "package (" + pkg.packageName +
+                                   ") labeled with seinfo=" + seinfo);
+
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // If we have a default seinfo value then great, otherwise
+        // we set a null object and that is what we started with.
+        pkg.applicationInfo.seinfo = sDefaultSeinfo;
+        if (DEBUG_POLICY_INSTALL)
+            Slog.i(TAG, "package (" + pkg.packageName + ") labeled with seinfo="
+                   + (sDefaultSeinfo == null ? "null" : sDefaultSeinfo));
+
+        return (sDefaultSeinfo != null);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
new file mode 100644
index 0000000..e599409
--- /dev/null
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -0,0 +1,3187 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.os.Process.SYSTEM_UID;
+import static android.os.Process.PACKAGE_INFO_GID;
+
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.PatternMatcher;
+import android.util.LogPrinter;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.JournaledFile;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.PackageManagerService.DumpState;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ComponentInfo;
+import android.content.pm.KeySet;
+import android.content.pm.PackageCleanItem;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionInfo;
+import android.content.pm.Signature;
+import android.content.pm.UserInfo;
+import android.content.pm.PackageUserState;
+import android.content.pm.VerifierDeviceIdentity;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import libcore.io.IoUtils;
+
+/**
+ * Holds information about dynamic settings.
+ */
+final class Settings {
+    private static final String TAG = "PackageSettings";
+
+    private static final boolean DEBUG_STOPPED = false;
+    private static final boolean DEBUG_MU = false;
+
+    private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
+    private static final String ATTR_ENFORCEMENT = "enforcement";
+
+    private static final String TAG_ITEM = "item";
+    private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
+    private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
+    private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
+    private static final String TAG_PACKAGE = "pkg";
+
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_USER = "user";
+    private static final String ATTR_CODE = "code";
+    private static final String ATTR_NOT_LAUNCHED = "nl";
+    private static final String ATTR_ENABLED = "enabled";
+    private static final String ATTR_ENABLED_CALLER = "enabledCaller";
+    private static final String ATTR_STOPPED = "stopped";
+    private static final String ATTR_BLOCKED = "blocked";
+    private static final String ATTR_INSTALLED = "inst";
+
+    private final File mSettingsFilename;
+    private final File mBackupSettingsFilename;
+    private final File mPackageListFilename;
+    private final File mStoppedPackagesFilename;
+    private final File mBackupStoppedPackagesFilename;
+
+    final HashMap<String, PackageSetting> mPackages =
+            new HashMap<String, PackageSetting>();
+    // List of replaced system applications
+    private final HashMap<String, PackageSetting> mDisabledSysPackages =
+        new HashMap<String, PackageSetting>();
+
+    private static int mFirstAvailableUid = 0;
+
+    // These are the last platform API version we were using for
+    // the apps installed on internal and external storage.  It is
+    // used to grant newer permissions one time during a system upgrade.
+    int mInternalSdkPlatform;
+    int mExternalSdkPlatform;
+
+    Boolean mReadExternalStorageEnforced;
+
+    /** Device identity for the purpose of package verification. */
+    private VerifierDeviceIdentity mVerifierDeviceIdentity;
+
+    // The user's preferred activities associated with particular intent
+    // filters.
+    final SparseArray<PreferredIntentResolver> mPreferredActivities =
+            new SparseArray<PreferredIntentResolver>();
+
+    final HashMap<String, SharedUserSetting> mSharedUsers =
+            new HashMap<String, SharedUserSetting>();
+    private final ArrayList<Object> mUserIds = new ArrayList<Object>();
+    private final SparseArray<Object> mOtherUserIds =
+            new SparseArray<Object>();
+
+    // For reading/writing settings file.
+    private final ArrayList<Signature> mPastSignatures =
+            new ArrayList<Signature>();
+
+    // Mapping from permission names to info about them.
+    final HashMap<String, BasePermission> mPermissions =
+            new HashMap<String, BasePermission>();
+
+    // Mapping from permission tree names to info about them.
+    final HashMap<String, BasePermission> mPermissionTrees =
+            new HashMap<String, BasePermission>();
+
+    // Packages that have been uninstalled and still need their external
+    // storage data deleted.
+    final ArrayList<PackageCleanItem> mPackagesToBeCleaned = new ArrayList<PackageCleanItem>();
+    
+    // Packages that have been renamed since they were first installed.
+    // Keys are the new names of the packages, values are the original
+    // names.  The packages appear everwhere else under their original
+    // names.
+    final HashMap<String, String> mRenamedPackages = new HashMap<String, String>();
+    
+    final StringBuilder mReadMessages = new StringBuilder();
+
+    /**
+     * Used to track packages that have a shared user ID that hasn't been read
+     * in yet.
+     * <p>
+     * TODO: make this just a local variable that is passed in during package
+     * scanning to make it less confusing.
+     */
+    private final ArrayList<PendingPackage> mPendingPackages = new ArrayList<PendingPackage>();
+
+    private final Context mContext;
+
+    private final File mSystemDir;
+
+    public final KeySetManager mKeySetManager = new KeySetManager(mPackages);
+
+    Settings(Context context) {
+        this(context, Environment.getDataDirectory());
+    }
+
+    Settings(Context context, File dataDir) {
+        mContext = context;
+        mSystemDir = new File(dataDir, "system");
+        mSystemDir.mkdirs();
+        FileUtils.setPermissions(mSystemDir.toString(),
+                FileUtils.S_IRWXU|FileUtils.S_IRWXG
+                |FileUtils.S_IROTH|FileUtils.S_IXOTH,
+                -1, -1);
+        mSettingsFilename = new File(mSystemDir, "packages.xml");
+        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
+        mPackageListFilename = new File(mSystemDir, "packages.list");
+        FileUtils.setPermissions(mPackageListFilename, 0660, SYSTEM_UID, PACKAGE_INFO_GID);
+
+        // Deprecated: Needed for migration
+        mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
+        mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
+    }
+
+    PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
+            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
+            String nativeLibraryPathString, int pkgFlags, UserHandle user, boolean add) {
+        final String name = pkg.packageName;
+        PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath,
+                resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags,
+                user, add, true /* allowInstall */);
+        return p;
+    }
+
+    PackageSetting peekPackageLPr(String name) {
+        return mPackages.get(name);
+    }
+
+    void setInstallStatus(String pkgName, int status) {
+        PackageSetting p = mPackages.get(pkgName);
+        if(p != null) {
+            if(p.getInstallStatus() != status) {
+                p.setInstallStatus(status);
+            }
+        }
+    }
+
+    void setInstallerPackageName(String pkgName,
+            String installerPkgName) {
+        PackageSetting p = mPackages.get(pkgName);
+        if(p != null) {
+            p.setInstallerPackageName(installerPkgName);
+        }
+    }
+
+    SharedUserSetting getSharedUserLPw(String name,
+            int pkgFlags, boolean create) {
+        SharedUserSetting s = mSharedUsers.get(name);
+        if (s == null) {
+            if (!create) {
+                return null;
+            }
+            s = new SharedUserSetting(name, pkgFlags);
+            s.userId = newUserIdLPw(s);
+            Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
+            // < 0 means we couldn't assign a userid; fall out and return
+            // s, which is currently null
+            if (s.userId >= 0) {
+                mSharedUsers.put(name, s);
+            }
+        }
+
+        return s;
+    }
+
+    boolean disableSystemPackageLPw(String name) {
+        final PackageSetting p = mPackages.get(name);
+        if(p == null) {
+            Log.w(PackageManagerService.TAG, "Package:"+name+" is not an installed package");
+            return false;
+        }
+        final PackageSetting dp = mDisabledSysPackages.get(name);
+        // always make sure the system package code and resource paths dont change
+        if (dp == null) {
+            if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
+                p.pkg.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+            }
+            mDisabledSysPackages.put(name, p);
+
+            // a little trick...  when we install the new package, we don't
+            // want to modify the existing PackageSetting for the built-in
+            // version.  so at this point we need a new PackageSetting that
+            // is okay to muck with.
+            PackageSetting newp = new PackageSetting(p);
+            replacePackageLPw(name, newp);
+            return true;
+        }
+        return false;
+    }
+
+    PackageSetting enableSystemPackageLPw(String name) {
+        PackageSetting p = mDisabledSysPackages.get(name);
+        if(p == null) {
+            Log.w(PackageManagerService.TAG, "Package:"+name+" is not disabled");
+            return null;
+        }
+        // Reset flag in ApplicationInfo object
+        if((p.pkg != null) && (p.pkg.applicationInfo != null)) {
+            p.pkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
+        }
+        PackageSetting ret = addPackageLPw(name, p.realName, p.codePath, p.resourcePath,
+                p.nativeLibraryPathString, p.appId, p.versionCode, p.pkgFlags);
+        mDisabledSysPackages.remove(name);
+        return ret;
+    }
+
+    boolean isDisabledSystemPackageLPr(String name) {
+        return mDisabledSysPackages.containsKey(name);
+    }
+
+    void removeDisabledSystemPackageLPw(String name) {
+        mDisabledSysPackages.remove(name);
+    }
+
+    PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
+            String nativeLibraryPathString, int uid, int vc, int pkgFlags) {
+        PackageSetting p = mPackages.get(name);
+        if (p != null) {
+            if (p.appId == uid) {
+                return p;
+            }
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Adding duplicate package, keeping first: " + name);
+            return null;
+        }
+        p = new PackageSetting(name, realName, codePath, resourcePath, nativeLibraryPathString,
+                vc, pkgFlags);
+        p.appId = uid;
+        if (addUserIdLPw(uid, p, name)) {
+            mPackages.put(name, p);
+            return p;
+        }
+        return null;
+    }
+
+    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) {
+        SharedUserSetting s = mSharedUsers.get(name);
+        if (s != null) {
+            if (s.userId == uid) {
+                return s;
+            }
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Adding duplicate shared user, keeping first: " + name);
+            return null;
+        }
+        s = new SharedUserSetting(name, pkgFlags);
+        s.userId = uid;
+        if (addUserIdLPw(uid, s, name)) {
+            mSharedUsers.put(name, s);
+            return s;
+        }
+        return null;
+    }
+
+    void pruneSharedUsersLPw() {
+        ArrayList<String> removeStage = new ArrayList<String>();
+        for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) {
+            final SharedUserSetting sus = entry.getValue();
+            if (sus == null || sus.packages.size() == 0) {
+                removeStage.add(entry.getKey());
+            }
+        }
+        for (int i = 0; i < removeStage.size(); i++) {
+            mSharedUsers.remove(removeStage.get(i));
+        }
+    }
+
+    // Transfer ownership of permissions from one package to another.
+    void transferPermissionsLPw(String origPkg, String newPkg) {
+        // Transfer ownership of permissions to the new package.
+        for (int i=0; i<2; i++) {
+            HashMap<String, BasePermission> permissions =
+                    i == 0 ? mPermissionTrees : mPermissions;
+            for (BasePermission bp : permissions.values()) {
+                if (origPkg.equals(bp.sourcePackage)) {
+                    if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG,
+                            "Moving permission " + bp.name
+                            + " from pkg " + bp.sourcePackage
+                            + " to " + newPkg);
+                    bp.sourcePackage = newPkg;
+                    bp.packageSetting = null;
+                    bp.perm = null;
+                    if (bp.pendingInfo != null) {
+                        bp.pendingInfo.packageName = newPkg;
+                    }
+                    bp.uid = 0;
+                    bp.gids = null;
+                }
+            }
+        }
+    }
+
+    private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
+            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
+            String nativeLibraryPathString, int vc, int pkgFlags,
+            UserHandle installUser, boolean add, boolean allowInstall) {
+        PackageSetting p = mPackages.get(name);
+        if (p != null) {
+            if (!p.codePath.equals(codePath)) {
+                // Check to see if its a disabled system app
+                if ((p.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                    // This is an updated system app with versions in both system
+                    // and data partition. Just let the most recent version
+                    // take precedence.
+                    Slog.w(PackageManagerService.TAG, "Trying to update system app code path from "
+                            + p.codePathString + " to " + codePath.toString());
+                } else {
+                    // Just a change in the code path is not an issue, but
+                    // let's log a message about it.
+                    Slog.i(PackageManagerService.TAG, "Package " + name + " codePath changed from "
+                            + p.codePath + " to " + codePath + "; Retaining data and using new");
+                    /*
+                     * Since we've changed paths, we need to prefer the new
+                     * native library path over the one stored in the
+                     * package settings since we might have moved from
+                     * internal to external storage or vice versa.
+                     */
+                    p.nativeLibraryPathString = nativeLibraryPathString;
+                }
+            }
+            if (p.sharedUser != sharedUser) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Package " + name + " shared user changed from "
+                        + (p.sharedUser != null ? p.sharedUser.name : "<nothing>")
+                        + " to "
+                        + (sharedUser != null ? sharedUser.name : "<nothing>")
+                        + "; replacing with new");
+                p = null;
+            } else {
+                // If what we are scanning is a system (and possibly privileged) package,
+                // then make it so, regardless of whether it was previously installed only
+                // in the data partition.
+                final int sysPrivFlags = pkgFlags
+                        & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PRIVILEGED);
+                p.pkgFlags |= sysPrivFlags;
+            }
+        }
+        if (p == null) {
+            if (origPackage != null) {
+                // We are consuming the data from an existing package.
+                p = new PackageSetting(origPackage.name, name, codePath, resourcePath,
+                        nativeLibraryPathString, vc, pkgFlags);
+                if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package "
+                        + name + " is adopting original package " + origPackage.name);
+                // Note that we will retain the new package's signature so
+                // that we can keep its data.
+                PackageSignatures s = p.signatures;
+                p.copyFrom(origPackage);
+                p.signatures = s;
+                p.sharedUser = origPackage.sharedUser;
+                p.appId = origPackage.appId;
+                p.origPackage = origPackage;
+                mRenamedPackages.put(name, origPackage.name);
+                name = origPackage.name;
+                // Update new package state.
+                p.setTimeStamp(codePath.lastModified());
+            } else {
+                p = new PackageSetting(name, realName, codePath, resourcePath,
+                        nativeLibraryPathString, vc, pkgFlags);
+                p.setTimeStamp(codePath.lastModified());
+                p.sharedUser = sharedUser;
+                // If this is not a system app, it starts out stopped.
+                if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    if (DEBUG_STOPPED) {
+                        RuntimeException e = new RuntimeException("here");
+                        e.fillInStackTrace();
+                        Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);
+                    }
+                    List<UserInfo> users = getAllUsers();
+                    if (users != null && allowInstall) {
+                        for (UserInfo user : users) {
+                            // By default we consider this app to be installed
+                            // for the user if no user has been specified (which
+                            // means to leave it at its original value, and the
+                            // original default value is true), or we are being
+                            // asked to install for all users, or this is the
+                            // user we are installing for.
+                            final boolean installed = installUser == null
+                                    || installUser.getIdentifier() == UserHandle.USER_ALL
+                                    || installUser.getIdentifier() == user.id;
+                            p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
+                                    installed,
+                                    true, // stopped,
+                                    true, // notLaunched
+                                    false, // blocked
+                                    null, null, null);
+                            writePackageRestrictionsLPr(user.id);
+                        }
+                    }
+                }
+                if (sharedUser != null) {
+                    p.appId = sharedUser.userId;
+                } else {
+                    // Clone the setting here for disabled system packages
+                    PackageSetting dis = mDisabledSysPackages.get(name);
+                    if (dis != null) {
+                        // For disabled packages a new setting is created
+                        // from the existing user id. This still has to be
+                        // added to list of user id's
+                        // Copy signatures from previous setting
+                        if (dis.signatures.mSignatures != null) {
+                            p.signatures.mSignatures = dis.signatures.mSignatures.clone();
+                        }
+                        p.appId = dis.appId;
+                        // Clone permissions
+                        p.grantedPermissions = new HashSet<String>(dis.grantedPermissions);
+                        // Clone component info
+                        List<UserInfo> users = getAllUsers();
+                        if (users != null) {
+                            for (UserInfo user : users) {
+                                int userId = user.id;
+                                p.setDisabledComponentsCopy(
+                                        dis.getDisabledComponents(userId), userId);
+                                p.setEnabledComponentsCopy(
+                                        dis.getEnabledComponents(userId), userId);
+                            }
+                        }
+                        // Add new setting to list of user ids
+                        addUserIdLPw(p.appId, p, name);
+                    } else {
+                        // Assign new user id
+                        p.appId = newUserIdLPw(p);
+                    }
+                }
+            }
+            if (p.appId < 0) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Package " + name + " could not be assigned a valid uid");
+                return null;
+            }
+            if (add) {
+                // Finish adding new package by adding it and updating shared
+                // user preferences
+                addPackageSettingLPw(p, name, sharedUser);
+            }
+        } else {
+            if (installUser != null && allowInstall) {
+                // The caller has explicitly specified the user they want this
+                // package installed for, and the package already exists.
+                // Make sure it conforms to the new request.
+                List<UserInfo> users = getAllUsers();
+                if (users != null) {
+                    for (UserInfo user : users) {
+                        if (installUser.getIdentifier() == UserHandle.USER_ALL
+                                || installUser.getIdentifier() == user.id) {
+                            boolean installed = p.getInstalled(user.id);
+                            if (!installed) {
+                                p.setInstalled(true, user.id);
+                                writePackageRestrictionsLPr(user.id);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return p;
+    }
+
+    void insertPackageSettingLPw(PackageSetting p, PackageParser.Package pkg) {
+        p.pkg = pkg;
+        // pkg.mSetEnabled = p.getEnabled(userId);
+        // pkg.mSetStopped = p.getStopped(userId);
+        final String codePath = pkg.applicationInfo.sourceDir;
+        final String resourcePath = pkg.applicationInfo.publicSourceDir;
+        // Update code path if needed
+        if (!codePath.equalsIgnoreCase(p.codePathString)) {
+            Slog.w(PackageManagerService.TAG, "Code path for pkg : " + p.pkg.packageName +
+                    " changing from " + p.codePathString + " to " + codePath);
+            p.codePath = new File(codePath);
+            p.codePathString = codePath;
+        }
+        //Update resource path if needed
+        if (!resourcePath.equalsIgnoreCase(p.resourcePathString)) {
+            Slog.w(PackageManagerService.TAG, "Resource path for pkg : " + p.pkg.packageName +
+                    " changing from " + p.resourcePathString + " to " + resourcePath);
+            p.resourcePath = new File(resourcePath);
+            p.resourcePathString = resourcePath;
+        }
+        // Update the native library path if needed
+        final String nativeLibraryPath = pkg.applicationInfo.nativeLibraryDir;
+        if (nativeLibraryPath != null
+                && !nativeLibraryPath.equalsIgnoreCase(p.nativeLibraryPathString)) {
+            p.nativeLibraryPathString = nativeLibraryPath;
+        }
+        // Update version code if needed
+        if (pkg.mVersionCode != p.versionCode) {
+            p.versionCode = pkg.mVersionCode;
+        }
+        // Update signatures if needed.
+        if (p.signatures.mSignatures == null) {
+            p.signatures.assignSignatures(pkg.mSignatures);
+        }
+        // Update flags if needed.
+        if (pkg.applicationInfo.flags != p.pkgFlags) {
+            p.pkgFlags = pkg.applicationInfo.flags;
+        }
+        // If this app defines a shared user id initialize
+        // the shared user signatures as well.
+        if (p.sharedUser != null && p.sharedUser.signatures.mSignatures == null) {
+            p.sharedUser.signatures.assignSignatures(pkg.mSignatures);
+        }
+        addPackageSettingLPw(p, pkg.packageName, p.sharedUser);
+    }
+
+    // Utility method that adds a PackageSetting to mPackages and
+    // completes updating the shared user attributes
+    private void addPackageSettingLPw(PackageSetting p, String name,
+            SharedUserSetting sharedUser) {
+        mPackages.put(name, p);
+        if (sharedUser != null) {
+            if (p.sharedUser != null && p.sharedUser != sharedUser) {
+                PackageManagerService.reportSettingsProblem(Log.ERROR,
+                        "Package " + p.name + " was user "
+                        + p.sharedUser + " but is now " + sharedUser
+                        + "; I am not changing its files so it will probably fail!");
+                p.sharedUser.removePackage(p);
+            } else if (p.appId != sharedUser.userId) {
+                PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Package " + p.name + " was user id " + p.appId
+                    + " but is now user " + sharedUser
+                    + " with id " + sharedUser.userId
+                    + "; I am not changing its files so it will probably fail!");
+            }
+
+            sharedUser.addPackage(p);
+            p.sharedUser = sharedUser;
+            p.appId = sharedUser.userId;
+        }
+    }
+
+    /*
+     * Update the shared user setting when a package using
+     * specifying the shared user id is removed. The gids
+     * associated with each permission of the deleted package
+     * are removed from the shared user's gid list only if its
+     * not in use by other permissions of packages in the
+     * shared user setting.
+     */
+    void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) {
+        if ((deletedPs == null) || (deletedPs.pkg == null)) {
+            Slog.i(PackageManagerService.TAG,
+                    "Trying to update info for null package. Just ignoring");
+            return;
+        }
+        // No sharedUserId
+        if (deletedPs.sharedUser == null) {
+            return;
+        }
+        SharedUserSetting sus = deletedPs.sharedUser;
+        // Update permissions
+        for (String eachPerm : deletedPs.pkg.requestedPermissions) {
+            boolean used = false;
+            if (!sus.grantedPermissions.contains(eachPerm)) {
+                continue;
+            }
+            for (PackageSetting pkg:sus.packages) {
+                if (pkg.pkg != null &&
+                        !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) &&
+                        pkg.pkg.requestedPermissions.contains(eachPerm)) {
+                    used = true;
+                    break;
+                }
+            }
+            if (!used) {
+                // can safely delete this permission from list
+                sus.grantedPermissions.remove(eachPerm);
+            }
+        }
+        // Update gids
+        int newGids[] = globalGids;
+        for (String eachPerm : sus.grantedPermissions) {
+            BasePermission bp = mPermissions.get(eachPerm);
+            if (bp != null) {
+                newGids = PackageManagerService.appendInts(newGids, bp.gids);
+            }
+        }
+        sus.gids = newGids;
+    }
+
+    int removePackageLPw(String name) {
+        final PackageSetting p = mPackages.get(name);
+        if (p != null) {
+            mPackages.remove(name);
+            if (p.sharedUser != null) {
+                p.sharedUser.removePackage(p);
+                if (p.sharedUser.packages.size() == 0) {
+                    mSharedUsers.remove(p.sharedUser.name);
+                    removeUserIdLPw(p.sharedUser.userId);
+                    return p.sharedUser.userId;
+                }
+            } else {
+                removeUserIdLPw(p.appId);
+                return p.appId;
+            }
+        }
+        return -1;
+    }
+
+    private void replacePackageLPw(String name, PackageSetting newp) {
+        final PackageSetting p = mPackages.get(name);
+        if (p != null) {
+            if (p.sharedUser != null) {
+                p.sharedUser.removePackage(p);
+                p.sharedUser.addPackage(newp);
+            } else {
+                replaceUserIdLPw(p.appId, newp);
+            }
+        }
+        mPackages.put(name, newp);
+    }
+
+    private boolean addUserIdLPw(int uid, Object obj, Object name) {
+        if (uid > Process.LAST_APPLICATION_UID) {
+            return false;
+        }
+
+        if (uid >= Process.FIRST_APPLICATION_UID) {
+            int N = mUserIds.size();
+            final int index = uid - Process.FIRST_APPLICATION_UID;
+            while (index >= N) {
+                mUserIds.add(null);
+                N++;
+            }
+            if (mUserIds.get(index) != null) {
+                PackageManagerService.reportSettingsProblem(Log.ERROR,
+                        "Adding duplicate user id: " + uid
+                        + " name=" + name);
+                return false;
+            }
+            mUserIds.set(index, obj);
+        } else {
+            if (mOtherUserIds.get(uid) != null) {
+                PackageManagerService.reportSettingsProblem(Log.ERROR,
+                        "Adding duplicate shared id: " + uid
+                        + " name=" + name);
+                return false;
+            }
+            mOtherUserIds.put(uid, obj);
+        }
+        return true;
+    }
+
+    public Object getUserIdLPr(int uid) {
+        if (uid >= Process.FIRST_APPLICATION_UID) {
+            final int N = mUserIds.size();
+            final int index = uid - Process.FIRST_APPLICATION_UID;
+            return index < N ? mUserIds.get(index) : null;
+        } else {
+            return mOtherUserIds.get(uid);
+        }
+    }
+
+    private void removeUserIdLPw(int uid) {
+        if (uid >= Process.FIRST_APPLICATION_UID) {
+            final int N = mUserIds.size();
+            final int index = uid - Process.FIRST_APPLICATION_UID;
+            if (index < N) mUserIds.set(index, null);
+        } else {
+            mOtherUserIds.remove(uid);
+        }
+        setFirstAvailableUid(uid+1);
+    }
+
+    private void replaceUserIdLPw(int uid, Object obj) {
+        if (uid >= Process.FIRST_APPLICATION_UID) {
+            final int N = mUserIds.size();
+            final int index = uid - Process.FIRST_APPLICATION_UID;
+            if (index < N) mUserIds.set(index, obj);
+        } else {
+            mOtherUserIds.put(uid, obj);
+        }
+    }
+
+    PreferredIntentResolver editPreferredActivitiesLPw(int userId) {
+        PreferredIntentResolver pir = mPreferredActivities.get(userId);
+        if (pir == null) {
+            pir = new PreferredIntentResolver();
+            mPreferredActivities.put(userId, pir);
+        }
+        return pir;
+    }
+
+    private File getUserPackagesStateFile(int userId) {
+        return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
+    }
+
+    private File getUserPackagesStateBackupFile(int userId) {
+        return new File(Environment.getUserSystemDirectory(userId),
+                "package-restrictions-backup.xml");
+    }
+
+    void writeAllUsersPackageRestrictionsLPr() {
+        List<UserInfo> users = getAllUsers();
+        if (users == null) return;
+
+        for (UserInfo user : users) {
+            writePackageRestrictionsLPr(user.id);
+        }
+    }
+
+    void readAllUsersPackageRestrictionsLPr() {
+        List<UserInfo> users = getAllUsers();
+        if (users == null) {
+            readPackageRestrictionsLPr(0);
+            return;
+        }
+
+        for (UserInfo user : users) {
+            readPackageRestrictionsLPr(user.id);
+        }
+    }
+
+    private void readPreferredActivitiesLPw(XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                PreferredActivity pa = new PreferredActivity(parser);
+                if (pa.mPref.getParseError() == null) {
+                    editPreferredActivitiesLPw(userId).addFilter(pa);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <preferred-activity> "
+                                    + pa.mPref.getParseError() + " at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <preferred-activities>: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    void readPackageRestrictionsLPr(int userId) {
+        if (DEBUG_MU) {
+            Log.i(TAG, "Reading package restrictions for user=" + userId);
+        }
+        FileInputStream str = null;
+        File userPackagesStateFile = getUserPackagesStateFile(userId);
+        File backupFile = getUserPackagesStateBackupFile(userId);
+        if (backupFile.exists()) {
+            try {
+                str = new FileInputStream(backupFile);
+                mReadMessages.append("Reading from backup stopped packages file\n");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "Need to read from backup stopped packages file");
+                if (userPackagesStateFile.exists()) {
+                    // If both the backup and normal file exist, we
+                    // ignore the normal one since it might have been
+                    // corrupted.
+                    Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
+                            + userPackagesStateFile);
+                    userPackagesStateFile.delete();
+                }
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        try {
+            if (str == null) {
+                if (!userPackagesStateFile.exists()) {
+                    mReadMessages.append("No stopped packages file found\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "No stopped packages file; "
+                            + "assuming all started");
+                    // At first boot, make sure no packages are stopped.
+                    // We usually want to have third party apps initialize
+                    // in the stopped state, but not at first boot.  Also
+                    // consider all applications to be installed.
+                    for (PackageSetting pkg : mPackages.values()) {
+                        pkg.setUserState(userId, COMPONENT_ENABLED_STATE_DEFAULT,
+                                true,   // installed
+                                false,  // stopped
+                                false,  // notLaunched
+                                false,  // blocked
+                                null, null, null);
+                    }
+                    return;
+                }
+                str = new FileInputStream(userPackagesStateFile);
+            }
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(str, null);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.START_TAG
+                       && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                mReadMessages.append("No start tag found in package restrictions file\n");
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "No start tag found in package manager stopped packages");
+                return;
+            }
+
+            int outerDepth = parser.getDepth();
+            PackageSetting ps = null;
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG
+                           || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG
+                        || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_PACKAGE)) {
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
+                    ps = mPackages.get(name);
+                    if (ps == null) {
+                        Slog.w(PackageManagerService.TAG, "No package known for stopped package: "
+                                + name);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
+                    final int enabled = enabledStr == null
+                            ? COMPONENT_ENABLED_STATE_DEFAULT : Integer.parseInt(enabledStr);
+                    final String enabledCaller = parser.getAttributeValue(null,
+                            ATTR_ENABLED_CALLER);
+                    final String installedStr = parser.getAttributeValue(null, ATTR_INSTALLED);
+                    final boolean installed = installedStr == null
+                            ? true : Boolean.parseBoolean(installedStr);
+                    final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED);
+                    final boolean stopped = stoppedStr == null
+                            ? false : Boolean.parseBoolean(stoppedStr);
+                    final String blockedStr = parser.getAttributeValue(null, ATTR_BLOCKED);
+                    final boolean blocked = blockedStr == null
+                            ? false : Boolean.parseBoolean(blockedStr);
+                    final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED);
+                    final boolean notLaunched = stoppedStr == null
+                            ? false : Boolean.parseBoolean(notLaunchedStr);
+
+                    HashSet<String> enabledComponents = null;
+                    HashSet<String> disabledComponents = null;
+
+                    int packageDepth = parser.getDepth();
+                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                            && (type != XmlPullParser.END_TAG
+                            || parser.getDepth() > packageDepth)) {
+                        if (type == XmlPullParser.END_TAG
+                                || type == XmlPullParser.TEXT) {
+                            continue;
+                        }
+                        tagName = parser.getName();
+                        if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
+                            enabledComponents = readComponentsLPr(parser);
+                        } else if (tagName.equals(TAG_DISABLED_COMPONENTS)) {
+                            disabledComponents = readComponentsLPr(parser);
+                        }
+                    }
+
+                    ps.setUserState(userId, enabled, installed, stopped, notLaunched, blocked,
+                            enabledCaller, enabledComponents, disabledComponents);
+                } else if (tagName.equals("preferred-activities")) {
+                    readPreferredActivitiesLPw(parser, userId);
+                } else {
+                    Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+                          + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+            str.close();
+
+        } catch (XmlPullParserException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Error reading stopped packages: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+
+        } catch (java.io.IOException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+        }
+    }
+
+    private HashSet<String> readComponentsLPr(XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        HashSet<String> components = null;
+        int type;
+        int outerDepth = parser.getDepth();
+        String tagName;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG
+                || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG
+                    || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String componentName = parser.getAttributeValue(null, ATTR_NAME);
+                if (componentName != null) {
+                    if (components == null) {
+                        components = new HashSet<String>();
+                    }
+                    components.add(componentName);
+                }
+            }
+        }
+        return components;
+    }
+
+    void writePreferredActivitiesLPr(XmlSerializer serializer, int userId, boolean full)
+            throws IllegalArgumentException, IllegalStateException, IOException {
+        serializer.startTag(null, "preferred-activities");
+        PreferredIntentResolver pir = mPreferredActivities.get(userId);
+        if (pir != null) {
+            for (final PreferredActivity pa : pir.filterSet()) {
+                serializer.startTag(null, TAG_ITEM);
+                pa.writeToXml(serializer, full);
+                serializer.endTag(null, TAG_ITEM);
+            }
+        }
+        serializer.endTag(null, "preferred-activities");
+    }
+
+    void writePackageRestrictionsLPr(int userId) {
+        if (DEBUG_MU) {
+            Log.i(TAG, "Writing package restrictions for user=" + userId);
+        }
+        // Keep the old stopped packages around until we know the new ones have
+        // been successfully written.
+        File userPackagesStateFile = getUserPackagesStateFile(userId);
+        File backupFile = getUserPackagesStateBackupFile(userId);
+        new File(userPackagesStateFile.getParent()).mkdirs();
+        if (userPackagesStateFile.exists()) {
+            // Presence of backup settings file indicates that we failed
+            // to persist packages earlier. So preserve the older
+            // backup for future reference since the current packages
+            // might have been corrupted.
+            if (!backupFile.exists()) {
+                if (!userPackagesStateFile.renameTo(backupFile)) {
+                    Log.wtf(PackageManagerService.TAG, "Unable to backup user packages state file, "
+                            + "current changes will be lost at reboot");
+                    return;
+                }
+            } else {
+                userPackagesStateFile.delete();
+                Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
+            }
+        }
+
+        try {
+            final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile);
+            final BufferedOutputStream str = new BufferedOutputStream(fstr);
+
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(str, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);
+
+            for (final PackageSetting pkg : mPackages.values()) {
+                PackageUserState ustate = pkg.readUserState(userId);
+                if (ustate.stopped || ustate.notLaunched || !ustate.installed
+                        || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT
+                        || ustate.blocked
+                        || (ustate.enabledComponents != null
+                                && ustate.enabledComponents.size() > 0)
+                        || (ustate.disabledComponents != null
+                                && ustate.disabledComponents.size() > 0)) {
+                    serializer.startTag(null, TAG_PACKAGE);
+                    serializer.attribute(null, ATTR_NAME, pkg.name);
+                    if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);
+
+                    if (!ustate.installed) {
+                        serializer.attribute(null, ATTR_INSTALLED, "false");
+                    }
+                    if (ustate.stopped) {
+                        serializer.attribute(null, ATTR_STOPPED, "true");
+                    }
+                    if (ustate.notLaunched) {
+                        serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
+                    }
+                    if (ustate.blocked) {
+                        serializer.attribute(null, ATTR_BLOCKED, "true");
+                    }
+                    if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
+                        serializer.attribute(null, ATTR_ENABLED,
+                                Integer.toString(ustate.enabled));
+                        if (ustate.lastDisableAppCaller != null) {
+                            serializer.attribute(null, ATTR_ENABLED_CALLER,
+                                    ustate.lastDisableAppCaller);
+                        }
+                    }
+                    if (ustate.enabledComponents != null
+                            && ustate.enabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_ENABLED_COMPONENTS);
+                        for (final String name : ustate.enabledComponents) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME, name);
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_ENABLED_COMPONENTS);
+                    }
+                    if (ustate.disabledComponents != null
+                            && ustate.disabledComponents.size() > 0) {
+                        serializer.startTag(null, TAG_DISABLED_COMPONENTS);
+                        for (final String name : ustate.disabledComponents) {
+                            serializer.startTag(null, TAG_ITEM);
+                            serializer.attribute(null, ATTR_NAME, name);
+                            serializer.endTag(null, TAG_ITEM);
+                        }
+                        serializer.endTag(null, TAG_DISABLED_COMPONENTS);
+                    }
+                    serializer.endTag(null, TAG_PACKAGE);
+                }
+            }
+
+            writePreferredActivitiesLPr(serializer, userId, true);
+
+            serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);
+
+            serializer.endDocument();
+
+            str.flush();
+            FileUtils.sync(fstr);
+            str.close();
+
+            // New settings successfully written, old ones are no longer
+            // needed.
+            backupFile.delete();
+            FileUtils.setPermissions(userPackagesStateFile.toString(),
+                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
+                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+                    -1, -1);
+
+            // Done, all is good!
+            return;
+        } catch(java.io.IOException e) {
+            Log.wtf(PackageManagerService.TAG,
+                    "Unable to write package manager user packages state, "
+                    + " current changes will be lost at reboot", e);
+        }
+
+        // Clean up partially written files
+        if (userPackagesStateFile.exists()) {
+            if (!userPackagesStateFile.delete()) {
+                Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: "
+                        + mStoppedPackagesFilename);
+            }
+        }
+    }
+
+    // Note: assumed "stopped" field is already cleared in all packages.
+    // Legacy reader, used to read in the old file format after an upgrade. Not used after that.
+    void readStoppedLPw() {
+        FileInputStream str = null;
+        if (mBackupStoppedPackagesFilename.exists()) {
+            try {
+                str = new FileInputStream(mBackupStoppedPackagesFilename);
+                mReadMessages.append("Reading from backup stopped packages file\n");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "Need to read from backup stopped packages file");
+                if (mSettingsFilename.exists()) {
+                    // If both the backup and normal file exist, we
+                    // ignore the normal one since it might have been
+                    // corrupted.
+                    Slog.w(PackageManagerService.TAG, "Cleaning up stopped packages file "
+                            + mStoppedPackagesFilename);
+                    mStoppedPackagesFilename.delete();
+                }
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        try {
+            if (str == null) {
+                if (!mStoppedPackagesFilename.exists()) {
+                    mReadMessages.append("No stopped packages file found\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "No stopped packages file file; assuming all started");
+                    // At first boot, make sure no packages are stopped.
+                    // We usually want to have third party apps initialize
+                    // in the stopped state, but not at first boot.
+                    for (PackageSetting pkg : mPackages.values()) {
+                        pkg.setStopped(false, 0);
+                        pkg.setNotLaunched(false, 0);
+                    }
+                    return;
+                }
+                str = new FileInputStream(mStoppedPackagesFilename);
+            }
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(str, null);
+
+            int type;
+            while ((type=parser.next()) != XmlPullParser.START_TAG
+                       && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                mReadMessages.append("No start tag found in stopped packages file\n");
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "No start tag found in package manager stopped packages");
+                return;
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                   && (type != XmlPullParser.END_TAG
+                           || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG
+                        || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals(TAG_PACKAGE)) {
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
+                    PackageSetting ps = mPackages.get(name);
+                    if (ps != null) {
+                        ps.setStopped(true, 0);
+                        if ("1".equals(parser.getAttributeValue(null, ATTR_NOT_LAUNCHED))) {
+                            ps.setNotLaunched(true, 0);
+                        }
+                    } else {
+                        Slog.w(PackageManagerService.TAG,
+                                "No package known for stopped package: " + name);
+                    }
+                    XmlUtils.skipCurrentTag(parser);
+                } else {
+                    Slog.w(PackageManagerService.TAG, "Unknown element under <stopped-packages>: "
+                          + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+            str.close();
+
+        } catch (XmlPullParserException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR,
+                    "Error reading stopped packages: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+
+        } catch (java.io.IOException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager stopped packages", e);
+
+        }
+    }
+
+    void writeLPr() {
+        //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024);
+
+        // Keep the old settings around until we know the new ones have
+        // been successfully written.
+        if (mSettingsFilename.exists()) {
+            // Presence of backup settings file indicates that we failed
+            // to persist settings earlier. So preserve the older
+            // backup for future reference since the current settings
+            // might have been corrupted.
+            if (!mBackupSettingsFilename.exists()) {
+                if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
+                    Log.wtf(PackageManagerService.TAG, "Unable to backup package manager settings, "
+                            + " current changes will be lost at reboot");
+                    return;
+                }
+            } else {
+                mSettingsFilename.delete();
+                Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
+            }
+        }
+
+        mPastSignatures.clear();
+
+        try {
+            FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
+            BufferedOutputStream str = new BufferedOutputStream(fstr);
+
+            //XmlSerializer serializer = XmlUtils.serializerInstance();
+            XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(str, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, "packages");
+
+            serializer.startTag(null, "last-platform-version");
+            serializer.attribute(null, "internal", Integer.toString(mInternalSdkPlatform));
+            serializer.attribute(null, "external", Integer.toString(mExternalSdkPlatform));
+            serializer.endTag(null, "last-platform-version");
+
+            if (mVerifierDeviceIdentity != null) {
+                serializer.startTag(null, "verifier");
+                serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
+                serializer.endTag(null, "verifier");
+            }
+
+            if (mReadExternalStorageEnforced != null) {
+                serializer.startTag(null, TAG_READ_EXTERNAL_STORAGE);
+                serializer.attribute(
+                        null, ATTR_ENFORCEMENT, mReadExternalStorageEnforced ? "1" : "0");
+                serializer.endTag(null, TAG_READ_EXTERNAL_STORAGE);
+            }
+
+            serializer.startTag(null, "permission-trees");
+            for (BasePermission bp : mPermissionTrees.values()) {
+                writePermissionLPr(serializer, bp);
+            }
+            serializer.endTag(null, "permission-trees");
+
+            serializer.startTag(null, "permissions");
+            for (BasePermission bp : mPermissions.values()) {
+                writePermissionLPr(serializer, bp);
+            }
+            serializer.endTag(null, "permissions");
+
+            for (final PackageSetting pkg : mPackages.values()) {
+                writePackageLPr(serializer, pkg);
+            }
+
+            for (final PackageSetting pkg : mDisabledSysPackages.values()) {
+                writeDisabledSysPackageLPr(serializer, pkg);
+            }
+
+            for (final SharedUserSetting usr : mSharedUsers.values()) {
+                serializer.startTag(null, "shared-user");
+                serializer.attribute(null, ATTR_NAME, usr.name);
+                serializer.attribute(null, "userId",
+                        Integer.toString(usr.userId));
+                usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
+                serializer.startTag(null, "perms");
+                for (String name : usr.grantedPermissions) {
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
+                }
+                serializer.endTag(null, "perms");
+                serializer.endTag(null, "shared-user");
+            }
+
+            if (mPackagesToBeCleaned.size() > 0) {
+                for (PackageCleanItem item : mPackagesToBeCleaned) {
+                    final String userStr = Integer.toString(item.userId);
+                    serializer.startTag(null, "cleaning-package");
+                    serializer.attribute(null, ATTR_NAME, item.packageName);
+                    serializer.attribute(null, ATTR_CODE, item.andCode ? "true" : "false");
+                    serializer.attribute(null, ATTR_USER, userStr);
+                    serializer.endTag(null, "cleaning-package");
+                }
+            }
+            
+            if (mRenamedPackages.size() > 0) {
+                for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
+                    serializer.startTag(null, "renamed-package");
+                    serializer.attribute(null, "new", e.getKey());
+                    serializer.attribute(null, "old", e.getValue());
+                    serializer.endTag(null, "renamed-package");
+                }
+            }
+            
+            mKeySetManager.writeKeySetManagerLPr(serializer);
+
+            serializer.endTag(null, "packages");
+
+            serializer.endDocument();
+
+            str.flush();
+            FileUtils.sync(fstr);
+            str.close();
+
+            // New settings successfully written, old ones are no longer
+            // needed.
+            mBackupSettingsFilename.delete();
+            FileUtils.setPermissions(mSettingsFilename.toString(),
+                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
+                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+                    -1, -1);
+
+            // Write package list file now, use a JournaledFile.
+            File tempFile = new File(mPackageListFilename.getAbsolutePath() + ".tmp");
+            JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile);
+
+            final File writeTarget = journal.chooseForWrite();
+            fstr = new FileOutputStream(writeTarget);
+            str = new BufferedOutputStream(fstr);
+            try {
+                FileUtils.setPermissions(fstr.getFD(), 0660, SYSTEM_UID, PACKAGE_INFO_GID);
+
+                StringBuilder sb = new StringBuilder();
+                for (final PackageSetting pkg : mPackages.values()) {
+                    if (pkg.pkg == null || pkg.pkg.applicationInfo == null) {
+                        Slog.w(TAG, "Skipping " + pkg + " due to missing metadata");
+                        continue;
+                    }
+
+                    final ApplicationInfo ai = pkg.pkg.applicationInfo;
+                    final String dataPath = ai.dataDir;
+                    final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+                    final int[] gids = pkg.getGids();
+
+                    // Avoid any application that has a space in its path.
+                    if (dataPath.indexOf(" ") >= 0)
+                        continue;
+
+                    // we store on each line the following information for now:
+                    //
+                    // pkgName    - package name
+                    // userId     - application-specific user id
+                    // debugFlag  - 0 or 1 if the package is debuggable.
+                    // dataPath   - path to package's data path
+                    // seinfo     - seinfo label for the app (assigned at install time)
+                    // gids       - supplementary gids this app launches with
+                    //
+                    // NOTE: We prefer not to expose all ApplicationInfo flags for now.
+                    //
+                    // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS
+                    // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES:
+                    //   system/core/run-as/run-as.c
+                    //   system/core/sdcard/sdcard.c
+                    //
+                    sb.setLength(0);
+                    sb.append(ai.packageName);
+                    sb.append(" ");
+                    sb.append((int)ai.uid);
+                    sb.append(isDebug ? " 1 " : " 0 ");
+                    sb.append(dataPath);
+                    sb.append(" ");
+                    sb.append(ai.seinfo);
+                    sb.append(" ");
+                    if (gids != null && gids.length > 0) {
+                        sb.append(gids[0]);
+                        for (int i = 1; i < gids.length; i++) {
+                            sb.append(",");
+                            sb.append(gids[i]);
+                        }
+                    } else {
+                        sb.append("none");
+                    }
+                    sb.append("\n");
+                    str.write(sb.toString().getBytes());
+                }
+                str.flush();
+                FileUtils.sync(fstr);
+                str.close();
+                journal.commit();
+            } catch (Exception e) {
+                Log.wtf(TAG, "Failed to write packages.list", e);
+                IoUtils.closeQuietly(str);
+                journal.rollback();
+            }
+
+            writeAllUsersPackageRestrictionsLPr();
+            return;
+
+        } catch(XmlPullParserException e) {
+            Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
+                    + "current changes will be lost at reboot", e);
+        } catch(java.io.IOException e) {
+            Log.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
+                    + "current changes will be lost at reboot", e);
+        }
+        // Clean up partially written files
+        if (mSettingsFilename.exists()) {
+            if (!mSettingsFilename.delete()) {
+                Log.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
+                        + mSettingsFilename);
+            }
+        }
+        //Debug.stopMethodTracing();
+    }
+
+    void writeDisabledSysPackageLPr(XmlSerializer serializer, final PackageSetting pkg)
+            throws java.io.IOException {
+        serializer.startTag(null, "updated-package");
+        serializer.attribute(null, ATTR_NAME, pkg.name);
+        if (pkg.realName != null) {
+            serializer.attribute(null, "realName", pkg.realName);
+        }
+        serializer.attribute(null, "codePath", pkg.codePathString);
+        serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
+        serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
+        serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
+        serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
+        if (!pkg.resourcePathString.equals(pkg.codePathString)) {
+            serializer.attribute(null, "resourcePath", pkg.resourcePathString);
+        }
+        if (pkg.nativeLibraryPathString != null) {
+            serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString);
+        }
+        if (pkg.sharedUser == null) {
+            serializer.attribute(null, "userId", Integer.toString(pkg.appId));
+        } else {
+            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
+        }
+        serializer.startTag(null, "perms");
+        if (pkg.sharedUser == null) {
+            // If this is a shared user, the permissions will
+            // be written there. We still need to write an
+            // empty permissions list so permissionsFixed will
+            // be set.
+            for (final String name : pkg.grantedPermissions) {
+                BasePermission bp = mPermissions.get(name);
+                if (bp != null) {
+                    // We only need to write signature or system permissions but
+                    // this wont
+                    // match the semantics of grantedPermissions. So write all
+                    // permissions.
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
+                }
+            }
+        }
+        serializer.endTag(null, "perms");
+        serializer.endTag(null, "updated-package");
+    }
+
+    void writePackageLPr(XmlSerializer serializer, final PackageSetting pkg)
+            throws java.io.IOException {
+        serializer.startTag(null, "package");
+        serializer.attribute(null, ATTR_NAME, pkg.name);
+        if (pkg.realName != null) {
+            serializer.attribute(null, "realName", pkg.realName);
+        }
+        serializer.attribute(null, "codePath", pkg.codePathString);
+        if (!pkg.resourcePathString.equals(pkg.codePathString)) {
+            serializer.attribute(null, "resourcePath", pkg.resourcePathString);
+        }
+        if (pkg.nativeLibraryPathString != null) {
+            serializer.attribute(null, "nativeLibraryPath", pkg.nativeLibraryPathString);
+        }
+        serializer.attribute(null, "flags", Integer.toString(pkg.pkgFlags));
+        serializer.attribute(null, "ft", Long.toHexString(pkg.timeStamp));
+        serializer.attribute(null, "it", Long.toHexString(pkg.firstInstallTime));
+        serializer.attribute(null, "ut", Long.toHexString(pkg.lastUpdateTime));
+        serializer.attribute(null, "version", String.valueOf(pkg.versionCode));
+        if (pkg.sharedUser == null) {
+            serializer.attribute(null, "userId", Integer.toString(pkg.appId));
+        } else {
+            serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
+        }
+        if (pkg.uidError) {
+            serializer.attribute(null, "uidError", "true");
+        }
+        if (pkg.installStatus == PackageSettingBase.PKG_INSTALL_INCOMPLETE) {
+            serializer.attribute(null, "installStatus", "false");
+        }
+        if (pkg.installerPackageName != null) {
+            serializer.attribute(null, "installer", pkg.installerPackageName);
+        }
+        pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
+        if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+            serializer.startTag(null, "perms");
+            if (pkg.sharedUser == null) {
+                // If this is a shared user, the permissions will
+                // be written there. We still need to write an
+                // empty permissions list so permissionsFixed will
+                // be set.
+                for (final String name : pkg.grantedPermissions) {
+                    serializer.startTag(null, TAG_ITEM);
+                    serializer.attribute(null, ATTR_NAME, name);
+                    serializer.endTag(null, TAG_ITEM);
+                }
+            }
+            serializer.endTag(null, "perms");
+        }
+
+        writeSigningKeySetsLPr(serializer, pkg.keySetData);
+        writeKeySetAliasesLPr(serializer, pkg.keySetData);
+
+        serializer.endTag(null, "package");
+    }
+
+    void writeSigningKeySetsLPr(XmlSerializer serializer,
+            PackageKeySetData data) throws IOException {
+        for (long id : data.getSigningKeySets()) {
+            serializer.startTag(null, "signing-keyset");
+            serializer.attribute(null, "identifier", Long.toString(id));
+            serializer.endTag(null, "signing-keyset");
+        }
+    }
+
+    void writeKeySetAliasesLPr(XmlSerializer serializer,
+            PackageKeySetData data) throws IOException {
+        for (Map.Entry<String, Long> e: data.getAliases().entrySet()) {
+            serializer.startTag(null, "defined-keyset");
+            serializer.attribute(null, "alias", e.getKey());
+            serializer.attribute(null, "identifier", Long.toString(e.getValue()));
+            serializer.endTag(null, "defined-keyset");
+        }
+    }
+
+    void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
+            throws XmlPullParserException, java.io.IOException {
+        if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) {
+            serializer.startTag(null, TAG_ITEM);
+            serializer.attribute(null, ATTR_NAME, bp.name);
+            serializer.attribute(null, "package", bp.sourcePackage);
+            if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
+                serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
+            }
+            if (PackageManagerService.DEBUG_SETTINGS)
+                Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type="
+                        + bp.type);
+            if (bp.type == BasePermission.TYPE_DYNAMIC) {
+                final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo;
+                if (pi != null) {
+                    serializer.attribute(null, "type", "dynamic");
+                    if (pi.icon != 0) {
+                        serializer.attribute(null, "icon", Integer.toString(pi.icon));
+                    }
+                    if (pi.nonLocalizedLabel != null) {
+                        serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
+                    }
+                }
+            }
+            serializer.endTag(null, TAG_ITEM);
+        }
+    }
+
+    ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() {
+        final HashSet<String> kList = new HashSet<String>(mPackages.keySet());
+        final Iterator<String> its = kList.iterator();
+        final ArrayList<PackageSetting> ret = new ArrayList<PackageSetting>();
+        while (its.hasNext()) {
+            final String key = its.next();
+            final PackageSetting ps = mPackages.get(key);
+            if (ps.getInstallStatus() == PackageSettingBase.PKG_INSTALL_INCOMPLETE) {
+                ret.add(ps);
+            }
+        }
+        return ret;
+    }
+
+    void addPackageToCleanLPw(PackageCleanItem pkg) {
+        if (!mPackagesToBeCleaned.contains(pkg)) {
+            mPackagesToBeCleaned.add(pkg);
+        }
+    }
+
+    boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,
+            boolean onlyCore) {
+        FileInputStream str = null;
+        if (mBackupSettingsFilename.exists()) {
+            try {
+                str = new FileInputStream(mBackupSettingsFilename);
+                mReadMessages.append("Reading from backup settings file\n");
+                PackageManagerService.reportSettingsProblem(Log.INFO,
+                        "Need to read from backup settings file");
+                if (mSettingsFilename.exists()) {
+                    // If both the backup and settings file exist, we
+                    // ignore the settings since it might have been
+                    // corrupted.
+                    Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
+                            + mSettingsFilename);
+                    mSettingsFilename.delete();
+                }
+            } catch (java.io.IOException e) {
+                // We'll try for the normal settings file.
+            }
+        }
+
+        mPendingPackages.clear();
+        mPastSignatures.clear();
+
+        try {
+            if (str == null) {
+                if (!mSettingsFilename.exists()) {
+                    mReadMessages.append("No settings file found\n");
+                    PackageManagerService.reportSettingsProblem(Log.INFO,
+                            "No settings file; creating initial state");
+                    mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion;
+                    return false;
+                }
+                str = new FileInputStream(mSettingsFilename);
+            }
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(str, null);
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                mReadMessages.append("No start tag found in settings file\n");
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "No start tag found in package manager settings");
+                Log.wtf(PackageManagerService.TAG,
+                        "No start tag found in package manager settings");
+                return false;
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("package")) {
+                    readPackageLPw(parser);
+                } else if (tagName.equals("permissions")) {
+                    readPermissionsLPw(mPermissions, parser);
+                } else if (tagName.equals("permission-trees")) {
+                    readPermissionsLPw(mPermissionTrees, parser);
+                } else if (tagName.equals("shared-user")) {
+                    readSharedUserLPw(parser);
+                } else if (tagName.equals("preferred-packages")) {
+                    // no longer used.
+                } else if (tagName.equals("preferred-activities")) {
+                    // Upgrading from old single-user implementation;
+                    // these are the preferred activities for user 0.
+                    readPreferredActivitiesLPw(parser, 0);
+                } else if (tagName.equals("updated-package")) {
+                    readDisabledSysPackageLPw(parser);
+                } else if (tagName.equals("cleaning-package")) {
+                    String name = parser.getAttributeValue(null, ATTR_NAME);
+                    String userStr = parser.getAttributeValue(null, ATTR_USER);
+                    String codeStr = parser.getAttributeValue(null, ATTR_CODE);
+                    if (name != null) {
+                        int userId = 0;
+                        boolean andCode = true;
+                        try {
+                            if (userStr != null) {
+                                userId = Integer.parseInt(userStr);
+                            }
+                        } catch (NumberFormatException e) {
+                        }
+                        if (codeStr != null) {
+                            andCode = Boolean.parseBoolean(codeStr);
+                        }
+                        addPackageToCleanLPw(new PackageCleanItem(userId, name, andCode));
+                    }
+                } else if (tagName.equals("renamed-package")) {
+                    String nname = parser.getAttributeValue(null, "new");
+                    String oname = parser.getAttributeValue(null, "old");
+                    if (nname != null && oname != null) {
+                        mRenamedPackages.put(nname, oname);
+                    }
+                } else if (tagName.equals("last-platform-version")) {
+                    mInternalSdkPlatform = mExternalSdkPlatform = 0;
+                    try {
+                        String internal = parser.getAttributeValue(null, "internal");
+                        if (internal != null) {
+                            mInternalSdkPlatform = Integer.parseInt(internal);
+                        }
+                        String external = parser.getAttributeValue(null, "external");
+                        if (external != null) {
+                            mExternalSdkPlatform = Integer.parseInt(external);
+                        }
+                    } catch (NumberFormatException e) {
+                    }
+                } else if (tagName.equals("verifier")) {
+                    final String deviceIdentity = parser.getAttributeValue(null, "device");
+                    try {
+                        mVerifierDeviceIdentity = VerifierDeviceIdentity.parse(deviceIdentity);
+                    } catch (IllegalArgumentException e) {
+                        Slog.w(PackageManagerService.TAG, "Discard invalid verifier device id: "
+                                + e.getMessage());
+                    }
+                } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
+                    final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
+                    mReadExternalStorageEnforced = "1".equals(enforcement);
+                } else if (tagName.equals("keyset-settings")) {
+                    mKeySetManager.readKeySetsLPw(parser);
+                } else {
+                    Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+            str.close();
+
+        } catch (XmlPullParserException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
+
+        } catch (java.io.IOException e) {
+            mReadMessages.append("Error reading: " + e.toString());
+            PackageManagerService.reportSettingsProblem(Log.ERROR, "Error reading settings: " + e);
+            Log.wtf(PackageManagerService.TAG, "Error reading package manager settings", e);
+        }
+
+        final int N = mPendingPackages.size();
+        for (int i = 0; i < N; i++) {
+            final PendingPackage pp = mPendingPackages.get(i);
+            Object idObj = getUserIdLPr(pp.sharedId);
+            if (idObj != null && idObj instanceof SharedUserSetting) {
+                PackageSetting p = getPackageLPw(pp.name, null, pp.realName,
+                        (SharedUserSetting) idObj, pp.codePath, pp.resourcePath,
+                        pp.nativeLibraryPathString, pp.versionCode, pp.pkgFlags,
+                        null, true /* add */, false /* allowInstall */);
+                if (p == null) {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Unable to create application package for " + pp.name);
+                    continue;
+                }
+                p.copyFrom(pp);
+            } else if (idObj != null) {
+                String msg = "Bad package setting: package " + pp.name + " has shared uid "
+                        + pp.sharedId + " that is not a shared uid\n";
+                mReadMessages.append(msg);
+                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
+            } else {
+                String msg = "Bad package setting: package " + pp.name + " has shared uid "
+                        + pp.sharedId + " that is not defined\n";
+                mReadMessages.append(msg);
+                PackageManagerService.reportSettingsProblem(Log.ERROR, msg);
+            }
+        }
+        mPendingPackages.clear();
+
+        if (mBackupStoppedPackagesFilename.exists()
+                || mStoppedPackagesFilename.exists()) {
+            // Read old file
+            readStoppedLPw();
+            mBackupStoppedPackagesFilename.delete();
+            mStoppedPackagesFilename.delete();
+            // Migrate to new file format
+            writePackageRestrictionsLPr(0);
+        } else {
+            if (users == null) {
+                readPackageRestrictionsLPr(0);
+            } else {
+                for (UserInfo user : users) {
+                    readPackageRestrictionsLPr(user.id);
+                }
+            }
+        }
+
+        /*
+         * Make sure all the updated system packages have their shared users
+         * associated with them.
+         */
+        final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
+        while (disabledIt.hasNext()) {
+            final PackageSetting disabledPs = disabledIt.next();
+            final Object id = getUserIdLPr(disabledPs.appId);
+            if (id != null && id instanceof SharedUserSetting) {
+                disabledPs.sharedUser = (SharedUserSetting) id;
+            }
+        }
+
+        mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
+                + mSharedUsers.size() + " shared uids\n");
+
+        return true;
+    }
+
+    void readDefaultPreferredAppsLPw(PackageManagerService service, int userId) {
+        // First pull data from any pre-installed apps.
+        for (PackageSetting ps : mPackages.values()) {
+            if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0 && ps.pkg != null
+                    && ps.pkg.preferredActivityFilters != null) {
+                ArrayList<PackageParser.ActivityIntentInfo> intents
+                        = ps.pkg.preferredActivityFilters;
+                for (int i=0; i<intents.size(); i++) {
+                    PackageParser.ActivityIntentInfo aii = intents.get(i);
+                    applyDefaultPreferredActivityLPw(service, aii, new ComponentName(
+                            ps.name, aii.activity.className), userId);
+                }
+            }
+        }
+
+        // Read preferred apps from .../etc/preferred-apps directory.
+        File preferredDir = new File(Environment.getRootDirectory(), "etc/preferred-apps");
+        if (!preferredDir.exists() || !preferredDir.isDirectory()) {
+            return;
+        }
+        if (!preferredDir.canRead()) {
+            Slog.w(TAG, "Directory " + preferredDir + " cannot be read");
+            return;
+        }
+
+        // Iterate over the files in the directory and scan .xml files
+        for (File f : preferredDir.listFiles()) {
+            if (!f.getPath().endsWith(".xml")) {
+                Slog.i(TAG, "Non-xml file " + f + " in " + preferredDir + " directory, ignoring");
+                continue;
+            }
+            if (!f.canRead()) {
+                Slog.w(TAG, "Preferred apps file " + f + " cannot be read");
+                continue;
+            }
+
+            if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Reading default preferred " + f);
+            FileInputStream str = null;
+            try {
+                str = new FileInputStream(f);
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(str, null);
+
+                int type;
+                while ((type = parser.next()) != XmlPullParser.START_TAG
+                        && type != XmlPullParser.END_DOCUMENT) {
+                    ;
+                }
+
+                if (type != XmlPullParser.START_TAG) {
+                    Slog.w(TAG, "Preferred apps file " + f + " does not have start tag");
+                    continue;
+                }
+                if (!"preferred-activities".equals(parser.getName())) {
+                    Slog.w(TAG, "Preferred apps file " + f
+                            + " does not start with 'preferred-activities'");
+                    continue;
+                }
+                readDefaultPreferredActivitiesLPw(service, parser, userId);
+            } catch (XmlPullParserException e) {
+                Slog.w(TAG, "Error reading apps file " + f, e);
+            } catch (IOException e) {
+                Slog.w(TAG, "Error reading apps file " + f, e);
+            } finally {
+                if (str != null) {
+                    try {
+                        str.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    private void applyDefaultPreferredActivityLPw(PackageManagerService service,
+            IntentFilter tmpPa, ComponentName cn, int userId) {
+        // The initial preferences only specify the target activity
+        // component and intent-filter, not the set of matches.  So we
+        // now need to query for the matches to build the correct
+        // preferred activity entry.
+        if (PackageManagerService.DEBUG_PREFERRED) {
+            Log.d(TAG, "Processing preferred:");
+            tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), "  ");
+        }
+        Intent intent = new Intent();
+        int flags = 0;
+        intent.setAction(tmpPa.getAction(0));
+        for (int i=0; i<tmpPa.countCategories(); i++) {
+            String cat = tmpPa.getCategory(i);
+            if (cat.equals(Intent.CATEGORY_DEFAULT)) {
+                flags |= PackageManager.MATCH_DEFAULT_ONLY;
+            } else {
+                intent.addCategory(cat);
+            }
+        }
+
+        boolean doNonData = true;
+        boolean hasSchemes = false;
+
+        for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
+            boolean doScheme = true;
+            String scheme = tmpPa.getDataScheme(ischeme);
+            if (scheme != null && !scheme.isEmpty()) {
+                hasSchemes = true;
+            }
+            for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) {
+                Uri.Builder builder = new Uri.Builder();
+                builder.scheme(scheme);
+                PatternMatcher ssp = tmpPa.getDataSchemeSpecificPart(issp);
+                builder.opaquePart(ssp.getPath());
+                Intent finalIntent = new Intent(intent);
+                finalIntent.setData(builder.build());
+                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                        scheme, ssp, null, null, null, userId);
+                doScheme = false;
+            }
+            for (int iauth=0; iauth<tmpPa.countDataAuthorities(); iauth++) {
+                boolean doAuth = true;
+                IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(iauth);
+                for (int ipath=0; ipath<tmpPa.countDataPaths(); ipath++) {
+                    Uri.Builder builder = new Uri.Builder();
+                    builder.scheme(scheme);
+                    if (auth.getHost() != null) {
+                        builder.authority(auth.getHost());
+                    }
+                    PatternMatcher path = tmpPa.getDataPath(ipath);
+                    builder.path(path.getPath());
+                    Intent finalIntent = new Intent(intent);
+                    finalIntent.setData(builder.build());
+                    applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                            scheme, null, auth, path, null, userId);
+                    doAuth = doScheme = false;
+                }
+                if (doAuth) {
+                    Uri.Builder builder = new Uri.Builder();
+                    builder.scheme(scheme);
+                    if (auth.getHost() != null) {
+                        builder.authority(auth.getHost());
+                    }
+                    Intent finalIntent = new Intent(intent);
+                    finalIntent.setData(builder.build());
+                    applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                            scheme, null, auth, null, null, userId);
+                    doScheme = false;
+                }
+            }
+            if (doScheme) {
+                Uri.Builder builder = new Uri.Builder();
+                builder.scheme(scheme);
+                Intent finalIntent = new Intent(intent);
+                finalIntent.setData(builder.build());
+                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                        scheme, null, null, null, null, userId);
+            }
+            doNonData = false;
+        }
+
+        for (int idata=0; idata<tmpPa.countDataTypes(); idata++) {
+            String mimeType = tmpPa.getDataType(idata);
+            if (hasSchemes) {
+                Uri.Builder builder = new Uri.Builder();
+                for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) {
+                    String scheme = tmpPa.getDataScheme(ischeme);
+                    if (scheme != null && !scheme.isEmpty()) {
+                        Intent finalIntent = new Intent(intent);
+                        builder.scheme(scheme);
+                        finalIntent.setDataAndType(builder.build(), mimeType);
+                        applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                                scheme, null, null, null, mimeType, userId);
+                    }
+                }
+            } else {
+                Intent finalIntent = new Intent(intent);
+                finalIntent.setType(mimeType);
+                applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn,
+                        null, null, null, null, mimeType, userId);
+            }
+            doNonData = false;
+        }
+
+        if (doNonData) {
+            applyDefaultPreferredActivityLPw(service, intent, flags, cn,
+                    null, null, null, null, null, userId);
+        }
+    }
+
+    private void applyDefaultPreferredActivityLPw(PackageManagerService service,
+            Intent intent, int flags, ComponentName cn, String scheme, PatternMatcher ssp,
+            IntentFilter.AuthorityEntry auth, PatternMatcher path, String mimeType,
+            int userId) {
+        List<ResolveInfo> ri = service.mActivities.queryIntent(intent,
+                intent.getType(), flags, 0);
+        if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent
+                + " results: " + ri);
+        int match = 0;
+        if (ri != null && ri.size() > 1) {
+            boolean haveAct = false;
+            boolean haveNonSys = false;
+            ComponentName[] set = new ComponentName[ri.size()];
+            for (int i=0; i<ri.size(); i++) {
+                ActivityInfo ai = ri.get(i).activityInfo;
+                set[i] = new ComponentName(ai.packageName, ai.name);
+                if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
+                    // If any of the matches are not system apps, then
+                    // there is a third party app that is now an option...
+                    // so don't set a default since we don't want to hide it.
+                    if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                            + ai.packageName + "/" + ai.name + ": non-system!");
+                    haveNonSys = true;
+                    break;
+                } else if (cn.getPackageName().equals(ai.packageName)
+                        && cn.getClassName().equals(ai.name)) {
+                    if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                            + ai.packageName + "/" + ai.name + ": default!");
+                    haveAct = true;
+                    match = ri.get(i).match;
+                } else {
+                    if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result "
+                            + ai.packageName + "/" + ai.name + ": skipped");
+                }
+            }
+            if (haveAct && !haveNonSys) {
+                IntentFilter filter = new IntentFilter();
+                if (intent.getAction() != null) {
+                    filter.addAction(intent.getAction());
+                }
+                if (intent.getCategories() != null) {
+                    for (String cat : intent.getCategories()) {
+                        filter.addCategory(cat);
+                    }
+                }
+                if ((flags&PackageManager.MATCH_DEFAULT_ONLY) != 0) {
+                    filter.addCategory(Intent.CATEGORY_DEFAULT);
+                }
+                if (scheme != null) {
+                    filter.addDataScheme(scheme);
+                }
+                if (ssp != null) {
+                    filter.addDataSchemeSpecificPart(ssp.getPath(), ssp.getType());
+                }
+                if (auth != null) {
+                    filter.addDataAuthority(auth);
+                }
+                if (path != null) {
+                    filter.addDataPath(path);
+                }
+                if (intent.getType() != null) {
+                    try {
+                        filter.addDataType(intent.getType());
+                    } catch (IntentFilter.MalformedMimeTypeException ex) {
+                        Slog.w(TAG, "Malformed mimetype " + intent.getType() + " for " + cn);
+                    }
+                }
+                PreferredActivity pa = new PreferredActivity(filter, match, set, cn, true);
+                editPreferredActivitiesLPw(userId).addFilter(pa);
+            } else if (!haveNonSys) {
+                Slog.w(TAG, "No component found for default preferred activity " + cn);
+            }
+        }
+    }
+
+    private void readDefaultPreferredActivitiesLPw(PackageManagerService service,
+            XmlPullParser parser, int userId)
+            throws XmlPullParserException, IOException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                PreferredActivity tmpPa = new PreferredActivity(parser);
+                if (tmpPa.mPref.getParseError() == null) {
+                    applyDefaultPreferredActivityLPw(service, tmpPa, tmpPa.mPref.mComponent,
+                            userId);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <preferred-activity> "
+                                    + tmpPa.mPref.getParseError() + " at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <preferred-activities>: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+    }
+
+    private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
+        String v = parser.getAttributeValue(ns, name);
+        try {
+            if (v == null) {
+                return defValue;
+            }
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: attribute " + name
+                            + " has bad integer value " + v + " at "
+                            + parser.getPositionDescription());
+        }
+        return defValue;
+    }
+
+    private void readPermissionsLPw(HashMap<String, BasePermission> out, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            final String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                final String name = parser.getAttributeValue(null, ATTR_NAME);
+                final String sourcePackage = parser.getAttributeValue(null, "package");
+                final String ptype = parser.getAttributeValue(null, "type");
+                if (name != null && sourcePackage != null) {
+                    final boolean dynamic = "dynamic".equals(ptype);
+                    final BasePermission bp = new BasePermission(name, sourcePackage,
+                            dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
+                    bp.protectionLevel = readInt(parser, null, "protection",
+                            PermissionInfo.PROTECTION_NORMAL);
+                    bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+                    if (dynamic) {
+                        PermissionInfo pi = new PermissionInfo();
+                        pi.packageName = sourcePackage.intern();
+                        pi.name = name.intern();
+                        pi.icon = readInt(parser, null, "icon", 0);
+                        pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
+                        pi.protectionLevel = bp.protectionLevel;
+                        bp.pendingInfo = pi;
+                    }
+                    out.put(bp.name, bp);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: permissions has" + " no name at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element reading permissions: " + parser.getName() + " at "
+                                + parser.getPositionDescription());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
+            IOException {
+        String name = parser.getAttributeValue(null, ATTR_NAME);
+        String realName = parser.getAttributeValue(null, "realName");
+        String codePathStr = parser.getAttributeValue(null, "codePath");
+        String resourcePathStr = parser.getAttributeValue(null, "resourcePath");
+        String nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");
+        if (resourcePathStr == null) {
+            resourcePathStr = codePathStr;
+        }
+        String version = parser.getAttributeValue(null, "version");
+        int versionCode = 0;
+        if (version != null) {
+            try {
+                versionCode = Integer.parseInt(version);
+            } catch (NumberFormatException e) {
+            }
+        }
+
+        int pkgFlags = 0;
+        pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+        final File codePathFile = new File(codePathStr);
+        if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+            pkgFlags |= ApplicationInfo.FLAG_PRIVILEGED;
+        }
+        PackageSetting ps = new PackageSetting(name, realName, codePathFile,
+                new File(resourcePathStr), nativeLibraryPathStr, versionCode, pkgFlags);
+        String timeStampStr = parser.getAttributeValue(null, "ft");
+        if (timeStampStr != null) {
+            try {
+                long timeStamp = Long.parseLong(timeStampStr, 16);
+                ps.setTimeStamp(timeStamp);
+            } catch (NumberFormatException e) {
+            }
+        } else {
+            timeStampStr = parser.getAttributeValue(null, "ts");
+            if (timeStampStr != null) {
+                try {
+                    long timeStamp = Long.parseLong(timeStampStr);
+                    ps.setTimeStamp(timeStamp);
+                } catch (NumberFormatException e) {
+                }
+            }
+        }
+        timeStampStr = parser.getAttributeValue(null, "it");
+        if (timeStampStr != null) {
+            try {
+                ps.firstInstallTime = Long.parseLong(timeStampStr, 16);
+            } catch (NumberFormatException e) {
+            }
+        }
+        timeStampStr = parser.getAttributeValue(null, "ut");
+        if (timeStampStr != null) {
+            try {
+                ps.lastUpdateTime = Long.parseLong(timeStampStr, 16);
+            } catch (NumberFormatException e) {
+            }
+        }
+        String idStr = parser.getAttributeValue(null, "userId");
+        ps.appId = idStr != null ? Integer.parseInt(idStr) : 0;
+        if (ps.appId <= 0) {
+            String sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
+            ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
+        }
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals("perms")) {
+                readGrantedPermissionsLPw(parser, ps.grantedPermissions);
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <updated-package>: " + parser.getName());
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+
+        mDisabledSysPackages.put(name, ps);
+    }
+
+    private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
+        String name = null;
+        String realName = null;
+        String idStr = null;
+        String sharedIdStr = null;
+        String codePathStr = null;
+        String resourcePathStr = null;
+        String nativeLibraryPathStr = null;
+        String systemStr = null;
+        String installerPackageName = null;
+        String uidError = null;
+        int pkgFlags = 0;
+        long timeStamp = 0;
+        long firstInstallTime = 0;
+        long lastUpdateTime = 0;
+        PackageSettingBase packageSetting = null;
+        String version = null;
+        int versionCode = 0;
+        try {
+            name = parser.getAttributeValue(null, ATTR_NAME);
+            realName = parser.getAttributeValue(null, "realName");
+            idStr = parser.getAttributeValue(null, "userId");
+            uidError = parser.getAttributeValue(null, "uidError");
+            sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
+            codePathStr = parser.getAttributeValue(null, "codePath");
+            resourcePathStr = parser.getAttributeValue(null, "resourcePath");
+            nativeLibraryPathStr = parser.getAttributeValue(null, "nativeLibraryPath");
+            version = parser.getAttributeValue(null, "version");
+            if (version != null) {
+                try {
+                    versionCode = Integer.parseInt(version);
+                } catch (NumberFormatException e) {
+                }
+            }
+            installerPackageName = parser.getAttributeValue(null, "installer");
+
+            systemStr = parser.getAttributeValue(null, "flags");
+            if (systemStr != null) {
+                try {
+                    pkgFlags = Integer.parseInt(systemStr);
+                } catch (NumberFormatException e) {
+                }
+            } else {
+                // For backward compatibility
+                systemStr = parser.getAttributeValue(null, "system");
+                if (systemStr != null) {
+                    pkgFlags |= ("true".equalsIgnoreCase(systemStr)) ? ApplicationInfo.FLAG_SYSTEM
+                            : 0;
+                } else {
+                    // Old settings that don't specify system... just treat
+                    // them as system, good enough.
+                    pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+                }
+            }
+            String timeStampStr = parser.getAttributeValue(null, "ft");
+            if (timeStampStr != null) {
+                try {
+                    timeStamp = Long.parseLong(timeStampStr, 16);
+                } catch (NumberFormatException e) {
+                }
+            } else {
+                timeStampStr = parser.getAttributeValue(null, "ts");
+                if (timeStampStr != null) {
+                    try {
+                        timeStamp = Long.parseLong(timeStampStr);
+                    } catch (NumberFormatException e) {
+                    }
+                }
+            }
+            timeStampStr = parser.getAttributeValue(null, "it");
+            if (timeStampStr != null) {
+                try {
+                    firstInstallTime = Long.parseLong(timeStampStr, 16);
+                } catch (NumberFormatException e) {
+                }
+            }
+            timeStampStr = parser.getAttributeValue(null, "ut");
+            if (timeStampStr != null) {
+                try {
+                    lastUpdateTime = Long.parseLong(timeStampStr, 16);
+                } catch (NumberFormatException e) {
+                }
+            }
+            if (PackageManagerService.DEBUG_SETTINGS)
+                Log.v(PackageManagerService.TAG, "Reading package: " + name + " userId=" + idStr
+                        + " sharedUserId=" + sharedIdStr);
+            int userId = idStr != null ? Integer.parseInt(idStr) : 0;
+            if (resourcePathStr == null) {
+                resourcePathStr = codePathStr;
+            }
+            if (realName != null) {
+                realName = realName.intern();
+            }
+            if (name == null) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Error in package manager settings: <package> has no name at "
+                                + parser.getPositionDescription());
+            } else if (codePathStr == null) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Error in package manager settings: <package> has no codePath at "
+                                + parser.getPositionDescription());
+            } else if (userId > 0) {
+                packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
+                        new File(resourcePathStr), nativeLibraryPathStr, userId, versionCode,
+                        pkgFlags);
+                if (PackageManagerService.DEBUG_SETTINGS)
+                    Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
+                            + userId + " pkg=" + packageSetting);
+                if (packageSetting == null) {
+                    PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid "
+                            + userId + " while parsing settings at "
+                            + parser.getPositionDescription());
+                } else {
+                    packageSetting.setTimeStamp(timeStamp);
+                    packageSetting.firstInstallTime = firstInstallTime;
+                    packageSetting.lastUpdateTime = lastUpdateTime;
+                }
+            } else if (sharedIdStr != null) {
+                userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
+                if (userId > 0) {
+                    packageSetting = new PendingPackage(name.intern(), realName, new File(
+                            codePathStr), new File(resourcePathStr), nativeLibraryPathStr, userId,
+                            versionCode, pkgFlags);
+                    packageSetting.setTimeStamp(timeStamp);
+                    packageSetting.firstInstallTime = firstInstallTime;
+                    packageSetting.lastUpdateTime = lastUpdateTime;
+                    mPendingPackages.add((PendingPackage) packageSetting);
+                    if (PackageManagerService.DEBUG_SETTINGS)
+                        Log.i(PackageManagerService.TAG, "Reading package " + name
+                                + ": sharedUserId=" + userId + " pkg=" + packageSetting);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: package " + name
+                                    + " has bad sharedId " + sharedIdStr + " at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Error in package manager settings: package " + name + " has bad userId "
+                                + idStr + " at " + parser.getPositionDescription());
+            }
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: package " + name + " has bad userId "
+                            + idStr + " at " + parser.getPositionDescription());
+        }
+        if (packageSetting != null) {
+            packageSetting.uidError = "true".equals(uidError);
+            packageSetting.installerPackageName = installerPackageName;
+            packageSetting.nativeLibraryPathString = nativeLibraryPathStr;
+            // Handle legacy string here for single-user mode
+            final String enabledStr = parser.getAttributeValue(null, ATTR_ENABLED);
+            if (enabledStr != null) {
+                try {
+                    packageSetting.setEnabled(Integer.parseInt(enabledStr), 0 /* userId */, null);
+                } catch (NumberFormatException e) {
+                    if (enabledStr.equalsIgnoreCase("true")) {
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_ENABLED, 0, null);
+                    } else if (enabledStr.equalsIgnoreCase("false")) {
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DISABLED, 0, null);
+                    } else if (enabledStr.equalsIgnoreCase("default")) {
+                        packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
+                    } else {
+                        PackageManagerService.reportSettingsProblem(Log.WARN,
+                                "Error in package manager settings: package " + name
+                                        + " has bad enabled value: " + idStr + " at "
+                                        + parser.getPositionDescription());
+                    }
+                }
+            } else {
+                packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
+            }
+
+            final String installStatusStr = parser.getAttributeValue(null, "installStatus");
+            if (installStatusStr != null) {
+                if (installStatusStr.equalsIgnoreCase("false")) {
+                    packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_INCOMPLETE;
+                } else {
+                    packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE;
+                }
+            }
+
+            int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                // Legacy 
+                if (tagName.equals(TAG_DISABLED_COMPONENTS)) {
+                    readDisabledComponentsLPw(packageSetting, parser, 0);
+                } else if (tagName.equals(TAG_ENABLED_COMPONENTS)) {
+                    readEnabledComponentsLPw(packageSetting, parser, 0);
+                } else if (tagName.equals("sigs")) {
+                    packageSetting.signatures.readXml(parser, mPastSignatures);
+                } else if (tagName.equals("perms")) {
+                    readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions);
+                    packageSetting.permissionsFixed = true;
+                } else if (tagName.equals("signing-keyset")) {
+                    long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
+                    packageSetting.keySetData.addSigningKeySet(id);
+                    if (false) Slog.d(TAG, "Adding signing keyset " + Long.toString(id)
+                            + " to " + name);
+                } else if (tagName.equals("defined-keyset")) {
+                    long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
+                    String alias = parser.getAttributeValue(null, "alias");
+                    packageSetting.keySetData.addDefinedKeySet(id, alias);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Unknown element under <package>: " + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+
+        } else {
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
+            int userId) throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name != null) {
+                    packageSetting.addDisabledComponent(name.intern(), userId);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <disabled-components> has"
+                                    + " no name at " + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <disabled-components>: " + parser.getName());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    private void readEnabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
+            int userId) throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name != null) {
+                    packageSetting.addEnabledComponent(name.intern(), userId);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <enabled-components> has"
+                                    + " no name at " + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <enabled-components>: " + parser.getName());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
+        String name = null;
+        String idStr = null;
+        int pkgFlags = 0;
+        SharedUserSetting su = null;
+        try {
+            name = parser.getAttributeValue(null, ATTR_NAME);
+            idStr = parser.getAttributeValue(null, "userId");
+            int userId = idStr != null ? Integer.parseInt(idStr) : 0;
+            if ("true".equals(parser.getAttributeValue(null, "system"))) {
+                pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
+            }
+            if (name == null) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Error in package manager settings: <shared-user> has no name at "
+                                + parser.getPositionDescription());
+            } else if (userId == 0) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Error in package manager settings: shared-user " + name
+                                + " has bad userId " + idStr + " at "
+                                + parser.getPositionDescription());
+            } else {
+                if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags)) == null) {
+                    PackageManagerService
+                            .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
+                                    + parser.getPositionDescription());
+                }
+            }
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: package " + name + " has bad userId "
+                            + idStr + " at " + parser.getPositionDescription());
+        }
+        ;
+
+        if (su != null) {
+            int outerDepth = parser.getDepth();
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("sigs")) {
+                    su.signatures.readXml(parser, mPastSignatures);
+                } else if (tagName.equals("perms")) {
+                    readGrantedPermissionsLPw(parser, su.grantedPermissions);
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Unknown element under <shared-user>: " + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+
+        } else {
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    private void readGrantedPermissionsLPw(XmlPullParser parser, HashSet<String> outPerms)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            String tagName = parser.getName();
+            if (tagName.equals(TAG_ITEM)) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name != null) {
+                    outPerms.add(name.intern());
+                } else {
+                    PackageManagerService.reportSettingsProblem(Log.WARN,
+                            "Error in package manager settings: <perms> has" + " no name at "
+                                    + parser.getPositionDescription());
+                }
+            } else {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element under <perms>: " + parser.getName());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    void createNewUserLILPw(PackageManagerService service, Installer installer,
+            int userHandle, File path) {
+        path.mkdir();
+        FileUtils.setPermissions(path.toString(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                | FileUtils.S_IXOTH, -1, -1);
+        for (PackageSetting ps : mPackages.values()) {
+            // Only system apps are initially installed.
+            ps.setInstalled((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0, userHandle);
+            // Need to create a data directory for all apps under this user.
+            installer.createUserData(ps.name,
+                    UserHandle.getUid(userHandle, ps.appId), userHandle,
+                    ps.pkg.applicationInfo.seinfo);
+        }
+        readDefaultPreferredAppsLPw(service, userHandle);
+        writePackageRestrictionsLPr(userHandle);
+    }
+
+    void removeUserLPr(int userId) {
+        Set<Entry<String, PackageSetting>> entries = mPackages.entrySet();
+        for (Entry<String, PackageSetting> entry : entries) {
+            entry.getValue().removeUser(userId);
+        }
+        mPreferredActivities.remove(userId);
+        File file = getUserPackagesStateFile(userId);
+        file.delete();
+        file = getUserPackagesStateBackupFile(userId);
+        file.delete();
+    }
+
+    // This should be called (at least) whenever an application is removed
+    private void setFirstAvailableUid(int uid) {
+        if (uid > mFirstAvailableUid) {
+            mFirstAvailableUid = uid;
+        }
+    }
+
+    // Returns -1 if we could not find an available UserId to assign
+    private int newUserIdLPw(Object obj) {
+        // Let's be stupidly inefficient for now...
+        final int N = mUserIds.size();
+        for (int i = mFirstAvailableUid; i < N; i++) {
+            if (mUserIds.get(i) == null) {
+                mUserIds.set(i, obj);
+                return Process.FIRST_APPLICATION_UID + i;
+            }
+        }
+
+        // None left?
+        if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
+            return -1;
+        }
+
+        mUserIds.add(obj);
+        return Process.FIRST_APPLICATION_UID + N;
+    }
+
+    public VerifierDeviceIdentity getVerifierDeviceIdentityLPw() {
+        if (mVerifierDeviceIdentity == null) {
+            mVerifierDeviceIdentity = VerifierDeviceIdentity.generate();
+
+            writeLPr();
+        }
+
+        return mVerifierDeviceIdentity;
+    }
+
+    public PackageSetting getDisabledSystemPkgLPr(String name) {
+        PackageSetting ps = mDisabledSysPackages.get(name);
+        return ps;
+    }
+
+    private String compToString(HashSet<String> cmp) {
+        return cmp != null ? Arrays.toString(cmp.toArray()) : "[]";
+    }
+ 
+    boolean isEnabledLPr(ComponentInfo componentInfo, int flags, int userId) {
+        if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) {
+            return true;
+        }
+        final String pkgName = componentInfo.packageName;
+        final PackageSetting packageSettings = mPackages.get(pkgName);
+        if (PackageManagerService.DEBUG_SETTINGS) {
+            Log.v(PackageManagerService.TAG, "isEnabledLock - packageName = "
+                    + componentInfo.packageName + " componentName = " + componentInfo.name);
+            Log.v(PackageManagerService.TAG, "enabledComponents: "
+                    + compToString(packageSettings.getEnabledComponents(userId)));
+            Log.v(PackageManagerService.TAG, "disabledComponents: "
+                    + compToString(packageSettings.getDisabledComponents(userId)));
+        }
+        if (packageSettings == null) {
+            return false;
+        }
+        PackageUserState ustate = packageSettings.readUserState(userId);
+        if ((flags&PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS) != 0) {
+            if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                return true;
+            }
+        }
+        if (ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED
+                || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_USER
+                || ustate.enabled == COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+                || (packageSettings.pkg != null && !packageSettings.pkg.applicationInfo.enabled
+                    && ustate.enabled == COMPONENT_ENABLED_STATE_DEFAULT)) {
+            return false;
+        }
+        if (ustate.enabledComponents != null
+                && ustate.enabledComponents.contains(componentInfo.name)) {
+            return true;
+        }
+        if (ustate.disabledComponents != null
+                && ustate.disabledComponents.contains(componentInfo.name)) {
+            return false;
+        }
+        return componentInfo.enabled;
+    }
+
+    String getInstallerPackageNameLPr(String packageName) {
+        final PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        return pkg.installerPackageName;
+    }
+
+    int getApplicationEnabledSettingLPr(String packageName, int userId) {
+        final PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        return pkg.getEnabled(userId);
+    }
+
+    int getComponentEnabledSettingLPr(ComponentName componentName, int userId) {
+        final String packageName = componentName.getPackageName();
+        final PackageSetting pkg = mPackages.get(packageName);
+        if (pkg == null) {
+            throw new IllegalArgumentException("Unknown component: " + componentName);
+        }
+        final String classNameStr = componentName.getClassName();
+        return pkg.getCurrentEnabledStateLPr(classNameStr, userId);
+    }
+
+    boolean setPackageStoppedStateLPw(String packageName, boolean stopped,
+            boolean allowedByPermission, int uid, int userId) {
+        int appId = UserHandle.getAppId(uid);
+        final PackageSetting pkgSetting = mPackages.get(packageName);
+        if (pkgSetting == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (!allowedByPermission && (appId != pkgSetting.appId)) {
+            throw new SecurityException(
+                    "Permission Denial: attempt to change stopped state from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + uid + ", package uid=" + pkgSetting.appId);
+        }
+        if (DEBUG_STOPPED) {
+            if (stopped) {
+                RuntimeException e = new RuntimeException("here");
+                e.fillInStackTrace();
+                Slog.i(TAG, "Stopping package " + packageName, e);
+            }
+        }
+        if (pkgSetting.getStopped(userId) != stopped) {
+            pkgSetting.setStopped(stopped, userId);
+            // pkgSetting.pkg.mSetStopped = stopped;
+            if (pkgSetting.getNotLaunched(userId)) {
+                if (pkgSetting.installerPackageName != null) {
+                    PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
+                            pkgSetting.name, null,
+                            pkgSetting.installerPackageName, null, new int[] {userId});
+                }
+                pkgSetting.setNotLaunched(false, userId);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private List<UserInfo> getAllUsers() {
+        long id = Binder.clearCallingIdentity();
+        try {
+            return UserManagerService.getInstance().getUsers(false);
+        } catch (NullPointerException npe) {
+            // packagemanager not yet initialized
+        } finally {
+            Binder.restoreCallingIdentity(id);
+        }
+        return null;
+    }
+
+    static final void printFlags(PrintWriter pw, int val, Object[] spec) {
+        pw.print("[ ");
+        for (int i=0; i<spec.length; i+=2) {
+            int mask = (Integer)spec[i];
+            if ((val & mask) != 0) {
+                pw.print(spec[i+1]);
+                pw.print(" ");
+            }
+        }
+        pw.print("]");
+    }
+
+    static final Object[] FLAG_DUMP_SPEC = new Object[] {
+        ApplicationInfo.FLAG_SYSTEM, "SYSTEM",
+        ApplicationInfo.FLAG_DEBUGGABLE, "DEBUGGABLE",
+        ApplicationInfo.FLAG_HAS_CODE, "HAS_CODE",
+        ApplicationInfo.FLAG_PERSISTENT, "PERSISTENT",
+        ApplicationInfo.FLAG_FACTORY_TEST, "FACTORY_TEST",
+        ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING, "ALLOW_TASK_REPARENTING",
+        ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA, "ALLOW_CLEAR_USER_DATA",
+        ApplicationInfo.FLAG_UPDATED_SYSTEM_APP, "UPDATED_SYSTEM_APP",
+        ApplicationInfo.FLAG_TEST_ONLY, "TEST_ONLY",
+        ApplicationInfo.FLAG_VM_SAFE_MODE, "VM_SAFE_MODE",
+        ApplicationInfo.FLAG_ALLOW_BACKUP, "ALLOW_BACKUP",
+        ApplicationInfo.FLAG_KILL_AFTER_RESTORE, "KILL_AFTER_RESTORE",
+        ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION",
+        ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE",
+        ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP",
+        ApplicationInfo.FLAG_PRIVILEGED, "PRIVILEGED",
+        ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK",
+        ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE",
+    };
+
+    void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf,
+            Date date, List<UserInfo> users) {
+        pw.print(prefix); pw.print("Package [");
+            pw.print(ps.realName != null ? ps.realName : ps.name);
+            pw.print("] (");
+            pw.print(Integer.toHexString(System.identityHashCode(ps)));
+            pw.println("):");
+
+        if (ps.realName != null) {
+            pw.print(prefix); pw.print("  compat name=");
+            pw.println(ps.name);
+        }
+
+        pw.print(prefix); pw.print("  userId="); pw.print(ps.appId);
+                pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+        if (ps.sharedUser != null) {
+            pw.print(prefix); pw.print("  sharedUser="); pw.println(ps.sharedUser);
+        }
+        pw.print(prefix); pw.print("  pkg="); pw.println(ps.pkg);
+        pw.print(prefix); pw.print("  codePath="); pw.println(ps.codePathString);
+        pw.print(prefix); pw.print("  resourcePath="); pw.println(ps.resourcePathString);
+        pw.print(prefix); pw.print("  nativeLibraryPath="); pw.println(ps.nativeLibraryPathString);
+        pw.print(prefix); pw.print("  versionCode="); pw.print(ps.versionCode);
+        if (ps.pkg != null) {
+            pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion);
+        }
+        pw.println();
+        if (ps.pkg != null) {
+            pw.print(prefix); pw.print("  versionName="); pw.println(ps.pkg.mVersionName);
+            pw.print(prefix); pw.print("  applicationInfo=");
+                pw.println(ps.pkg.applicationInfo.toString());
+            pw.print(prefix); pw.print("  flags="); printFlags(pw, ps.pkg.applicationInfo.flags,
+                    FLAG_DUMP_SPEC); pw.println();
+            pw.print(prefix); pw.print("  dataDir="); pw.println(ps.pkg.applicationInfo.dataDir);
+            if (ps.pkg.mOperationPending) {
+                pw.print(prefix); pw.println("  mOperationPending=true");
+            }
+            pw.print(prefix); pw.print("  supportsScreens=[");
+            boolean first = true;
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("small");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("medium");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("large");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("xlarge");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("resizeable");
+            }
+            if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) {
+                if (!first)
+                    pw.print(", ");
+                first = false;
+                pw.print("anyDensity");
+            }
+            pw.println("]");
+            if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) {
+                pw.print(prefix); pw.println("  libraries:");
+                for (int i=0; i<ps.pkg.libraryNames.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.libraryNames.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesLibraries:");
+                for (int i=0; i<ps.pkg.usesLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesOptionalLibraries != null
+                    && ps.pkg.usesOptionalLibraries.size() > 0) {
+                pw.print(prefix); pw.println("  usesOptionalLibraries:");
+                for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) {
+                    pw.print(prefix); pw.print("    ");
+                        pw.println(ps.pkg.usesOptionalLibraries.get(i));
+                }
+            }
+            if (ps.pkg.usesLibraryFiles != null
+                    && ps.pkg.usesLibraryFiles.length > 0) {
+                pw.print(prefix); pw.println("  usesLibraryFiles:");
+                for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) {
+                    pw.print(prefix); pw.print("    "); pw.println(ps.pkg.usesLibraryFiles[i]);
+                }
+            }
+        }
+        pw.print(prefix); pw.print("  timeStamp=");
+            date.setTime(ps.timeStamp);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  firstInstallTime=");
+            date.setTime(ps.firstInstallTime);
+            pw.println(sdf.format(date));
+        pw.print(prefix); pw.print("  lastUpdateTime=");
+            date.setTime(ps.lastUpdateTime);
+            pw.println(sdf.format(date));
+        if (ps.installerPackageName != null) {
+            pw.print(prefix); pw.print("  installerPackageName=");
+                    pw.println(ps.installerPackageName);
+        }
+        pw.print(prefix); pw.print("  signatures="); pw.println(ps.signatures);
+        pw.print(prefix); pw.print("  permissionsFixed="); pw.print(ps.permissionsFixed);
+                pw.print(" haveGids="); pw.print(ps.haveGids);
+                pw.print(" installStatus="); pw.println(ps.installStatus);
+        pw.print(prefix); pw.print("  pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
+                pw.println();
+        for (UserInfo user : users) {
+            pw.print(prefix); pw.print("  User "); pw.print(user.id); pw.print(": ");
+            pw.print(" installed=");
+            pw.print(ps.getInstalled(user.id));
+            pw.print(" blocked=");
+            pw.print(ps.getBlocked(user.id));
+            pw.print(" stopped=");
+            pw.print(ps.getStopped(user.id));
+            pw.print(" notLaunched=");
+            pw.print(ps.getNotLaunched(user.id));
+            pw.print(" enabled=");
+            pw.println(ps.getEnabled(user.id));
+            String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
+            if (lastDisabledAppCaller != null) {
+                pw.print(prefix); pw.print("    lastDisabledCaller: ");
+                        pw.println(lastDisabledAppCaller);
+            }
+            HashSet<String> cmp = ps.getDisabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    disabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+            cmp = ps.getEnabledComponents(user.id);
+            if (cmp != null && cmp.size() > 0) {
+                pw.print(prefix); pw.println("    enabledComponents:");
+                for (String s : cmp) {
+                    pw.print(prefix); pw.print("    "); pw.println(s);
+                }
+            }
+        }
+        if (ps.grantedPermissions.size() > 0) {
+            pw.print(prefix); pw.println("  grantedPermissions:");
+            for (String s : ps.grantedPermissions) {
+                pw.print(prefix); pw.print("    "); pw.println(s);
+            }
+        }
+    }
+
+    void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) {
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        final Date date = new Date();
+        boolean printedSomething = false;
+        List<UserInfo> users = getAllUsers();
+        for (final PackageSetting ps : mPackages.values()) {
+            if (packageName != null && !packageName.equals(ps.realName)
+                    && !packageName.equals(ps.name)) {
+                continue;
+            }
+
+            if (packageName != null) {
+                dumpState.setSharedUser(ps.sharedUser);
+            }
+
+            if (!printedSomething) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Packages:");
+                printedSomething = true;
+            }
+            dumpPackageLPr(pw, "  ", ps, sdf, date, users);
+        }
+
+        printedSomething = false;
+        if (mRenamedPackages.size() > 0) {
+            for (final Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
+                if (packageName != null && !packageName.equals(e.getKey())
+                        && !packageName.equals(e.getValue())) {
+                    continue;
+                }
+                if (!printedSomething) {
+                    if (dumpState.onTitlePrinted())
+                        pw.println();
+                    pw.println("Renamed packages:");
+                    printedSomething = true;
+                }
+                pw.print("  ");
+                pw.print(e.getKey());
+                pw.print(" -> ");
+                pw.println(e.getValue());
+            }
+        }
+
+        printedSomething = false;
+        if (mDisabledSysPackages.size() > 0) {
+            for (final PackageSetting ps : mDisabledSysPackages.values()) {
+                if (packageName != null && !packageName.equals(ps.realName)
+                        && !packageName.equals(ps.name)) {
+                    continue;
+                }
+                if (!printedSomething) {
+                    if (dumpState.onTitlePrinted())
+                        pw.println();
+                    pw.println("Hidden system packages:");
+                    printedSomething = true;
+                }
+                dumpPackageLPr(pw, "  ", ps, sdf, date, users);
+            }
+        }
+    }
+
+    void dumpPermissionsLPr(PrintWriter pw, String packageName, DumpState dumpState) {
+        boolean printedSomething = false;
+        for (BasePermission p : mPermissions.values()) {
+            if (packageName != null && !packageName.equals(p.sourcePackage)) {
+                continue;
+            }
+            if (!printedSomething) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Permissions:");
+                printedSomething = true;
+            }
+            pw.print("  Permission ["); pw.print(p.name); pw.print("] (");
+                    pw.print(Integer.toHexString(System.identityHashCode(p)));
+                    pw.println("):");
+            pw.print("    sourcePackage="); pw.println(p.sourcePackage);
+            pw.print("    uid="); pw.print(p.uid);
+                    pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids));
+                    pw.print(" type="); pw.print(p.type);
+                    pw.print(" prot=");
+                    pw.println(PermissionInfo.protectionToString(p.protectionLevel));
+            if (p.packageSetting != null) {
+                pw.print("    packageSetting="); pw.println(p.packageSetting);
+            }
+            if (p.perm != null) {
+                pw.print("    perm="); pw.println(p.perm);
+            }
+            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
+                pw.print("    enforced=");
+                pw.println(mReadExternalStorageEnforced);
+            }
+        }
+    }
+
+    void dumpSharedUsersLPr(PrintWriter pw, String packageName, DumpState dumpState) {
+        boolean printedSomething = false;
+        for (SharedUserSetting su : mSharedUsers.values()) {
+            if (packageName != null && su != dumpState.getSharedUser()) {
+                continue;
+            }
+            if (!printedSomething) {
+                if (dumpState.onTitlePrinted())
+                    pw.println();
+                pw.println("Shared users:");
+                printedSomething = true;
+            }
+            pw.print("  SharedUser [");
+            pw.print(su.name);
+            pw.print("] (");
+            pw.print(Integer.toHexString(System.identityHashCode(su)));
+                    pw.println("):");
+            pw.print("    userId=");
+            pw.print(su.userId);
+            pw.print(" gids=");
+            pw.println(PackageManagerService.arrayToString(su.gids));
+            pw.println("    grantedPermissions:");
+            for (String s : su.grantedPermissions) {
+                pw.print("      ");
+                pw.println(s);
+            }
+        }
+    }
+
+    void dumpReadMessagesLPr(PrintWriter pw, DumpState dumpState) {
+        pw.println("Settings parse messages:");
+        pw.print(mReadMessages.toString());
+    }
+}
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
new file mode 100644
index 0000000..ca1eeea
--- /dev/null
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import java.util.HashSet;
+
+/**
+ * Settings data for a particular shared user ID we know about.
+ */
+final class SharedUserSetting extends GrantedPermissions {
+    final String name;
+
+    int userId;
+
+    // flags that are associated with this uid, regardless of any package flags
+    int uidFlags;
+
+    final HashSet<PackageSetting> packages = new HashSet<PackageSetting>();
+
+    final PackageSignatures signatures = new PackageSignatures();
+
+    SharedUserSetting(String _name, int _pkgFlags) {
+        super(_pkgFlags);
+        uidFlags =  _pkgFlags;
+        name = _name;
+    }
+
+    @Override
+    public String toString() {
+        return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " "
+                + name + "/" + userId + "}";
+    }
+
+    void removePackage(PackageSetting packageSetting) {
+        if (packages.remove(packageSetting)) {
+            // recalculate the pkgFlags for this shared user if needed
+            if ((this.pkgFlags & packageSetting.pkgFlags) != 0) {
+                int aggregatedFlags = uidFlags;
+                for (PackageSetting ps : packages) {
+                    aggregatedFlags |= ps.pkgFlags;
+                }
+                setFlags(aggregatedFlags);
+            }
+        }
+    }
+
+    void addPackage(PackageSetting packageSetting) {
+        if (packages.add(packageSetting)) {
+            setFlags(this.pkgFlags | packageSetting.pkgFlags);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
new file mode 100644
index 0000000..c33134a
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -0,0 +1,1568 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.ActivityThread;
+import android.app.IStopUserCallback;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.IUserManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+import android.util.Xml;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+public class UserManagerService extends IUserManager.Stub {
+
+    private static final String LOG_TAG = "UserManagerService";
+
+    private static final boolean DBG = false;
+
+    private static final String TAG_NAME = "name";
+    private static final String ATTR_FLAGS = "flags";
+    private static final String ATTR_ICON_PATH = "icon";
+    private static final String ATTR_ID = "id";
+    private static final String ATTR_CREATION_TIME = "created";
+    private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn";
+    private static final String ATTR_SALT = "salt";
+    private static final String ATTR_PIN_HASH = "pinHash";
+    private static final String ATTR_FAILED_ATTEMPTS = "failedAttempts";
+    private static final String ATTR_LAST_RETRY_MS = "lastAttemptMs";
+    private static final String ATTR_SERIAL_NO = "serialNumber";
+    private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber";
+    private static final String ATTR_PARTIAL = "partial";
+    private static final String ATTR_USER_VERSION = "version";
+    private static final String TAG_USERS = "users";
+    private static final String TAG_USER = "user";
+    private static final String TAG_RESTRICTIONS = "restrictions";
+    private static final String TAG_ENTRY = "entry";
+    private static final String TAG_VALUE = "value";
+    private static final String ATTR_KEY = "key";
+    private static final String ATTR_VALUE_TYPE = "type";
+    private static final String ATTR_MULTIPLE = "m";
+
+    private static final String ATTR_TYPE_STRING_ARRAY = "sa";
+    private static final String ATTR_TYPE_STRING = "s";
+    private static final String ATTR_TYPE_BOOLEAN = "b";
+
+    private static final String USER_INFO_DIR = "system" + File.separator + "users";
+    private static final String USER_LIST_FILENAME = "userlist.xml";
+    private static final String USER_PHOTO_FILENAME = "photo.png";
+
+    private static final String RESTRICTIONS_FILE_PREFIX = "res_";
+    private static final String XML_SUFFIX = ".xml";
+
+    private static final int MIN_USER_ID = 10;
+
+    private static final int USER_VERSION = 4;
+
+    private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms
+
+    // Number of attempts before jumping to the next BACKOFF_TIMES slot
+    private static final int BACKOFF_INC_INTERVAL = 5;
+
+    // Amount of time to force the user to wait before entering the PIN again, after failing
+    // BACKOFF_INC_INTERVAL times.
+    private static final int[] BACKOFF_TIMES = { 0, 30*1000, 60*1000, 5*60*1000, 30*60*1000 };
+
+    private final Context mContext;
+    private final PackageManagerService mPm;
+    private final Object mInstallLock;
+    private final Object mPackagesLock;
+
+    private final Handler mHandler;
+
+    private final File mUsersDir;
+    private final File mUserListFile;
+    private final File mBaseUserPath;
+
+    private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>();
+    private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>();
+
+    class RestrictionsPinState {
+        long salt;
+        String pinHash;
+        int failedAttempts;
+        long lastAttemptTime;
+    }
+
+    private final SparseArray<RestrictionsPinState> mRestrictionsPinStates =
+            new SparseArray<RestrictionsPinState>();
+
+    /**
+     * Set of user IDs being actively removed. Removed IDs linger in this set
+     * for several seconds to work around a VFS caching issue.
+     */
+    // @GuardedBy("mPackagesLock")
+    private final SparseBooleanArray mRemovingUserIds = new SparseBooleanArray();
+
+    private int[] mUserIds;
+    private boolean mGuestEnabled;
+    private int mNextSerialNumber;
+    private int mUserVersion = 0;
+
+    private static UserManagerService sInstance;
+
+    public static UserManagerService getInstance() {
+        synchronized (UserManagerService.class) {
+            return sInstance;
+        }
+    }
+
+    /**
+     * Available for testing purposes.
+     */
+    UserManagerService(File dataDir, File baseUserPath) {
+        this(null, null, new Object(), new Object(), dataDir, baseUserPath);
+    }
+
+    /**
+     * Called by package manager to create the service.  This is closely
+     * associated with the package manager, and the given lock is the
+     * package manager's own lock.
+     */
+    UserManagerService(Context context, PackageManagerService pm,
+            Object installLock, Object packagesLock) {
+        this(context, pm, installLock, packagesLock,
+                Environment.getDataDirectory(),
+                new File(Environment.getDataDirectory(), "user"));
+    }
+
+    /**
+     * Available for testing purposes.
+     */
+    private UserManagerService(Context context, PackageManagerService pm,
+            Object installLock, Object packagesLock,
+            File dataDir, File baseUserPath) {
+        mContext = context;
+        mPm = pm;
+        mInstallLock = installLock;
+        mPackagesLock = packagesLock;
+        mHandler = new Handler();
+        synchronized (mInstallLock) {
+            synchronized (mPackagesLock) {
+                mUsersDir = new File(dataDir, USER_INFO_DIR);
+                mUsersDir.mkdirs();
+                // Make zeroth user directory, for services to migrate their files to that location
+                File userZeroDir = new File(mUsersDir, "0");
+                userZeroDir.mkdirs();
+                mBaseUserPath = baseUserPath;
+                FileUtils.setPermissions(mUsersDir.toString(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG
+                        |FileUtils.S_IROTH|FileUtils.S_IXOTH,
+                        -1, -1);
+                mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+                readUserListLocked();
+                // Prune out any partially created/partially removed users.
+                ArrayList<UserInfo> partials = new ArrayList<UserInfo>();
+                for (int i = 0; i < mUsers.size(); i++) {
+                    UserInfo ui = mUsers.valueAt(i);
+                    if (ui.partial && i != 0) {
+                        partials.add(ui);
+                    }
+                }
+                for (int i = 0; i < partials.size(); i++) {
+                    UserInfo ui = partials.get(i);
+                    Slog.w(LOG_TAG, "Removing partially created user #" + i
+                            + " (name=" + ui.name + ")");
+                    removeUserStateLocked(ui.id);
+                }
+                sInstance = this;
+            }
+        }
+    }
+
+    void systemReady() {
+        final Context context = ActivityThread.systemMain().getSystemContext();
+        mUserPackageMonitor.register(context,
+                null, UserHandle.ALL, false);
+        userForeground(UserHandle.USER_OWNER);
+    }
+
+    @Override
+    public List<UserInfo> getUsers(boolean excludeDying) {
+        checkManageUsersPermission("query users");
+        synchronized (mPackagesLock) {
+            ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+            for (int i = 0; i < mUsers.size(); i++) {
+                UserInfo ui = mUsers.valueAt(i);
+                if (ui.partial) {
+                    continue;
+                }
+                if (!excludeDying || !mRemovingUserIds.get(ui.id)) {
+                    users.add(ui);
+                }
+            }
+            return users;
+        }
+    }
+
+    @Override
+    public UserInfo getUserInfo(int userId) {
+        checkManageUsersPermission("query user");
+        synchronized (mPackagesLock) {
+            return getUserInfoLocked(userId);
+        }
+    }
+
+    @Override
+    public boolean isRestricted() {
+        synchronized (mPackagesLock) {
+            return getUserInfoLocked(UserHandle.getCallingUserId()).isRestricted();
+        }
+    }
+
+    /*
+     * Should be locked on mUsers before calling this.
+     */
+    private UserInfo getUserInfoLocked(int userId) {
+        UserInfo ui = mUsers.get(userId);
+        // If it is partial and not in the process of being removed, return as unknown user.
+        if (ui != null && ui.partial && !mRemovingUserIds.get(userId)) {
+            Slog.w(LOG_TAG, "getUserInfo: unknown user #" + userId);
+            return null;
+        }
+        return ui;
+    }
+
+    public boolean exists(int userId) {
+        synchronized (mPackagesLock) {
+            return ArrayUtils.contains(mUserIds, userId);
+        }
+    }
+
+    @Override
+    public void setUserName(int userId, String name) {
+        checkManageUsersPermission("rename users");
+        boolean changed = false;
+        synchronized (mPackagesLock) {
+            UserInfo info = mUsers.get(userId);
+            if (info == null || info.partial) {
+                Slog.w(LOG_TAG, "setUserName: unknown user #" + userId);
+                return;
+            }
+            if (name != null && !name.equals(info.name)) {
+                info.name = name;
+                writeUserLocked(info);
+                changed = true;
+            }
+        }
+        if (changed) {
+            sendUserInfoChangedBroadcast(userId);
+        }
+    }
+
+    @Override
+    public void setUserIcon(int userId, Bitmap bitmap) {
+        checkManageUsersPermission("update users");
+        synchronized (mPackagesLock) {
+            UserInfo info = mUsers.get(userId);
+            if (info == null || info.partial) {
+                Slog.w(LOG_TAG, "setUserIcon: unknown user #" + userId);
+                return;
+            }
+            writeBitmapLocked(info, bitmap);
+            writeUserLocked(info);
+        }
+        sendUserInfoChangedBroadcast(userId);
+    }
+
+    private void sendUserInfoChangedBroadcast(int userId) {
+        Intent changedIntent = new Intent(Intent.ACTION_USER_INFO_CHANGED);
+        changedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        changedIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mContext.sendBroadcastAsUser(changedIntent, UserHandle.ALL);
+    }
+
+    @Override
+    public Bitmap getUserIcon(int userId) {
+        checkManageUsersPermission("read users");
+        synchronized (mPackagesLock) {
+            UserInfo info = mUsers.get(userId);
+            if (info == null || info.partial) {
+                Slog.w(LOG_TAG, "getUserIcon: unknown user #" + userId);
+                return null;
+            }
+            if (info.iconPath == null) {
+                return null;
+            }
+            return BitmapFactory.decodeFile(info.iconPath);
+        }
+    }
+
+    @Override
+    public void setGuestEnabled(boolean enable) {
+        checkManageUsersPermission("enable guest users");
+        synchronized (mPackagesLock) {
+            if (mGuestEnabled != enable) {
+                mGuestEnabled = enable;
+                // Erase any guest user that currently exists
+                for (int i = 0; i < mUsers.size(); i++) {
+                    UserInfo user = mUsers.valueAt(i);
+                    if (!user.partial && user.isGuest()) {
+                        if (!enable) {
+                            removeUser(user.id);
+                        }
+                        return;
+                    }
+                }
+                // No guest was found
+                if (enable) {
+                    createUser("Guest", UserInfo.FLAG_GUEST);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean isGuestEnabled() {
+        synchronized (mPackagesLock) {
+            return mGuestEnabled;
+        }
+    }
+
+    @Override
+    public void wipeUser(int userHandle) {
+        checkManageUsersPermission("wipe user");
+        // TODO:
+    }
+
+    public void makeInitialized(int userId) {
+        checkManageUsersPermission("makeInitialized");
+        synchronized (mPackagesLock) {
+            UserInfo info = mUsers.get(userId);
+            if (info == null || info.partial) {
+                Slog.w(LOG_TAG, "makeInitialized: unknown user #" + userId);
+            }
+            if ((info.flags&UserInfo.FLAG_INITIALIZED) == 0) {
+                info.flags |= UserInfo.FLAG_INITIALIZED;
+                writeUserLocked(info);
+            }
+        }
+    }
+
+    @Override
+    public Bundle getUserRestrictions(int userId) {
+        // checkManageUsersPermission("getUserRestrictions");
+
+        synchronized (mPackagesLock) {
+            Bundle restrictions = mUserRestrictions.get(userId);
+            return restrictions != null ? restrictions : Bundle.EMPTY;
+        }
+    }
+
+    @Override
+    public void setUserRestrictions(Bundle restrictions, int userId) {
+        checkManageUsersPermission("setUserRestrictions");
+        if (restrictions == null) return;
+
+        synchronized (mPackagesLock) {
+            mUserRestrictions.get(userId).clear();
+            mUserRestrictions.get(userId).putAll(restrictions);
+            writeUserLocked(mUsers.get(userId));
+        }
+    }
+
+    /**
+     * Check if we've hit the limit of how many users can be created.
+     */
+    private boolean isUserLimitReachedLocked() {
+        int nUsers = mUsers.size();
+        return nUsers >= UserManager.getMaxSupportedUsers();
+    }
+
+    /**
+     * Enforces that only the system UID or root's UID or apps that have the
+     * {@link android.Manifest.permission.MANAGE_USERS MANAGE_USERS}
+     * permission can make certain calls to the UserManager.
+     *
+     * @param message used as message if SecurityException is thrown
+     * @throws SecurityException if the caller is not system or root
+     */
+    private static final void checkManageUsersPermission(String message) {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID && uid != 0
+                && ActivityManager.checkComponentPermission(
+                        android.Manifest.permission.MANAGE_USERS,
+                        uid, -1, true) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("You need MANAGE_USERS permission to: " + message);
+        }
+    }
+
+    private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
+        try {
+            File dir = new File(mUsersDir, Integer.toString(info.id));
+            File file = new File(dir, USER_PHOTO_FILENAME);
+            if (!dir.exists()) {
+                dir.mkdir();
+                FileUtils.setPermissions(
+                        dir.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
+            FileOutputStream os;
+            if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, os = new FileOutputStream(file))) {
+                info.iconPath = file.getAbsolutePath();
+            }
+            try {
+                os.close();
+            } catch (IOException ioe) {
+                // What the ... !
+            }
+        } catch (FileNotFoundException e) {
+            Slog.w(LOG_TAG, "Error setting photo for user ", e);
+        }
+    }
+
+    /**
+     * Returns an array of user ids. This array is cached here for quick access, so do not modify or
+     * cache it elsewhere.
+     * @return the array of user ids.
+     */
+    public int[] getUserIds() {
+        synchronized (mPackagesLock) {
+            return mUserIds;
+        }
+    }
+
+    int[] getUserIdsLPr() {
+        return mUserIds;
+    }
+
+    private void readUserListLocked() {
+        mGuestEnabled = false;
+        if (!mUserListFile.exists()) {
+            fallbackToSingleUserLocked();
+            return;
+        }
+        FileInputStream fis = null;
+        AtomicFile userListFile = new AtomicFile(mUserListFile);
+        try {
+            fis = userListFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(LOG_TAG, "Unable to read user list");
+                fallbackToSingleUserLocked();
+                return;
+            }
+
+            mNextSerialNumber = -1;
+            if (parser.getName().equals(TAG_USERS)) {
+                String lastSerialNumber = parser.getAttributeValue(null, ATTR_NEXT_SERIAL_NO);
+                if (lastSerialNumber != null) {
+                    mNextSerialNumber = Integer.parseInt(lastSerialNumber);
+                }
+                String versionNumber = parser.getAttributeValue(null, ATTR_USER_VERSION);
+                if (versionNumber != null) {
+                    mUserVersion = Integer.parseInt(versionNumber);
+                }
+            }
+
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+                    String id = parser.getAttributeValue(null, ATTR_ID);
+                    UserInfo user = readUserLocked(Integer.parseInt(id));
+
+                    if (user != null) {
+                        mUsers.put(user.id, user);
+                        if (user.isGuest()) {
+                            mGuestEnabled = true;
+                        }
+                        if (mNextSerialNumber < 0 || mNextSerialNumber <= user.id) {
+                            mNextSerialNumber = user.id + 1;
+                        }
+                    }
+                }
+            }
+            updateUserIdsLocked();
+            upgradeIfNecessaryLocked();
+        } catch (IOException ioe) {
+            fallbackToSingleUserLocked();
+        } catch (XmlPullParserException pe) {
+            fallbackToSingleUserLocked();
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Upgrade steps between versions, either for fixing bugs or changing the data format.
+     */
+    private void upgradeIfNecessaryLocked() {
+        int userVersion = mUserVersion;
+        if (userVersion < 1) {
+            // Assign a proper name for the owner, if not initialized correctly before
+            UserInfo user = mUsers.get(UserHandle.USER_OWNER);
+            if ("Primary".equals(user.name)) {
+                user.name = mContext.getResources().getString(com.android.internal.R.string.owner_name);
+                writeUserLocked(user);
+            }
+            userVersion = 1;
+        }
+
+        if (userVersion < 2) {
+            // Owner should be marked as initialized
+            UserInfo user = mUsers.get(UserHandle.USER_OWNER);
+            if ((user.flags & UserInfo.FLAG_INITIALIZED) == 0) {
+                user.flags |= UserInfo.FLAG_INITIALIZED;
+                writeUserLocked(user);
+            }
+            userVersion = 2;
+        }
+
+
+        if (userVersion < 4) {
+            userVersion = 4;
+        }
+
+        if (userVersion < USER_VERSION) {
+            Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to "
+                    + USER_VERSION);
+        } else {
+            mUserVersion = userVersion;
+            writeUserListLocked();
+        }
+    }
+
+    private void fallbackToSingleUserLocked() {
+        // Create the primary user
+        UserInfo primary = new UserInfo(UserHandle.USER_OWNER,
+                mContext.getResources().getString(com.android.internal.R.string.owner_name), null,
+                UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED);
+        mUsers.put(0, primary);
+        mNextSerialNumber = MIN_USER_ID;
+        mUserVersion = USER_VERSION;
+
+        Bundle restrictions = new Bundle();
+        mUserRestrictions.append(UserHandle.USER_OWNER, restrictions);
+
+        updateUserIdsLocked();
+
+        writeUserListLocked();
+        writeUserLocked(primary);
+    }
+
+    /*
+     * Writes the user file in this format:
+     *
+     * <user flags="20039023" id="0">
+     *   <name>Primary</name>
+     * </user>
+     */
+    private void writeUserLocked(UserInfo userInfo) {
+        FileOutputStream fos = null;
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX));
+        try {
+            fos = userFile.startWrite();
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_USER);
+            serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
+            serializer.attribute(null, ATTR_SERIAL_NO, Integer.toString(userInfo.serialNumber));
+            serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
+            serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime));
+            serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME,
+                    Long.toString(userInfo.lastLoggedInTime));
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userInfo.id);
+            if (pinState != null) {
+                if (pinState.salt != 0) {
+                    serializer.attribute(null, ATTR_SALT, Long.toString(pinState.salt));
+                }
+                if (pinState.pinHash != null) {
+                    serializer.attribute(null, ATTR_PIN_HASH, pinState.pinHash);
+                }
+                if (pinState.failedAttempts != 0) {
+                    serializer.attribute(null, ATTR_FAILED_ATTEMPTS,
+                            Integer.toString(pinState.failedAttempts));
+                    serializer.attribute(null, ATTR_LAST_RETRY_MS,
+                            Long.toString(pinState.lastAttemptTime));
+                }
+            }
+            if (userInfo.iconPath != null) {
+                serializer.attribute(null,  ATTR_ICON_PATH, userInfo.iconPath);
+            }
+            if (userInfo.partial) {
+                serializer.attribute(null, ATTR_PARTIAL, "true");
+            }
+
+            serializer.startTag(null, TAG_NAME);
+            serializer.text(userInfo.name);
+            serializer.endTag(null, TAG_NAME);
+
+            Bundle restrictions = mUserRestrictions.get(userInfo.id);
+            if (restrictions != null) {
+                serializer.startTag(null, TAG_RESTRICTIONS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
+                writeBoolean(serializer, restrictions,
+                        UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
+                writeBoolean(serializer, restrictions, UserManager.DISALLOW_REMOVE_USER);
+                serializer.endTag(null, TAG_RESTRICTIONS);
+            }
+            serializer.endTag(null, TAG_USER);
+
+            serializer.endDocument();
+            userFile.finishWrite(fos);
+        } catch (Exception ioe) {
+            Slog.e(LOG_TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+            userFile.failWrite(fos);
+        }
+    }
+
+    /*
+     * Writes the user list file in this format:
+     *
+     * <users nextSerialNumber="3">
+     *   <user id="0"></user>
+     *   <user id="2"></user>
+     * </users>
+     */
+    private void writeUserListLocked() {
+        FileOutputStream fos = null;
+        AtomicFile userListFile = new AtomicFile(mUserListFile);
+        try {
+            fos = userListFile.startWrite();
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_USERS);
+            serializer.attribute(null, ATTR_NEXT_SERIAL_NO, Integer.toString(mNextSerialNumber));
+            serializer.attribute(null, ATTR_USER_VERSION, Integer.toString(mUserVersion));
+
+            for (int i = 0; i < mUsers.size(); i++) {
+                UserInfo user = mUsers.valueAt(i);
+                serializer.startTag(null, TAG_USER);
+                serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
+                serializer.endTag(null, TAG_USER);
+            }
+
+            serializer.endTag(null, TAG_USERS);
+
+            serializer.endDocument();
+            userListFile.finishWrite(fos);
+        } catch (Exception e) {
+            userListFile.failWrite(fos);
+            Slog.e(LOG_TAG, "Error writing user list");
+        }
+    }
+
+    private UserInfo readUserLocked(int id) {
+        int flags = 0;
+        int serialNumber = id;
+        String name = null;
+        String iconPath = null;
+        long creationTime = 0L;
+        long lastLoggedInTime = 0L;
+        long salt = 0L;
+        String pinHash = null;
+        int failedAttempts = 0;
+        long lastAttemptTime = 0L;
+        boolean partial = false;
+        Bundle restrictions = new Bundle();
+
+        FileInputStream fis = null;
+        try {
+            AtomicFile userFile =
+                    new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX));
+            fis = userFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(LOG_TAG, "Unable to read user " + id);
+                return null;
+            }
+
+            if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+                int storedId = readIntAttribute(parser, ATTR_ID, -1);
+                if (storedId != id) {
+                    Slog.e(LOG_TAG, "User id does not match the file name");
+                    return null;
+                }
+                serialNumber = readIntAttribute(parser, ATTR_SERIAL_NO, id);
+                flags = readIntAttribute(parser, ATTR_FLAGS, 0);
+                iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH);
+                creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0);
+                lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0);
+                salt = readLongAttribute(parser, ATTR_SALT, 0L);
+                pinHash = parser.getAttributeValue(null, ATTR_PIN_HASH);
+                failedAttempts = readIntAttribute(parser, ATTR_FAILED_ATTEMPTS, 0);
+                lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L);
+                String valueString = parser.getAttributeValue(null, ATTR_PARTIAL);
+                if ("true".equals(valueString)) {
+                    partial = true;
+                }
+
+                int outerDepth = parser.getDepth();
+                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                       && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                        continue;
+                    }
+                    String tag = parser.getName();
+                    if (TAG_NAME.equals(tag)) {
+                        type = parser.next();
+                        if (type == XmlPullParser.TEXT) {
+                            name = parser.getText();
+                        }
+                    } else if (TAG_RESTRICTIONS.equals(tag)) {
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_WIFI);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_INSTALL_APPS);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_UNINSTALL_APPS);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_SHARE_LOCATION);
+                        readBoolean(parser, restrictions,
+                                UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_BLUETOOTH);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_USB_FILE_TRANSFER);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_CONFIG_CREDENTIALS);
+                        readBoolean(parser, restrictions, UserManager.DISALLOW_REMOVE_USER);
+                    }
+                }
+            }
+
+            UserInfo userInfo = new UserInfo(id, name, iconPath, flags);
+            userInfo.serialNumber = serialNumber;
+            userInfo.creationTime = creationTime;
+            userInfo.lastLoggedInTime = lastLoggedInTime;
+            userInfo.partial = partial;
+            mUserRestrictions.append(id, restrictions);
+            if (salt != 0L) {
+                RestrictionsPinState pinState = mRestrictionsPinStates.get(id);
+                if (pinState == null) {
+                    pinState = new RestrictionsPinState();
+                    mRestrictionsPinStates.put(id, pinState);
+                }
+                pinState.salt = salt;
+                pinState.pinHash = pinHash;
+                pinState.failedAttempts = failedAttempts;
+                pinState.lastAttemptTime = lastAttemptTime;
+            }
+            return userInfo;
+
+        } catch (IOException ioe) {
+        } catch (XmlPullParserException pe) {
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return null;
+    }
+
+    private void readBoolean(XmlPullParser parser, Bundle restrictions,
+            String restrictionKey) {
+        String value = parser.getAttributeValue(null, restrictionKey);
+        if (value != null) {
+            restrictions.putBoolean(restrictionKey, Boolean.parseBoolean(value));
+        }
+    }
+
+    private void writeBoolean(XmlSerializer xml, Bundle restrictions, String restrictionKey)
+            throws IOException {
+        if (restrictions.containsKey(restrictionKey)) {
+            xml.attribute(null, restrictionKey,
+                    Boolean.toString(restrictions.getBoolean(restrictionKey)));
+        }
+    }
+
+    private int readIntAttribute(XmlPullParser parser, String attr, int defaultValue) {
+        String valueString = parser.getAttributeValue(null, attr);
+        if (valueString == null) return defaultValue;
+        try {
+            return Integer.parseInt(valueString);
+        } catch (NumberFormatException nfe) {
+            return defaultValue;
+        }
+    }
+
+    private long readLongAttribute(XmlPullParser parser, String attr, long defaultValue) {
+        String valueString = parser.getAttributeValue(null, attr);
+        if (valueString == null) return defaultValue;
+        try {
+            return Long.parseLong(valueString);
+        } catch (NumberFormatException nfe) {
+            return defaultValue;
+        }
+    }
+
+    private boolean isPackageInstalled(String pkg, int userId) {
+        final ApplicationInfo info = mPm.getApplicationInfo(pkg,
+                PackageManager.GET_UNINSTALLED_PACKAGES,
+                userId);
+        if (info == null || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Removes all the restrictions files (res_<packagename>) for a given user, if all is true,
+     * else removes only those packages that have been uninstalled.
+     * Does not do any permissions checking.
+     */
+    private void cleanAppRestrictions(int userId, boolean all) {
+        synchronized (mPackagesLock) {
+            File dir = Environment.getUserSystemDirectory(userId);
+            String[] files = dir.list();
+            if (files == null) return;
+            for (String fileName : files) {
+                if (fileName.startsWith(RESTRICTIONS_FILE_PREFIX)) {
+                    File resFile = new File(dir, fileName);
+                    if (resFile.exists()) {
+                        if (all) {
+                            resFile.delete();
+                        } else {
+                            String pkg = restrictionsFileNameToPackage(fileName);
+                            if (!isPackageInstalled(pkg, userId)) {
+                                resFile.delete();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Removes the app restrictions file for a specific package and user id, if it exists.
+     */
+    private void cleanAppRestrictionsForPackage(String pkg, int userId) {
+        synchronized (mPackagesLock) {
+            File dir = Environment.getUserSystemDirectory(userId);
+            File resFile = new File(dir, packageToRestrictionsFileName(pkg));
+            if (resFile.exists()) {
+                resFile.delete();
+            }
+        }
+    }
+
+    @Override
+    public UserInfo createUser(String name, int flags) {
+        checkManageUsersPermission("Only the system can create users");
+
+        final long ident = Binder.clearCallingIdentity();
+        final UserInfo userInfo;
+        try {
+            synchronized (mInstallLock) {
+                synchronized (mPackagesLock) {
+                    if (isUserLimitReachedLocked()) return null;
+                    int userId = getNextAvailableIdLocked();
+                    userInfo = new UserInfo(userId, name, null, flags);
+                    File userPath = new File(mBaseUserPath, Integer.toString(userId));
+                    userInfo.serialNumber = mNextSerialNumber++;
+                    long now = System.currentTimeMillis();
+                    userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
+                    userInfo.partial = true;
+                    Environment.getUserSystemDirectory(userInfo.id).mkdirs();
+                    mUsers.put(userId, userInfo);
+                    writeUserListLocked();
+                    writeUserLocked(userInfo);
+                    mPm.createNewUserLILPw(userId, userPath);
+                    userInfo.partial = false;
+                    writeUserLocked(userInfo);
+                    updateUserIdsLocked();
+                    Bundle restrictions = new Bundle();
+                    mUserRestrictions.append(userId, restrictions);
+                }
+            }
+            if (userInfo != null) {
+                Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
+                addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+                mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
+                        android.Manifest.permission.MANAGE_USERS);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return userInfo;
+    }
+
+    /**
+     * Removes a user and all data directories created for that user. This method should be called
+     * after the user's processes have been terminated.
+     * @param id the user's id
+     */
+    public boolean removeUser(int userHandle) {
+        checkManageUsersPermission("Only the system can remove users");
+        final UserInfo user;
+        synchronized (mPackagesLock) {
+            user = mUsers.get(userHandle);
+            if (userHandle == 0 || user == null) {
+                return false;
+            }
+            mRemovingUserIds.put(userHandle, true);
+            // Set this to a partially created user, so that the user will be purged
+            // on next startup, in case the runtime stops now before stopping and
+            // removing the user completely.
+            user.partial = true;
+            writeUserLocked(user);
+        }
+        if (DBG) Slog.i(LOG_TAG, "Stopping user " + userHandle);
+        int res;
+        try {
+            res = ActivityManagerNative.getDefault().stopUser(userHandle,
+                    new IStopUserCallback.Stub() {
+                        @Override
+                        public void userStopped(int userId) {
+                            finishRemoveUser(userId);
+                        }
+                        @Override
+                        public void userStopAborted(int userId) {
+                        }
+            });
+        } catch (RemoteException e) {
+            return false;
+        }
+
+        return res == ActivityManager.USER_OP_SUCCESS;
+    }
+
+    void finishRemoveUser(final int userHandle) {
+        if (DBG) Slog.i(LOG_TAG, "finishRemoveUser " + userHandle);
+        // Let other services shutdown any activity and clean up their state before completely
+        // wiping the user's system directory and removing from the user list
+        long ident = Binder.clearCallingIdentity();
+        try {
+            Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+            addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userHandle);
+            mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
+                    android.Manifest.permission.MANAGE_USERS,
+
+                    new BroadcastReceiver() {
+                        @Override
+                        public void onReceive(Context context, Intent intent) {
+                            if (DBG) {
+                                Slog.i(LOG_TAG,
+                                        "USER_REMOVED broadcast sent, cleaning up user data "
+                                        + userHandle);
+                            }
+                            new Thread() {
+                                public void run() {
+                                    synchronized (mInstallLock) {
+                                        synchronized (mPackagesLock) {
+                                            removeUserStateLocked(userHandle);
+                                        }
+                                    }
+                                }
+                            }.start();
+                        }
+                    },
+
+                    null, Activity.RESULT_OK, null, null);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void removeUserStateLocked(final int userHandle) {
+        // Cleanup package manager settings
+        mPm.cleanUpUserLILPw(userHandle);
+
+        // Remove this user from the list
+        mUsers.remove(userHandle);
+
+        // Have user ID linger for several seconds to let external storage VFS
+        // cache entries expire. This must be greater than the 'entry_valid'
+        // timeout used by the FUSE daemon.
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mPackagesLock) {
+                    mRemovingUserIds.delete(userHandle);
+                }
+            }
+        }, MINUTE_IN_MILLIS);
+
+        mRestrictionsPinStates.remove(userHandle);
+        // Remove user file
+        AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX));
+        userFile.delete();
+        // Update the user list
+        writeUserListLocked();
+        updateUserIdsLocked();
+        removeDirectoryRecursive(Environment.getUserSystemDirectory(userHandle));
+    }
+
+    private void removeDirectoryRecursive(File parent) {
+        if (parent.isDirectory()) {
+            String[] files = parent.list();
+            for (String filename : files) {
+                File child = new File(parent, filename);
+                removeDirectoryRecursive(child);
+            }
+        }
+        parent.delete();
+    }
+
+    @Override
+    public Bundle getApplicationRestrictions(String packageName) {
+        return getApplicationRestrictionsForUser(packageName, UserHandle.getCallingUserId());
+    }
+
+    @Override
+    public Bundle getApplicationRestrictionsForUser(String packageName, int userId) {
+        if (UserHandle.getCallingUserId() != userId
+                || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
+            checkManageUsersPermission("Only system can get restrictions for other users/apps");
+        }
+        synchronized (mPackagesLock) {
+            // Read the restrictions from XML
+            return readApplicationRestrictionsLocked(packageName, userId);
+        }
+    }
+
+    @Override
+    public void setApplicationRestrictions(String packageName, Bundle restrictions,
+            int userId) {
+        if (UserHandle.getCallingUserId() != userId
+                || !UserHandle.isSameApp(Binder.getCallingUid(), getUidForPackage(packageName))) {
+            checkManageUsersPermission("Only system can set restrictions for other users/apps");
+        }
+        synchronized (mPackagesLock) {
+            // Write the restrictions to XML
+            writeApplicationRestrictionsLocked(packageName, restrictions, userId);
+        }
+    }
+
+    @Override
+    public boolean setRestrictionsChallenge(String newPin) {
+        checkManageUsersPermission("Only system can modify the restrictions pin");
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            if (pinState == null) {
+                pinState = new RestrictionsPinState();
+            }
+            if (newPin == null) {
+                pinState.salt = 0;
+                pinState.pinHash = null;
+            } else {
+                try {
+                    pinState.salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
+                } catch (NoSuchAlgorithmException e) {
+                    pinState.salt = (long) (Math.random() * Long.MAX_VALUE);
+                }
+                pinState.pinHash = passwordToHash(newPin, pinState.salt);
+                pinState.failedAttempts = 0;
+            }
+            mRestrictionsPinStates.put(userId, pinState);
+            writeUserLocked(mUsers.get(userId));
+        }
+        return true;
+    }
+
+    @Override
+    public int checkRestrictionsChallenge(String pin) {
+        checkManageUsersPermission("Only system can verify the restrictions pin");
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            // If there's no pin set, return error code
+            if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) {
+                return UserManager.PIN_VERIFICATION_FAILED_NOT_SET;
+            } else if (pin == null) {
+                // If just checking if user can be prompted, return remaining time
+                int waitTime = getRemainingTimeForPinAttempt(pinState);
+                Slog.d(LOG_TAG, "Remaining waittime peek=" + waitTime);
+                return waitTime;
+            } else {
+                int waitTime = getRemainingTimeForPinAttempt(pinState);
+                Slog.d(LOG_TAG, "Remaining waittime=" + waitTime);
+                if (waitTime > 0) {
+                    return waitTime;
+                }
+                if (passwordToHash(pin, pinState.salt).equals(pinState.pinHash)) {
+                    pinState.failedAttempts = 0;
+                    writeUserLocked(mUsers.get(userId));
+                    return UserManager.PIN_VERIFICATION_SUCCESS;
+                } else {
+                    pinState.failedAttempts++;
+                    pinState.lastAttemptTime = System.currentTimeMillis();
+                    writeUserLocked(mUsers.get(userId));
+                    return waitTime;
+                }
+            }
+        }
+    }
+
+    private int getRemainingTimeForPinAttempt(RestrictionsPinState pinState) {
+        int backoffIndex = Math.min(pinState.failedAttempts / BACKOFF_INC_INTERVAL,
+                BACKOFF_TIMES.length - 1);
+        int backoffTime = (pinState.failedAttempts % BACKOFF_INC_INTERVAL) == 0 ?
+                BACKOFF_TIMES[backoffIndex] : 0;
+        return (int) Math.max(backoffTime + pinState.lastAttemptTime - System.currentTimeMillis(),
+                0);
+    }
+
+    @Override
+    public boolean hasRestrictionsChallenge() {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mPackagesLock) {
+            return hasRestrictionsPinLocked(userId);
+        }
+    }
+
+    private boolean hasRestrictionsPinLocked(int userId) {
+        RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+        if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void removeRestrictions() {
+        checkManageUsersPermission("Only system can remove restrictions");
+        final int userHandle = UserHandle.getCallingUserId();
+        removeRestrictionsForUser(userHandle, true);
+    }
+
+    private void removeRestrictionsForUser(final int userHandle, boolean unblockApps) {
+        synchronized (mPackagesLock) {
+            // Remove all user restrictions
+            setUserRestrictions(new Bundle(), userHandle);
+            // Remove restrictions pin
+            setRestrictionsChallenge(null);
+            // Remove any app restrictions
+            cleanAppRestrictions(userHandle, true);
+        }
+        if (unblockApps) {
+            unblockAllAppsForUser(userHandle);
+        }
+    }
+
+    private void unblockAllAppsForUser(final int userHandle) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                List<ApplicationInfo> apps =
+                        mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES,
+                                userHandle).getList();
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    for (ApplicationInfo appInfo : apps) {
+                        if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0
+                                && (appInfo.flags & ApplicationInfo.FLAG_BLOCKED) != 0) {
+                            mPm.setApplicationBlockedSettingAsUser(appInfo.packageName, false,
+                                    userHandle);
+                        }
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            }
+        });
+    }
+
+    /*
+     * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash.
+     * Not the most secure, but it is at least a second level of protection. First level is that
+     * the file is in a location only readable by the system process.
+     * @param password the password.
+     * @param salt the randomly generated salt
+     * @return the hash of the pattern in a String.
+     */
+    private String passwordToHash(String password, long salt) {
+        if (password == null) {
+            return null;
+        }
+        String algo = null;
+        String hashed = salt + password;
+        try {
+            byte[] saltedPassword = (password + salt).getBytes();
+            byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword);
+            byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword);
+            hashed = toHex(sha1) + toHex(md5);
+        } catch (NoSuchAlgorithmException e) {
+            Log.w(LOG_TAG, "Failed to encode string because of missing algorithm: " + algo);
+        }
+        return hashed;
+    }
+
+    private static String toHex(byte[] ary) {
+        final String hex = "0123456789ABCDEF";
+        String ret = "";
+        for (int i = 0; i < ary.length; i++) {
+            ret += hex.charAt((ary[i] >> 4) & 0xf);
+            ret += hex.charAt(ary[i] & 0xf);
+        }
+        return ret;
+    }
+
+    private int getUidForPackage(String packageName) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            return mContext.getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+        } catch (NameNotFoundException nnfe) {
+            return -1;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private Bundle readApplicationRestrictionsLocked(String packageName,
+            int userId) {
+        final Bundle restrictions = new Bundle();
+        final ArrayList<String> values = new ArrayList<String>();
+
+        FileInputStream fis = null;
+        try {
+            AtomicFile restrictionsFile =
+                    new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
+                            packageToRestrictionsFileName(packageName)));
+            fis = restrictionsFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                Slog.e(LOG_TAG, "Unable to read restrictions file "
+                        + restrictionsFile.getBaseFile());
+                return restrictions;
+            }
+
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
+                    String key = parser.getAttributeValue(null, ATTR_KEY);
+                    String valType = parser.getAttributeValue(null, ATTR_VALUE_TYPE);
+                    String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
+                    if (multiple != null) {
+                        int count = Integer.parseInt(multiple);
+                        while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                            if (type == XmlPullParser.START_TAG
+                                    && parser.getName().equals(TAG_VALUE)) {
+                                values.add(parser.nextText().trim());
+                                count--;
+                            }
+                        }
+                        String [] valueStrings = new String[values.size()];
+                        values.toArray(valueStrings);
+                        restrictions.putStringArray(key, valueStrings);
+                    } else if (ATTR_TYPE_BOOLEAN.equals(valType)) {
+                        restrictions.putBoolean(key, Boolean.parseBoolean(
+                                parser.nextText().trim()));
+                    } else {
+                        String value = parser.nextText().trim();
+                        restrictions.putString(key, value);
+                    }
+                }
+            }
+
+        } catch (IOException ioe) {
+        } catch (XmlPullParserException pe) {
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+        return restrictions;
+    }
+
+    private void writeApplicationRestrictionsLocked(String packageName,
+            Bundle restrictions, int userId) {
+        FileOutputStream fos = null;
+        AtomicFile restrictionsFile = new AtomicFile(
+                new File(Environment.getUserSystemDirectory(userId),
+                        packageToRestrictionsFileName(packageName)));
+        try {
+            fos = restrictionsFile.startWrite();
+            final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+            // XmlSerializer serializer = XmlUtils.serializerInstance();
+            final XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(bos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+            serializer.startTag(null, TAG_RESTRICTIONS);
+
+            for (String key : restrictions.keySet()) {
+                Object value = restrictions.get(key);
+                serializer.startTag(null, TAG_ENTRY);
+                serializer.attribute(null, ATTR_KEY, key);
+
+                if (value instanceof Boolean) {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_BOOLEAN);
+                    serializer.text(value.toString());
+                } else if (value == null || value instanceof String) {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING);
+                    serializer.text(value != null ? (String) value : "");
+                } else {
+                    serializer.attribute(null, ATTR_VALUE_TYPE, ATTR_TYPE_STRING_ARRAY);
+                    String[] values = (String[]) value;
+                    serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
+                    for (String choice : values) {
+                        serializer.startTag(null, TAG_VALUE);
+                        serializer.text(choice != null ? choice : "");
+                        serializer.endTag(null, TAG_VALUE);
+                    }
+                }
+                serializer.endTag(null, TAG_ENTRY);
+            }
+
+            serializer.endTag(null, TAG_RESTRICTIONS);
+
+            serializer.endDocument();
+            restrictionsFile.finishWrite(fos);
+        } catch (Exception e) {
+            restrictionsFile.failWrite(fos);
+            Slog.e(LOG_TAG, "Error writing application restrictions list");
+        }
+    }
+
+    @Override
+    public int getUserSerialNumber(int userHandle) {
+        synchronized (mPackagesLock) {
+            if (!exists(userHandle)) return -1;
+            return getUserInfoLocked(userHandle).serialNumber;
+        }
+    }
+
+    @Override
+    public int getUserHandle(int userSerialNumber) {
+        synchronized (mPackagesLock) {
+            for (int userId : mUserIds) {
+                if (getUserInfoLocked(userId).serialNumber == userSerialNumber) return userId;
+            }
+            // Not found
+            return -1;
+        }
+    }
+
+    /**
+     * Caches the list of user ids in an array, adjusting the array size when necessary.
+     */
+    private void updateUserIdsLocked() {
+        int num = 0;
+        for (int i = 0; i < mUsers.size(); i++) {
+            if (!mUsers.valueAt(i).partial) {
+                num++;
+            }
+        }
+        final int[] newUsers = new int[num];
+        int n = 0;
+        for (int i = 0; i < mUsers.size(); i++) {
+            if (!mUsers.valueAt(i).partial) {
+                newUsers[n++] = mUsers.keyAt(i);
+            }
+        }
+        mUserIds = newUsers;
+    }
+
+    /**
+     * Make a note of the last started time of a user and do some cleanup.
+     * @param userId the user that was just foregrounded
+     */
+    public void userForeground(int userId) {
+        synchronized (mPackagesLock) {
+            UserInfo user = mUsers.get(userId);
+            long now = System.currentTimeMillis();
+            if (user == null || user.partial) {
+                Slog.w(LOG_TAG, "userForeground: unknown user #" + userId);
+                return;
+            }
+            if (now > EPOCH_PLUS_30_YEARS) {
+                user.lastLoggedInTime = now;
+                writeUserLocked(user);
+            }
+            // If this is not a restricted profile and there is no restrictions pin, clean up
+            // all restrictions files that might have been left behind, else clean up just the
+            // ones with uninstalled packages
+            RestrictionsPinState pinState = mRestrictionsPinStates.get(userId);
+            final long salt = pinState == null ? 0 : pinState.salt;
+            cleanAppRestrictions(userId, (!user.isRestricted() && salt == 0));
+        }
+    }
+
+    /**
+     * Returns the next available user id, filling in any holes in the ids.
+     * TODO: May not be a good idea to recycle ids, in case it results in confusion
+     * for data and battery stats collection, or unexpected cross-talk.
+     * @return
+     */
+    private int getNextAvailableIdLocked() {
+        synchronized (mPackagesLock) {
+            int i = MIN_USER_ID;
+            while (i < Integer.MAX_VALUE) {
+                if (mUsers.indexOfKey(i) < 0 && !mRemovingUserIds.get(i)) {
+                    break;
+                }
+                i++;
+            }
+            return i;
+        }
+    }
+
+    private String packageToRestrictionsFileName(String packageName) {
+        return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX;
+    }
+
+    private String restrictionsFileNameToPackage(String fileName) {
+        return fileName.substring(RESTRICTIONS_FILE_PREFIX.length(),
+                (int) (fileName.length() - XML_SUFFIX.length()));
+    }
+
+    @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 UserManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+
+        long now = System.currentTimeMillis();
+        StringBuilder sb = new StringBuilder();
+        synchronized (mPackagesLock) {
+            pw.println("Users:");
+            for (int i = 0; i < mUsers.size(); i++) {
+                UserInfo user = mUsers.valueAt(i);
+                if (user == null) continue;
+                pw.print("  "); pw.print(user); pw.print(" serialNo="); pw.print(user.serialNumber);
+                if (mRemovingUserIds.get(mUsers.keyAt(i))) pw.print(" <removing> ");
+                if (user.partial) pw.print(" <partial>");
+                pw.println();
+                pw.print("    Created: ");
+                if (user.creationTime == 0) {
+                    pw.println("<unknown>");
+                } else {
+                    sb.setLength(0);
+                    TimeUtils.formatDuration(now - user.creationTime, sb);
+                    sb.append(" ago");
+                    pw.println(sb);
+                }
+                pw.print("    Last logged in: ");
+                if (user.lastLoggedInTime == 0) {
+                    pw.println("<unknown>");
+                } else {
+                    sb.setLength(0);
+                    TimeUtils.formatDuration(now - user.lastLoggedInTime, sb);
+                    sb.append(" ago");
+                    pw.println(sb);
+                }
+            }
+        }
+    }
+
+    private PackageMonitor mUserPackageMonitor = new PackageMonitor() {
+        @Override
+        public void onPackageRemoved(String pkg, int uid) {
+            final int userId = this.getChangingUserId();
+            // Package could be disappearing because it is being blocked, so also check if
+            // it has been uninstalled.
+            final boolean uninstalled = isPackageDisappearing(pkg) == PACKAGE_PERMANENT_CHANGE;
+            if (uninstalled && userId >= 0 && !isPackageInstalled(pkg, userId)) {
+                cleanAppRestrictionsForPackage(pkg, userId);
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/power/DisplayBlanker.java b/services/core/java/com/android/server/power/DisplayBlanker.java
new file mode 100644
index 0000000..6072053
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayBlanker.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+/**
+ * Blanks or unblanks all displays.
+ */
+interface DisplayBlanker {
+    public void blankAllDisplays();
+    public void unblankAllDisplays();
+}
diff --git a/services/core/java/com/android/server/power/DisplayPowerController.java b/services/core/java/com/android/server/power/DisplayPowerController.java
new file mode 100644
index 0000000..b8a78e3
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayPowerController.java
@@ -0,0 +1,1379 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+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;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.util.FloatMath;
+import android.util.Slog;
+import android.util.Spline;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the power state of the display.
+ *
+ * Handles the proximity sensor, light sensor, and animations between states
+ * including the screen off animation.
+ *
+ * This component acts independently of the rest of the power manager service.
+ * In particular, it does not share any state and it only communicates
+ * via asynchronous callbacks to inform the power manager that something has
+ * changed.
+ *
+ * Everything this class does internally is serialized on its handler although
+ * it may be accessed by other threads from the outside.
+ *
+ * Note that the power manager service guarantees that it will hold a suspend
+ * blocker as long as the display is not ready.  So most of the work done here
+ * does not need to worry about holding a suspend blocker unless it happens
+ * independently of the display ready signal.
+ *
+ * For debugging, you can make the electron beam and brightness animations run
+ * slower by changing the "animator duration scale" option in Development Settings.
+ */
+final class DisplayPowerController {
+    private static final String TAG = "DisplayPowerController";
+
+    private static boolean DEBUG = false;
+    private static final boolean DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT = false;
+    private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
+
+    // If true, uses the electron beam on animation.
+    // We might want to turn this off if we cannot get a guarantee that the screen
+    // actually turns on and starts showing new content after the call to set the
+    // screen state returns.  Playing the animation can also be somewhat slow.
+    private static final boolean USE_ELECTRON_BEAM_ON_ANIMATION = false;
+
+    // If true, enables the use of the screen auto-brightness adjustment setting.
+    private static final boolean USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT =
+            PowerManager.useScreenAutoBrightnessAdjustmentFeature();
+
+    // The maximum range of gamma adjustment possible using the screen
+    // auto-brightness adjustment setting.
+    private static final float SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA = 3.0f;
+
+    // The minimum reduction in brightness when dimmed.
+    private static final int SCREEN_DIM_MINIMUM_REDUCTION = 10;
+
+    // If true, enables the use of the current time as an auto-brightness adjustment.
+    // The basic idea here is to expand the dynamic range of auto-brightness
+    // when it is especially dark outside.  The light sensor tends to perform
+    // poorly at low light levels so we compensate for it by making an
+    // assumption about the environment.
+    private static final boolean USE_TWILIGHT_ADJUSTMENT =
+            PowerManager.useTwilightAdjustmentFeature();
+
+    // Specifies the maximum magnitude of the time of day adjustment.
+    private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1.5f;
+
+    // The amount of time after or before sunrise over which to start adjusting
+    // the gamma.  We want the change to happen gradually so that it is below the
+    // threshold of perceptibility and so that the adjustment has maximum effect
+    // well after dusk.
+    private static final long TWILIGHT_ADJUSTMENT_TIME = DateUtils.HOUR_IN_MILLIS * 2;
+
+    private static final int ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS = 250;
+    private static final int ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS = 400;
+
+    private static final int MSG_UPDATE_POWER_STATE = 1;
+    private static final int MSG_PROXIMITY_SENSOR_DEBOUNCED = 2;
+    private static final int MSG_LIGHT_SENSOR_DEBOUNCED = 3;
+
+    private static final int PROXIMITY_UNKNOWN = -1;
+    private static final int PROXIMITY_NEGATIVE = 0;
+    private static final int PROXIMITY_POSITIVE = 1;
+
+    // Proximity sensor debounce delay in milliseconds for positive or negative transitions.
+    private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0;
+    private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250;
+
+    // Trigger proximity if distance is less than 5 cm.
+    private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f;
+
+    // Light sensor event rate in milliseconds.
+    private static final int LIGHT_SENSOR_RATE_MILLIS = 1000;
+
+    // A rate for generating synthetic light sensor events in the case where the light
+    // sensor hasn't reported any new data in a while and we need it to update the
+    // debounce filter.  We only synthesize light sensor measurements when needed.
+    private static final int SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS =
+            LIGHT_SENSOR_RATE_MILLIS * 2;
+
+    // Brightness animation ramp rate in brightness units per second.
+    private static final int BRIGHTNESS_RAMP_RATE_FAST = 200;
+    private static final int BRIGHTNESS_RAMP_RATE_SLOW = 40;
+
+    // IIR filter time constants in milliseconds for computing two moving averages of
+    // the light samples.  One is a long-term average and the other is a short-term average.
+    // We can use these filters to assess trends in ambient brightness.
+    // The short term average gives us a filtered but relatively low latency measurement.
+    // The long term average informs us about the overall trend.
+    private static final long SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 1000;
+    private static final long LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT = 5000;
+
+    // Stability requirements in milliseconds for accepting a new brightness
+    // level.  This is used for debouncing the light sensor.  Different constants
+    // are used to debounce the light sensor when adapting to brighter or darker environments.
+    // This parameter controls how quickly brightness changes occur in response to
+    // an observed change in light level that exceeds the hysteresis threshold.
+    private static final long BRIGHTENING_LIGHT_DEBOUNCE = 4000;
+    private static final long DARKENING_LIGHT_DEBOUNCE = 8000;
+
+    // Hysteresis constraints for brightening or darkening.
+    // The recent lux must have changed by at least this fraction relative to the
+    // current ambient lux before a change will be considered.
+    private static final float BRIGHTENING_LIGHT_HYSTERESIS = 0.10f;
+    private static final float DARKENING_LIGHT_HYSTERESIS = 0.20f;
+
+    private final Object mLock = new Object();
+
+    // Notifier for sending asynchronous notifications.
+    private final Notifier mNotifier;
+
+    // The display suspend blocker.
+    // Held while there are pending state change notifications.
+    private final SuspendBlocker mDisplaySuspendBlocker;
+
+    // The display blanker.
+    private final DisplayBlanker mDisplayBlanker;
+
+    // Our handler.
+    private final DisplayControllerHandler mHandler;
+
+    // Asynchronous callbacks into the power manager service.
+    // Only invoked from the handler thread while no locks are held.
+    private final Callbacks mCallbacks;
+    private Handler mCallbackHandler;
+
+    // The lights service.
+    private final LightsManager mLights;
+
+    // The twilight service.
+    private final TwilightManager mTwilight;
+
+    // The display manager.
+    private final DisplayManagerService mDisplayManager;
+
+    // The sensor manager.
+    private final SensorManager mSensorManager;
+
+    // The proximity sensor, or null if not available or needed.
+    private Sensor mProximitySensor;
+
+    // The light sensor, or null if not available or needed.
+    private Sensor mLightSensor;
+
+    // The dim screen brightness.
+    private final int mScreenBrightnessDimConfig;
+
+    // The minimum allowed brightness.
+    private final int mScreenBrightnessRangeMinimum;
+
+    // The maximum allowed brightness.
+    private final int mScreenBrightnessRangeMaximum;
+
+    // True if auto-brightness should be used.
+    private boolean mUseSoftwareAutoBrightnessConfig;
+
+    // The auto-brightness spline adjustment.
+    // The brightness values have been scaled to a range of 0..1.
+    private Spline mScreenAutoBrightnessSpline;
+
+    // Amount of time to delay auto-brightness after screen on while waiting for
+    // the light sensor to warm-up in milliseconds.
+    // May be 0 if no warm-up is required.
+    private int mLightSensorWarmUpTimeConfig;
+
+    // True if we should fade the screen while turning it off, false if we should play
+    // a stylish electron beam animation instead.
+    private boolean mElectronBeamFadesConfig;
+
+    // The pending power request.
+    // Initially null until the first call to requestPowerState.
+    // Guarded by mLock.
+    private DisplayPowerRequest mPendingRequestLocked;
+
+    // True if a request has been made to wait for the proximity sensor to go negative.
+    // Guarded by mLock.
+    private boolean mPendingWaitForNegativeProximityLocked;
+
+    // True if the pending power request or wait for negative proximity flag
+    // has been changed since the last update occurred.
+    // Guarded by mLock.
+    private boolean mPendingRequestChangedLocked;
+
+    // Set to true when the important parts of the pending power request have been applied.
+    // The important parts are mainly the screen state.  Brightness changes may occur
+    // concurrently.
+    // Guarded by mLock.
+    private boolean mDisplayReadyLocked;
+
+    // Set to true if a power state update is required.
+    // Guarded by mLock.
+    private boolean mPendingUpdatePowerStateLocked;
+
+    /* The following state must only be accessed by the handler thread. */
+
+    // The currently requested power state.
+    // The power controller will progressively update its internal state to match
+    // the requested power state.  Initially null until the first update.
+    private DisplayPowerRequest mPowerRequest;
+
+    // The current power state.
+    // Must only be accessed on the handler thread.
+    private DisplayPowerState mPowerState;
+
+    // True if the device should wait for negative proximity sensor before
+    // waking up the screen.  This is set to false as soon as a negative
+    // proximity sensor measurement is observed or when the device is forced to
+    // go to sleep by the user.  While true, the screen remains off.
+    private boolean mWaitingForNegativeProximity;
+
+    // The actual proximity sensor threshold value.
+    private float mProximityThreshold;
+
+    // Set to true if the proximity sensor listener has been registered
+    // with the sensor manager.
+    private boolean mProximitySensorEnabled;
+
+    // The debounced proximity sensor state.
+    private int mProximity = PROXIMITY_UNKNOWN;
+
+    // The raw non-debounced proximity sensor state.
+    private int mPendingProximity = PROXIMITY_UNKNOWN;
+    private long mPendingProximityDebounceTime = -1; // -1 if fully debounced
+
+    // True if the screen was turned off because of the proximity sensor.
+    // When the screen turns on again, we report user activity to the power manager.
+    private boolean mScreenOffBecauseOfProximity;
+
+    // True if the screen on is being blocked.
+    private boolean mScreenOnWasBlocked;
+
+    // The elapsed real time when the screen on was blocked.
+    private long mScreenOnBlockStartRealTime;
+
+    // Set to true if the light sensor is enabled.
+    private boolean mLightSensorEnabled;
+
+    // The time when the light sensor was enabled.
+    private long mLightSensorEnableTime;
+
+    // The currently accepted nominal ambient light level.
+    private float mAmbientLux;
+
+    // True if mAmbientLux holds a valid value.
+    private boolean mAmbientLuxValid;
+
+    // The ambient light level threshold at which to brighten or darken the screen.
+    private float mBrighteningLuxThreshold;
+    private float mDarkeningLuxThreshold;
+
+    // The most recent light sample.
+    private float mLastObservedLux;
+
+    // The time of the most light recent sample.
+    private long mLastObservedLuxTime;
+
+    // The number of light samples collected since the light sensor was enabled.
+    private int mRecentLightSamples;
+
+    // The long-term and short-term filtered light measurements.
+    private float mRecentShortTermAverageLux;
+    private float mRecentLongTermAverageLux;
+
+    // The direction in which the average lux is moving relative to the current ambient lux.
+    //    0 if not changing or within hysteresis threshold.
+    //    1 if brightening beyond hysteresis threshold.
+    //   -1 if darkening beyond hysteresis threshold.
+    private int mDebounceLuxDirection;
+
+    // The time when the average lux last changed direction.
+    private long mDebounceLuxTime;
+
+    // The screen brightness level that has been chosen by the auto-brightness
+    // algorithm.  The actual brightness should ramp towards this value.
+    // We preserve this value even when we stop using the light sensor so
+    // that we can quickly revert to the previous auto-brightness level
+    // while the light sensor warms up.
+    // Use -1 if there is no current auto-brightness value available.
+    private int mScreenAutoBrightness = -1;
+
+    // The last screen auto-brightness gamma.  (For printing in dump() only.)
+    private float mLastScreenAutoBrightnessGamma = 1.0f;
+
+    // True if the screen auto-brightness value is actually being used to
+    // set the display brightness.
+    private boolean mUsingScreenAutoBrightness;
+
+    // Animators.
+    private ObjectAnimator mElectronBeamOnAnimator;
+    private ObjectAnimator mElectronBeamOffAnimator;
+    private RampAnimator<DisplayPowerState> mScreenBrightnessRampAnimator;
+
+    // Twilight changed.  We might recalculate auto-brightness values.
+    private boolean mTwilightChanged;
+
+    /**
+     * Creates the display power controller.
+     */
+    public DisplayPowerController(Looper looper, Context context, Notifier notifier,
+            LightsManager lights, TwilightManager twilight, SensorManager sensorManager,
+            DisplayManagerService displayManager,
+            SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker,
+            Callbacks callbacks, Handler callbackHandler) {
+        mHandler = new DisplayControllerHandler(looper);
+        mNotifier = notifier;
+        mDisplaySuspendBlocker = displaySuspendBlocker;
+        mDisplayBlanker = displayBlanker;
+        mCallbacks = callbacks;
+        mCallbackHandler = callbackHandler;
+
+        mLights = lights;
+        mTwilight = twilight;
+        mSensorManager = sensorManager;
+        mDisplayManager = displayManager;
+
+        final Resources resources = context.getResources();
+
+        mScreenBrightnessDimConfig = clampAbsoluteBrightness(resources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessDim));
+
+        int screenBrightnessMinimum = Math.min(resources.getInteger(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum),
+                mScreenBrightnessDimConfig);
+
+        mUseSoftwareAutoBrightnessConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_automatic_brightness_available);
+        if (mUseSoftwareAutoBrightnessConfig) {
+            int[] lux = resources.getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLevels);
+            int[] screenBrightness = resources.getIntArray(
+                    com.android.internal.R.array.config_autoBrightnessLcdBacklightValues);
+
+            mScreenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness);
+            if (mScreenAutoBrightnessSpline == null) {
+                Slog.e(TAG, "Error in config.xml.  config_autoBrightnessLcdBacklightValues "
+                        + "(size " + screenBrightness.length + ") "
+                        + "must be monotic and have exactly one more entry than "
+                        + "config_autoBrightnessLevels (size " + lux.length + ") "
+                        + "which must be strictly increasing.  "
+                        + "Auto-brightness will be disabled.");
+                mUseSoftwareAutoBrightnessConfig = false;
+            } else {
+                if (screenBrightness[0] < screenBrightnessMinimum) {
+                    screenBrightnessMinimum = screenBrightness[0];
+                }
+            }
+
+            mLightSensorWarmUpTimeConfig = resources.getInteger(
+                    com.android.internal.R.integer.config_lightSensorWarmupTime);
+        }
+
+        mScreenBrightnessRangeMinimum = clampAbsoluteBrightness(screenBrightnessMinimum);
+        mScreenBrightnessRangeMaximum = PowerManager.BRIGHTNESS_ON;
+
+        mElectronBeamFadesConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_animateScreenLights);
+
+        if (!DEBUG_PRETEND_PROXIMITY_SENSOR_ABSENT) {
+            mProximitySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+            if (mProximitySensor != null) {
+                mProximityThreshold = Math.min(mProximitySensor.getMaximumRange(),
+                        TYPICAL_PROXIMITY_THRESHOLD);
+            }
+        }
+
+        if (mUseSoftwareAutoBrightnessConfig
+                && !DEBUG_PRETEND_LIGHT_SENSOR_ABSENT) {
+            mLightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+        }
+
+        if (mUseSoftwareAutoBrightnessConfig && USE_TWILIGHT_ADJUSTMENT) {
+            mTwilight.registerListener(mTwilightListener, mHandler);
+        }
+    }
+
+    private static Spline createAutoBrightnessSpline(int[] lux, int[] brightness) {
+        try {
+            final int n = brightness.length;
+            float[] x = new float[n];
+            float[] y = new float[n];
+            y[0] = normalizeAbsoluteBrightness(brightness[0]);
+            for (int i = 1; i < n; i++) {
+                x[i] = lux[i - 1];
+                y[i] = normalizeAbsoluteBrightness(brightness[i]);
+            }
+
+            Spline spline = Spline.createMonotoneCubicSpline(x, y);
+            if (DEBUG) {
+                Slog.d(TAG, "Auto-brightness spline: " + spline);
+                for (float v = 1f; v < lux[lux.length - 1] * 1.25f; v *= 1.25f) {
+                    Slog.d(TAG, String.format("  %7.1f: %7.1f", v, spline.interpolate(v)));
+                }
+            }
+            return spline;
+        } catch (IllegalArgumentException ex) {
+            Slog.e(TAG, "Could not create auto-brightness spline.", ex);
+            return null;
+        }
+    }
+
+    /**
+     * Returns true if the proximity sensor screen-off function is available.
+     */
+    public boolean isProximitySensorAvailable() {
+        return mProximitySensor != null;
+    }
+
+    /**
+     * Requests a new power state.
+     * The controller makes a copy of the provided object and then
+     * begins adjusting the power state to match what was requested.
+     *
+     * @param request The requested power state.
+     * @param waitForNegativeProximity If true, issues a request to wait for
+     * negative proximity before turning the screen back on, assuming the screen
+     * was turned off by the proximity sensor.
+     * @return True if display is ready, false if there are important changes that must
+     * be made asynchronously (such as turning the screen on), in which case the caller
+     * should grab a wake lock, watch for {@link Callbacks#onStateChanged()} then try
+     * the request again later until the state converges.
+     */
+    public boolean requestPowerState(DisplayPowerRequest request,
+            boolean waitForNegativeProximity) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestPowerState: "
+                    + request + ", waitForNegativeProximity=" + waitForNegativeProximity);
+        }
+
+        synchronized (mLock) {
+            boolean changed = false;
+
+            if (waitForNegativeProximity
+                    && !mPendingWaitForNegativeProximityLocked) {
+                mPendingWaitForNegativeProximityLocked = true;
+                changed = true;
+            }
+
+            if (mPendingRequestLocked == null) {
+                mPendingRequestLocked = new DisplayPowerRequest(request);
+                changed = true;
+            } else if (!mPendingRequestLocked.equals(request)) {
+                mPendingRequestLocked.copyFrom(request);
+                changed = true;
+            }
+
+            if (changed) {
+                mDisplayReadyLocked = false;
+            }
+
+            if (changed && !mPendingRequestChangedLocked) {
+                mPendingRequestChangedLocked = true;
+                sendUpdatePowerStateLocked();
+            }
+
+            return mDisplayReadyLocked;
+        }
+    }
+
+    private void sendUpdatePowerState() {
+        synchronized (mLock) {
+            sendUpdatePowerStateLocked();
+        }
+    }
+
+    private void sendUpdatePowerStateLocked() {
+        if (!mPendingUpdatePowerStateLocked) {
+            mPendingUpdatePowerStateLocked = true;
+            Message msg = mHandler.obtainMessage(MSG_UPDATE_POWER_STATE);
+            msg.setAsynchronous(true);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private void initialize() {
+        mPowerState = new DisplayPowerState(
+                new ElectronBeam(mDisplayManager), mDisplayBlanker,
+                mLights.getLight(LightsManager.LIGHT_ID_BACKLIGHT));
+
+        mElectronBeamOnAnimator = ObjectAnimator.ofFloat(
+                mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 0.0f, 1.0f);
+        mElectronBeamOnAnimator.setDuration(ELECTRON_BEAM_ON_ANIMATION_DURATION_MILLIS);
+        mElectronBeamOnAnimator.addListener(mAnimatorListener);
+
+        mElectronBeamOffAnimator = ObjectAnimator.ofFloat(
+                mPowerState, DisplayPowerState.ELECTRON_BEAM_LEVEL, 1.0f, 0.0f);
+        mElectronBeamOffAnimator.setDuration(ELECTRON_BEAM_OFF_ANIMATION_DURATION_MILLIS);
+        mElectronBeamOffAnimator.addListener(mAnimatorListener);
+
+        mScreenBrightnessRampAnimator = new RampAnimator<DisplayPowerState>(
+                mPowerState, DisplayPowerState.SCREEN_BRIGHTNESS);
+    }
+
+    private final Animator.AnimatorListener mAnimatorListener = new Animator.AnimatorListener() {
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            sendUpdatePowerState();
+        }
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+    };
+
+    private void updatePowerState() {
+        // Update the power state request.
+        final boolean mustNotify;
+        boolean mustInitialize = false;
+        boolean updateAutoBrightness = mTwilightChanged;
+        boolean wasDim = false;
+        mTwilightChanged = false;
+
+        synchronized (mLock) {
+            mPendingUpdatePowerStateLocked = false;
+            if (mPendingRequestLocked == null) {
+                return; // wait until first actual power request
+            }
+
+            if (mPowerRequest == null) {
+                mPowerRequest = new DisplayPowerRequest(mPendingRequestLocked);
+                mWaitingForNegativeProximity = mPendingWaitForNegativeProximityLocked;
+                mPendingWaitForNegativeProximityLocked = false;
+                mPendingRequestChangedLocked = false;
+                mustInitialize = true;
+            } else if (mPendingRequestChangedLocked) {
+                if (mPowerRequest.screenAutoBrightnessAdjustment
+                        != mPendingRequestLocked.screenAutoBrightnessAdjustment) {
+                    updateAutoBrightness = true;
+                }
+                wasDim = (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM);
+                mPowerRequest.copyFrom(mPendingRequestLocked);
+                mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
+                mPendingWaitForNegativeProximityLocked = false;
+                mPendingRequestChangedLocked = false;
+                mDisplayReadyLocked = false;
+            }
+
+            mustNotify = !mDisplayReadyLocked;
+        }
+
+        // Initialize things the first time the power state is changed.
+        if (mustInitialize) {
+            initialize();
+        }
+
+        // Apply the proximity sensor.
+        if (mProximitySensor != null) {
+            if (mPowerRequest.useProximitySensor
+                    && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) {
+                setProximitySensorEnabled(true);
+                if (!mScreenOffBecauseOfProximity
+                        && mProximity == PROXIMITY_POSITIVE) {
+                    mScreenOffBecauseOfProximity = true;
+                    sendOnProximityPositiveWithWakelock();
+                    setScreenOn(false);
+                }
+            } else if (mWaitingForNegativeProximity
+                    && mScreenOffBecauseOfProximity
+                    && mProximity == PROXIMITY_POSITIVE
+                    && mPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) {
+                setProximitySensorEnabled(true);
+            } else {
+                setProximitySensorEnabled(false);
+                mWaitingForNegativeProximity = false;
+            }
+            if (mScreenOffBecauseOfProximity
+                    && mProximity != PROXIMITY_POSITIVE) {
+                mScreenOffBecauseOfProximity = false;
+                sendOnProximityNegativeWithWakelock();
+            }
+        } else {
+            mWaitingForNegativeProximity = false;
+        }
+
+        // Turn on the light sensor if needed.
+        if (mLightSensor != null) {
+            setLightSensorEnabled(mPowerRequest.useAutoBrightness
+                    && wantScreenOn(mPowerRequest.screenState), updateAutoBrightness);
+        }
+
+        // Set the screen brightness.
+        if (wantScreenOn(mPowerRequest.screenState)) {
+            int target;
+            boolean slow;
+            if (mScreenAutoBrightness >= 0 && mLightSensorEnabled) {
+                // Use current auto-brightness value.
+                target = mScreenAutoBrightness;
+                slow = mUsingScreenAutoBrightness;
+                mUsingScreenAutoBrightness = true;
+            } else {
+                // Light sensor is disabled or not ready yet.
+                // Use the current brightness setting from the request, which is expected
+                // provide a nominal default value for the case where auto-brightness
+                // is not ready yet.
+                target = mPowerRequest.screenBrightness;
+                slow = false;
+                mUsingScreenAutoBrightness = false;
+            }
+            if (mPowerRequest.screenState == DisplayPowerRequest.SCREEN_STATE_DIM) {
+                // Dim quickly by at least some minimum amount.
+                target = Math.min(target - SCREEN_DIM_MINIMUM_REDUCTION,
+                        mScreenBrightnessDimConfig);
+                slow = false;
+            } else if (wasDim) {
+                // Brighten quickly.
+                slow = false;
+            }
+            animateScreenBrightness(clampScreenBrightness(target),
+                    slow ? BRIGHTNESS_RAMP_RATE_SLOW : BRIGHTNESS_RAMP_RATE_FAST);
+        } else {
+            // Screen is off.  Don't bother changing the brightness.
+            mUsingScreenAutoBrightness = false;
+        }
+
+        // Animate the screen on or off.
+        if (!mScreenOffBecauseOfProximity) {
+            if (wantScreenOn(mPowerRequest.screenState)) {
+                // Want screen on.
+                // Wait for previous off animation to complete beforehand.
+                // It is relatively short but if we cancel it and switch to the
+                // on animation immediately then the results are pretty ugly.
+                if (!mElectronBeamOffAnimator.isStarted()) {
+                    // Turn the screen on.  The contents of the screen may not yet
+                    // be visible if the electron beam has not been dismissed because
+                    // its last frame of animation is solid black.
+                    setScreenOn(true);
+
+                    if (mPowerRequest.blockScreenOn
+                            && mPowerState.getElectronBeamLevel() == 0.0f) {
+                        blockScreenOn();
+                    } else {
+                        unblockScreenOn();
+                        if (USE_ELECTRON_BEAM_ON_ANIMATION) {
+                            if (!mElectronBeamOnAnimator.isStarted()) {
+                                if (mPowerState.getElectronBeamLevel() == 1.0f) {
+                                    mPowerState.dismissElectronBeam();
+                                } else if (mPowerState.prepareElectronBeam(
+                                        mElectronBeamFadesConfig ?
+                                                ElectronBeam.MODE_FADE :
+                                                        ElectronBeam.MODE_WARM_UP)) {
+                                    mElectronBeamOnAnimator.start();
+                                } else {
+                                    mElectronBeamOnAnimator.end();
+                                }
+                            }
+                        } else {
+                            mPowerState.setElectronBeamLevel(1.0f);
+                            mPowerState.dismissElectronBeam();
+                        }
+                    }
+                }
+            } else {
+                // Want screen off.
+                // Wait for previous on animation to complete beforehand.
+                if (!mElectronBeamOnAnimator.isStarted()) {
+                    if (!mElectronBeamOffAnimator.isStarted()) {
+                        if (mPowerState.getElectronBeamLevel() == 0.0f) {
+                            setScreenOn(false);
+                        } else if (mPowerState.prepareElectronBeam(
+                                mElectronBeamFadesConfig ?
+                                        ElectronBeam.MODE_FADE :
+                                                ElectronBeam.MODE_COOL_DOWN)
+                                && mPowerState.isScreenOn()) {
+                            mElectronBeamOffAnimator.start();
+                        } else {
+                            mElectronBeamOffAnimator.end();
+                        }
+                    }
+                }
+            }
+        }
+
+        // Report whether the display is ready for use.
+        // We mostly care about the screen state here, ignoring brightness changes
+        // which will be handled asynchronously.
+        if (mustNotify
+                && !mScreenOnWasBlocked
+                && !mElectronBeamOnAnimator.isStarted()
+                && !mElectronBeamOffAnimator.isStarted()
+                && mPowerState.waitUntilClean(mCleanListener)) {
+            synchronized (mLock) {
+                if (!mPendingRequestChangedLocked) {
+                    mDisplayReadyLocked = true;
+
+                    if (DEBUG) {
+                        Slog.d(TAG, "Display ready!");
+                    }
+                }
+            }
+            sendOnStateChangedWithWakelock();
+        }
+    }
+
+    private void blockScreenOn() {
+        if (!mScreenOnWasBlocked) {
+            mScreenOnWasBlocked = true;
+            if (DEBUG) {
+                Slog.d(TAG, "Blocked screen on.");
+                mScreenOnBlockStartRealTime = SystemClock.elapsedRealtime();
+            }
+        }
+    }
+
+    private void unblockScreenOn() {
+        if (mScreenOnWasBlocked) {
+            mScreenOnWasBlocked = false;
+            if (DEBUG) {
+                Slog.d(TAG, "Unblocked screen on after " +
+                        (SystemClock.elapsedRealtime() - mScreenOnBlockStartRealTime) + " ms");
+            }
+        }
+    }
+
+    private void setScreenOn(boolean on) {
+        if (!mPowerState.isScreenOn() == on) {
+            mPowerState.setScreenOn(on);
+            if (on) {
+                mNotifier.onScreenOn();
+            } else {
+                mNotifier.onScreenOff();
+            }
+        }
+    }
+
+    private int clampScreenBrightness(int value) {
+        return clamp(value, mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
+    }
+
+    private static int clampAbsoluteBrightness(int value) {
+        return clamp(value, PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON);
+    }
+
+    private static int clamp(int value, int min, int max) {
+        if (value <= min) {
+            return min;
+        }
+        if (value >= max) {
+            return max;
+        }
+        return value;
+    }
+
+    private static float normalizeAbsoluteBrightness(int value) {
+        return (float)clampAbsoluteBrightness(value) / PowerManager.BRIGHTNESS_ON;
+    }
+
+    private void animateScreenBrightness(int target, int rate) {
+        if (mScreenBrightnessRampAnimator.animateTo(target, rate)) {
+            mNotifier.onScreenBrightness(target);
+        }
+    }
+
+    private final Runnable mCleanListener = new Runnable() {
+        @Override
+        public void run() {
+            sendUpdatePowerState();
+        }
+    };
+
+    private void setProximitySensorEnabled(boolean enable) {
+        if (enable) {
+            if (!mProximitySensorEnabled) {
+                // Register the listener.
+                // Proximity sensor state already cleared initially.
+                mProximitySensorEnabled = true;
+                mSensorManager.registerListener(mProximitySensorListener, mProximitySensor,
+                        SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+            }
+        } else {
+            if (mProximitySensorEnabled) {
+                // Unregister the listener.
+                // Clear the proximity sensor state for next time.
+                mProximitySensorEnabled = false;
+                mProximity = PROXIMITY_UNKNOWN;
+                mPendingProximity = PROXIMITY_UNKNOWN;
+                mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                mSensorManager.unregisterListener(mProximitySensorListener);
+                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+            }
+        }
+    }
+
+    private void handleProximitySensorEvent(long time, boolean positive) {
+        if (mProximitySensorEnabled) {
+            if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) {
+                return; // no change
+            }
+            if (mPendingProximity == PROXIMITY_POSITIVE && positive) {
+                return; // no change
+            }
+
+            // Only accept a proximity sensor reading if it remains
+            // stable for the entire debounce delay.  We hold a wake lock while
+            // debouncing the sensor.
+            mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+            if (positive) {
+                mPendingProximity = PROXIMITY_POSITIVE;
+                setPendingProximityDebounceTime(
+                        time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock
+            } else {
+                mPendingProximity = PROXIMITY_NEGATIVE;
+                setPendingProximityDebounceTime(
+                        time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock
+            }
+
+            // Debounce the new sensor reading.
+            debounceProximitySensor();
+        }
+    }
+
+    private void debounceProximitySensor() {
+        if (mProximitySensorEnabled
+                && mPendingProximity != PROXIMITY_UNKNOWN
+                && mPendingProximityDebounceTime >= 0) {
+            final long now = SystemClock.uptimeMillis();
+            if (mPendingProximityDebounceTime <= now) {
+                // Sensor reading accepted.  Apply the change then release the wake lock.
+                mProximity = mPendingProximity;
+                updatePowerState();
+                clearPendingProximityDebounceTime(); // release wake lock (must be last)
+            } else {
+                // Need to wait a little longer.
+                // Debounce again later.  We continue holding a wake lock while waiting.
+                Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime);
+            }
+        }
+    }
+
+    private void clearPendingProximityDebounceTime() {
+        if (mPendingProximityDebounceTime >= 0) {
+            mPendingProximityDebounceTime = -1;
+            mDisplaySuspendBlocker.release(); // release wake lock
+        }
+    }
+
+    private void setPendingProximityDebounceTime(long debounceTime) {
+        if (mPendingProximityDebounceTime < 0) {
+            mDisplaySuspendBlocker.acquire(); // acquire wake lock
+        }
+        mPendingProximityDebounceTime = debounceTime;
+    }
+
+    private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) {
+        if (enable) {
+            if (!mLightSensorEnabled) {
+                updateAutoBrightness = true;
+                mLightSensorEnabled = true;
+                mLightSensorEnableTime = SystemClock.uptimeMillis();
+                mSensorManager.registerListener(mLightSensorListener, mLightSensor,
+                        LIGHT_SENSOR_RATE_MILLIS * 1000, mHandler);
+            }
+        } else {
+            if (mLightSensorEnabled) {
+                mLightSensorEnabled = false;
+                mAmbientLuxValid = false;
+                mRecentLightSamples = 0;
+                mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
+                mSensorManager.unregisterListener(mLightSensorListener);
+            }
+        }
+        if (updateAutoBrightness) {
+            updateAutoBrightness(false);
+        }
+    }
+
+    private void handleLightSensorEvent(long time, float lux) {
+        mHandler.removeMessages(MSG_LIGHT_SENSOR_DEBOUNCED);
+
+        applyLightSensorMeasurement(time, lux);
+        updateAmbientLux(time);
+    }
+
+    private void applyLightSensorMeasurement(long time, float lux) {
+        // Update our filters.
+        mRecentLightSamples += 1;
+        if (mRecentLightSamples == 1) {
+            mRecentShortTermAverageLux = lux;
+            mRecentLongTermAverageLux = lux;
+        } else {
+            final long timeDelta = time - mLastObservedLuxTime;
+            mRecentShortTermAverageLux += (lux - mRecentShortTermAverageLux)
+                    * timeDelta / (SHORT_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta);
+            mRecentLongTermAverageLux += (lux - mRecentLongTermAverageLux)
+                    * timeDelta / (LONG_TERM_AVERAGE_LIGHT_TIME_CONSTANT + timeDelta);
+        }
+
+        // Remember this sample value.
+        mLastObservedLux = lux;
+        mLastObservedLuxTime = time;
+    }
+
+    private void setAmbientLux(float lux) {
+        mAmbientLux = lux;
+        mBrighteningLuxThreshold = mAmbientLux * (1.0f + BRIGHTENING_LIGHT_HYSTERESIS);
+        mDarkeningLuxThreshold = mAmbientLux * (1.0f - DARKENING_LIGHT_HYSTERESIS);
+    }
+
+    private void updateAmbientLux(long time) {
+        // If the light sensor was just turned on then immediately update our initial
+        // estimate of the current ambient light level.
+        if (!mAmbientLuxValid) {
+            final long timeWhenSensorWarmedUp =
+                mLightSensorWarmUpTimeConfig + mLightSensorEnableTime;
+            if (time < timeWhenSensorWarmedUp) {
+                mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
+                        timeWhenSensorWarmedUp);
+                return;
+            }
+            setAmbientLux(mRecentShortTermAverageLux);
+            mAmbientLuxValid = true;
+            mDebounceLuxDirection = 0;
+            mDebounceLuxTime = time;
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Initializing: "
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+            updateAutoBrightness(true);
+        } else if (mRecentShortTermAverageLux > mBrighteningLuxThreshold
+                && mRecentLongTermAverageLux > mBrighteningLuxThreshold) {
+            // The ambient environment appears to be brightening.
+            if (mDebounceLuxDirection <= 0) {
+                mDebounceLuxDirection = 1;
+                mDebounceLuxTime = time;
+                if (DEBUG) {
+                    Slog.d(TAG, "updateAmbientLux: Possibly brightened, waiting for "
+                            + BRIGHTENING_LIGHT_DEBOUNCE + " ms: "
+                            + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+                            + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                            + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                            + ", mAmbientLux=" + mAmbientLux);
+                }
+            }
+            long debounceTime = mDebounceLuxTime + BRIGHTENING_LIGHT_DEBOUNCE;
+            if (time < debounceTime) {
+                mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+                return;
+            }
+            setAmbientLux(mRecentShortTermAverageLux);
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Brightened: "
+                        + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+            updateAutoBrightness(true);
+        } else if (mRecentShortTermAverageLux < mDarkeningLuxThreshold
+                && mRecentLongTermAverageLux < mDarkeningLuxThreshold) {
+            // The ambient environment appears to be darkening.
+            if (mDebounceLuxDirection >= 0) {
+                mDebounceLuxDirection = -1;
+                mDebounceLuxTime = time;
+                if (DEBUG) {
+                    Slog.d(TAG, "updateAmbientLux: Possibly darkened, waiting for "
+                            + DARKENING_LIGHT_DEBOUNCE + " ms: "
+                            + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+                            + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                            + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                            + ", mAmbientLux=" + mAmbientLux);
+                }
+            }
+            long debounceTime = mDebounceLuxTime + DARKENING_LIGHT_DEBOUNCE;
+            if (time < debounceTime) {
+                mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED, debounceTime);
+                return;
+            }
+            // Be conservative about reducing the brightness, only reduce it a little bit
+            // at a time to avoid having to bump it up again soon.
+            setAmbientLux(Math.max(mRecentShortTermAverageLux, mRecentLongTermAverageLux));
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Darkened: "
+                        + "mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+            updateAutoBrightness(true);
+        } else if (mDebounceLuxDirection != 0) {
+            // No change or change is within the hysteresis thresholds.
+            mDebounceLuxDirection = 0;
+            mDebounceLuxTime = time;
+            if (DEBUG) {
+                Slog.d(TAG, "updateAmbientLux: Canceled debounce: "
+                        + "mBrighteningLuxThreshold=" + mBrighteningLuxThreshold
+                        + ", mDarkeningLuxThreshold=" + mDarkeningLuxThreshold
+                        + ", mRecentShortTermAverageLux=" + mRecentShortTermAverageLux
+                        + ", mRecentLongTermAverageLux=" + mRecentLongTermAverageLux
+                        + ", mAmbientLux=" + mAmbientLux);
+            }
+        }
+
+        // Now that we've done all of that, we haven't yet posted a debounce
+        // message. So consider the case where current lux is beyond the
+        // threshold. It's possible that the light sensor may not report values
+        // if the light level does not change, so we need to occasionally
+        // synthesize sensor readings in order to make sure the brightness is
+        // adjusted accordingly. Note these thresholds may have changed since
+        // we entered the function because we called setAmbientLux and
+        // updateAutoBrightness along the way.
+        if (mLastObservedLux > mBrighteningLuxThreshold
+                || mLastObservedLux < mDarkeningLuxThreshold) {
+            mHandler.sendEmptyMessageAtTime(MSG_LIGHT_SENSOR_DEBOUNCED,
+                    time + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS);
+        }
+    }
+
+    private void debounceLightSensor() {
+        if (mLightSensorEnabled) {
+            long time = SystemClock.uptimeMillis();
+            if (time >= mLastObservedLuxTime + SYNTHETIC_LIGHT_SENSOR_RATE_MILLIS) {
+                if (DEBUG) {
+                    Slog.d(TAG, "debounceLightSensor: Synthesizing light sensor measurement "
+                            + "after " + (time - mLastObservedLuxTime) + " ms.");
+                }
+                applyLightSensorMeasurement(time, mLastObservedLux);
+            }
+            updateAmbientLux(time);
+        }
+    }
+
+    private void updateAutoBrightness(boolean sendUpdate) {
+        if (!mAmbientLuxValid) {
+            return;
+        }
+
+        float value = mScreenAutoBrightnessSpline.interpolate(mAmbientLux);
+        float gamma = 1.0f;
+
+        if (USE_SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT
+                && mPowerRequest.screenAutoBrightnessAdjustment != 0.0f) {
+            final float adjGamma = FloatMath.pow(SCREEN_AUTO_BRIGHTNESS_ADJUSTMENT_MAX_GAMMA,
+                    Math.min(1.0f, Math.max(-1.0f,
+                            -mPowerRequest.screenAutoBrightnessAdjustment)));
+            gamma *= adjGamma;
+            if (DEBUG) {
+                Slog.d(TAG, "updateAutoBrightness: adjGamma=" + adjGamma);
+            }
+        }
+
+        if (USE_TWILIGHT_ADJUSTMENT) {
+            TwilightState state = mTwilight.getCurrentState();
+            if (state != null && state.isNight()) {
+                final long now = System.currentTimeMillis();
+                final float earlyGamma =
+                        getTwilightGamma(now, state.getYesterdaySunset(), state.getTodaySunrise());
+                final float lateGamma =
+                        getTwilightGamma(now, state.getTodaySunset(), state.getTomorrowSunrise());
+                gamma *= earlyGamma * lateGamma;
+                if (DEBUG) {
+                    Slog.d(TAG, "updateAutoBrightness: earlyGamma=" + earlyGamma
+                            + ", lateGamma=" + lateGamma);
+                }
+            }
+        }
+
+        if (gamma != 1.0f) {
+            final float in = value;
+            value = FloatMath.pow(value, gamma);
+            if (DEBUG) {
+                Slog.d(TAG, "updateAutoBrightness: gamma=" + gamma
+                        + ", in=" + in + ", out=" + value);
+            }
+        }
+
+        int newScreenAutoBrightness = clampScreenBrightness(
+                Math.round(value * PowerManager.BRIGHTNESS_ON));
+        if (mScreenAutoBrightness != newScreenAutoBrightness) {
+            if (DEBUG) {
+                Slog.d(TAG, "updateAutoBrightness: mScreenAutoBrightness="
+                        + mScreenAutoBrightness + ", newScreenAutoBrightness="
+                        + newScreenAutoBrightness);
+            }
+
+            mScreenAutoBrightness = newScreenAutoBrightness;
+            mLastScreenAutoBrightnessGamma = gamma;
+            if (sendUpdate) {
+                sendUpdatePowerState();
+            }
+        }
+    }
+
+    private static float getTwilightGamma(long now, long lastSunset, long nextSunrise) {
+        if (lastSunset < 0 || nextSunrise < 0
+                || now < lastSunset || now > nextSunrise) {
+            return 1.0f;
+        }
+
+        if (now < lastSunset + TWILIGHT_ADJUSTMENT_TIME) {
+            return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
+                    (float)(now - lastSunset) / TWILIGHT_ADJUSTMENT_TIME);
+        }
+
+        if (now > nextSunrise - TWILIGHT_ADJUSTMENT_TIME) {
+            return lerp(1.0f, TWILIGHT_ADJUSTMENT_MAX_GAMMA,
+                    (float)(nextSunrise - now) / TWILIGHT_ADJUSTMENT_TIME);
+        }
+
+        return TWILIGHT_ADJUSTMENT_MAX_GAMMA;
+    }
+
+    private static float lerp(float x, float y, float alpha) {
+        return x + (y - x) * alpha;
+    }
+
+    private void sendOnStateChangedWithWakelock() {
+        mDisplaySuspendBlocker.acquire();
+        mCallbackHandler.post(mOnStateChangedRunnable);
+    }
+
+    private final Runnable mOnStateChangedRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCallbacks.onStateChanged();
+            mDisplaySuspendBlocker.release();
+        }
+    };
+
+    private void sendOnProximityPositiveWithWakelock() {
+        mDisplaySuspendBlocker.acquire();
+        mCallbackHandler.post(mOnProximityPositiveRunnable);
+    }
+
+    private final Runnable mOnProximityPositiveRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCallbacks.onProximityPositive();
+            mDisplaySuspendBlocker.release();
+        }
+    };
+
+    private void sendOnProximityNegativeWithWakelock() {
+        mDisplaySuspendBlocker.acquire();
+        mCallbackHandler.post(mOnProximityNegativeRunnable);
+    }
+
+    private final Runnable mOnProximityNegativeRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mCallbacks.onProximityNegative();
+            mDisplaySuspendBlocker.release();
+        }
+    };
+
+    public void dump(final PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Display Controller Locked State:");
+            pw.println("  mDisplayReadyLocked=" + mDisplayReadyLocked);
+            pw.println("  mPendingRequestLocked=" + mPendingRequestLocked);
+            pw.println("  mPendingRequestChangedLocked=" + mPendingRequestChangedLocked);
+            pw.println("  mPendingWaitForNegativeProximityLocked="
+                    + mPendingWaitForNegativeProximityLocked);
+            pw.println("  mPendingUpdatePowerStateLocked=" + mPendingUpdatePowerStateLocked);
+        }
+
+        pw.println();
+        pw.println("Display Controller Configuration:");
+        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+        pw.println("  mScreenBrightnessRangeMinimum=" + mScreenBrightnessRangeMinimum);
+        pw.println("  mScreenBrightnessRangeMaximum=" + mScreenBrightnessRangeMaximum);
+        pw.println("  mUseSoftwareAutoBrightnessConfig="
+                + mUseSoftwareAutoBrightnessConfig);
+        pw.println("  mScreenAutoBrightnessSpline=" + mScreenAutoBrightnessSpline);
+        pw.println("  mLightSensorWarmUpTimeConfig=" + mLightSensorWarmUpTimeConfig);
+
+        mHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                dumpLocal(pw);
+            }
+        }, 1000);
+    }
+
+    private void dumpLocal(PrintWriter pw) {
+        pw.println();
+        pw.println("Display Controller Thread State:");
+        pw.println("  mPowerRequest=" + mPowerRequest);
+        pw.println("  mWaitingForNegativeProximity=" + mWaitingForNegativeProximity);
+
+        pw.println("  mProximitySensor=" + mProximitySensor);
+        pw.println("  mProximitySensorEnabled=" + mProximitySensorEnabled);
+        pw.println("  mProximityThreshold=" + mProximityThreshold);
+        pw.println("  mProximity=" + proximityToString(mProximity));
+        pw.println("  mPendingProximity=" + proximityToString(mPendingProximity));
+        pw.println("  mPendingProximityDebounceTime="
+                + TimeUtils.formatUptime(mPendingProximityDebounceTime));
+        pw.println("  mScreenOffBecauseOfProximity=" + mScreenOffBecauseOfProximity);
+
+        pw.println("  mLightSensor=" + mLightSensor);
+        pw.println("  mLightSensorEnabled=" + mLightSensorEnabled);
+        pw.println("  mLightSensorEnableTime="
+                + TimeUtils.formatUptime(mLightSensorEnableTime));
+        pw.println("  mAmbientLux=" + mAmbientLux);
+        pw.println("  mAmbientLuxValid=" + mAmbientLuxValid);
+        pw.println("  mLastObservedLux=" + mLastObservedLux);
+        pw.println("  mLastObservedLuxTime="
+                + TimeUtils.formatUptime(mLastObservedLuxTime));
+        pw.println("  mRecentLightSamples=" + mRecentLightSamples);
+        pw.println("  mRecentShortTermAverageLux=" + mRecentShortTermAverageLux);
+        pw.println("  mRecentLongTermAverageLux=" + mRecentLongTermAverageLux);
+        pw.println("  mDebounceLuxDirection=" + mDebounceLuxDirection);
+        pw.println("  mDebounceLuxTime=" + TimeUtils.formatUptime(mDebounceLuxTime));
+        pw.println("  mScreenAutoBrightness=" + mScreenAutoBrightness);
+        pw.println("  mUsingScreenAutoBrightness=" + mUsingScreenAutoBrightness);
+        pw.println("  mLastScreenAutoBrightnessGamma=" + mLastScreenAutoBrightnessGamma);
+        pw.println("  mTwilight.getCurrentState()=" + mTwilight.getCurrentState());
+
+        if (mElectronBeamOnAnimator != null) {
+            pw.println("  mElectronBeamOnAnimator.isStarted()=" +
+                    mElectronBeamOnAnimator.isStarted());
+        }
+        if (mElectronBeamOffAnimator != null) {
+            pw.println("  mElectronBeamOffAnimator.isStarted()=" +
+                    mElectronBeamOffAnimator.isStarted());
+        }
+
+        if (mPowerState != null) {
+            mPowerState.dump(pw);
+        }
+    }
+
+    private static String proximityToString(int state) {
+        switch (state) {
+            case PROXIMITY_UNKNOWN:
+                return "Unknown";
+            case PROXIMITY_NEGATIVE:
+                return "Negative";
+            case PROXIMITY_POSITIVE:
+                return "Positive";
+            default:
+                return Integer.toString(state);
+        }
+    }
+
+    private static boolean wantScreenOn(int state) {
+        switch (state) {
+            case DisplayPowerRequest.SCREEN_STATE_BRIGHT:
+            case DisplayPowerRequest.SCREEN_STATE_DIM:
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Asynchronous callbacks from the power controller to the power manager service.
+     */
+    public interface Callbacks {
+        void onStateChanged();
+        void onProximityPositive();
+        void onProximityNegative();
+    }
+
+    private final class DisplayControllerHandler extends Handler {
+        public DisplayControllerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_POWER_STATE:
+                    updatePowerState();
+                    break;
+
+                case MSG_PROXIMITY_SENSOR_DEBOUNCED:
+                    debounceProximitySensor();
+                    break;
+
+                case MSG_LIGHT_SENSOR_DEBOUNCED:
+                    debounceLightSensor();
+                    break;
+            }
+        }
+    }
+
+    private final SensorEventListener mProximitySensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mProximitySensorEnabled) {
+                final long time = SystemClock.uptimeMillis();
+                final float distance = event.values[0];
+                boolean positive = distance >= 0.0f && distance < mProximityThreshold;
+                handleProximitySensorEvent(time, positive);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+    private final SensorEventListener mLightSensorListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            if (mLightSensorEnabled) {
+                final long time = SystemClock.uptimeMillis();
+                final float lux = event.values[0];
+                handleLightSensorEvent(time, lux);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // Not used.
+        }
+    };
+
+    private final TwilightListener mTwilightListener = new TwilightListener() {
+        @Override
+        public void onTwilightStateChanged() {
+            mTwilightChanged = true;
+            updatePowerState();
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/power/DisplayPowerRequest.java b/services/core/java/com/android/server/power/DisplayPowerRequest.java
new file mode 100644
index 0000000..22f17d7
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayPowerRequest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.os.PowerManager;
+
+/**
+ * Describes the requested power state of the display.
+ *
+ * This object is intended to describe the general characteristics of the
+ * power state, such as whether the screen should be on or off and the current
+ * brightness controls leaving the {@link DisplayPowerController} to manage the
+ * details of how the transitions between states should occur.  The goal is for
+ * the {@link PowerManagerService} to focus on the global power state and not
+ * have to micro-manage screen off animations, auto-brightness and other effects.
+ */
+final class DisplayPowerRequest {
+    public static final int SCREEN_STATE_OFF = 0;
+    public static final int SCREEN_STATE_DIM = 1;
+    public static final int SCREEN_STATE_BRIGHT = 2;
+
+    // The requested minimum screen power state: off, dim or bright.
+    public int screenState;
+
+    // If true, the proximity sensor overrides the screen state when an object is
+    // nearby, turning it off temporarily until the object is moved away.
+    public boolean useProximitySensor;
+
+    // The desired screen brightness in the range 0 (minimum / off) to 255 (brightest).
+    // The display power controller may choose to clamp the brightness.
+    // When auto-brightness is enabled, this field should specify a nominal default
+    // value to use while waiting for the light sensor to report enough data.
+    public int screenBrightness;
+
+    // The screen auto-brightness adjustment factor in the range -1 (dimmer) to 1 (brighter).
+    public float screenAutoBrightnessAdjustment;
+
+    // If true, enables automatic brightness control.
+    public boolean useAutoBrightness;
+
+    // If true, prevents the screen from completely turning on if it is currently off.
+    // The display does not enter a "ready" state if this flag is true and screen on is
+    // blocked.  The window manager policy blocks screen on while it prepares the keyguard to
+    // prevent the user from seeing intermediate updates.
+    //
+    // Technically, we may not block the screen itself from turning on (because that introduces
+    // extra unnecessary latency) but we do prevent content on screen from becoming
+    // visible to the user.
+    public boolean blockScreenOn;
+
+    public DisplayPowerRequest() {
+        screenState = SCREEN_STATE_BRIGHT;
+        useProximitySensor = false;
+        screenBrightness = PowerManager.BRIGHTNESS_ON;
+        screenAutoBrightnessAdjustment = 0.0f;
+        useAutoBrightness = false;
+        blockScreenOn = false;
+    }
+
+    public DisplayPowerRequest(DisplayPowerRequest other) {
+        copyFrom(other);
+    }
+
+    public void copyFrom(DisplayPowerRequest other) {
+        screenState = other.screenState;
+        useProximitySensor = other.useProximitySensor;
+        screenBrightness = other.screenBrightness;
+        screenAutoBrightnessAdjustment = other.screenAutoBrightnessAdjustment;
+        useAutoBrightness = other.useAutoBrightness;
+        blockScreenOn = other.blockScreenOn;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof DisplayPowerRequest
+                && equals((DisplayPowerRequest)o);
+    }
+
+    public boolean equals(DisplayPowerRequest other) {
+        return other != null
+                && screenState == other.screenState
+                && useProximitySensor == other.useProximitySensor
+                && screenBrightness == other.screenBrightness
+                && screenAutoBrightnessAdjustment == other.screenAutoBrightnessAdjustment
+                && useAutoBrightness == other.useAutoBrightness
+                && blockScreenOn == other.blockScreenOn;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
+    @Override
+    public String toString() {
+        return "screenState=" + screenState
+                + ", useProximitySensor=" + useProximitySensor
+                + ", screenBrightness=" + screenBrightness
+                + ", screenAutoBrightnessAdjustment=" + screenAutoBrightnessAdjustment
+                + ", useAutoBrightness=" + useAutoBrightness
+                + ", blockScreenOn=" + blockScreenOn;
+    }
+}
diff --git a/services/core/java/com/android/server/power/DisplayPowerState.java b/services/core/java/com/android/server/power/DisplayPowerState.java
new file mode 100644
index 0000000..42af4b4
--- /dev/null
+++ b/services/core/java/com/android/server/power/DisplayPowerState.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import com.android.server.lights.Light;
+
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.util.Slog;
+import android.view.Choreographer;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls the display power state.
+ * <p>
+ * This component is similar in nature to a {@link View} except that it describes
+ * the properties of a display.  When properties are changed, the component
+ * invalidates itself and posts a callback to apply the changes in a consistent order.
+ * This mechanism enables multiple properties of the display power state to be animated
+ * together smoothly by the animation framework.  Some of the work to blank or unblank
+ * the display is done on a separate thread to avoid blocking the looper.
+ * </p><p>
+ * This component must only be created or accessed by the {@link Looper} thread
+ * that belongs to the {@link DisplayPowerController}.
+ * </p><p>
+ * We don't need to worry about holding a suspend blocker here because the
+ * {@link PowerManagerService} does that for us whenever there is a change
+ * in progress.
+ * </p>
+ */
+final class DisplayPowerState {
+    private static final String TAG = "DisplayPowerState";
+
+    private static boolean DEBUG = false;
+
+    private final Handler mHandler;
+    private final Choreographer mChoreographer;
+    private final ElectronBeam mElectronBeam;
+    private final DisplayBlanker mDisplayBlanker;
+    private final Light mBacklight;
+    private final PhotonicModulator mPhotonicModulator;
+
+    private boolean mScreenOn;
+    private int mScreenBrightness;
+    private boolean mScreenReady;
+    private boolean mScreenUpdatePending;
+
+    private boolean mElectronBeamPrepared;
+    private float mElectronBeamLevel;
+    private boolean mElectronBeamReady;
+    private boolean mElectronBeamDrawPending;
+
+    private Runnable mCleanListener;
+
+    public DisplayPowerState(ElectronBeam electronBean,
+            DisplayBlanker displayBlanker, Light backlight) {
+        mHandler = new Handler(true /*async*/);
+        mChoreographer = Choreographer.getInstance();
+        mElectronBeam = electronBean;
+        mDisplayBlanker = displayBlanker;
+        mBacklight = backlight;
+        mPhotonicModulator = new PhotonicModulator();
+
+        // At boot time, we know that the screen is on and the electron beam
+        // animation is not playing.  We don't know the screen's brightness though,
+        // so prepare to set it to a known state when the state is next applied.
+        // Although we set the brightness to full on here, the display power controller
+        // will reset the brightness to a new level immediately before the changes
+        // actually have a chance to be applied.
+        mScreenOn = true;
+        mScreenBrightness = PowerManager.BRIGHTNESS_ON;
+        scheduleScreenUpdate();
+
+        mElectronBeamPrepared = false;
+        mElectronBeamLevel = 1.0f;
+        mElectronBeamReady = true;
+    }
+
+    public static final FloatProperty<DisplayPowerState> ELECTRON_BEAM_LEVEL =
+            new FloatProperty<DisplayPowerState>("electronBeamLevel") {
+        @Override
+        public void setValue(DisplayPowerState object, float value) {
+            object.setElectronBeamLevel(value);
+        }
+
+        @Override
+        public Float get(DisplayPowerState object) {
+            return object.getElectronBeamLevel();
+        }
+    };
+
+    public static final IntProperty<DisplayPowerState> SCREEN_BRIGHTNESS =
+            new IntProperty<DisplayPowerState>("screenBrightness") {
+        @Override
+        public void setValue(DisplayPowerState object, int value) {
+            object.setScreenBrightness(value);
+        }
+
+        @Override
+        public Integer get(DisplayPowerState object) {
+            return object.getScreenBrightness();
+        }
+    };
+
+    /**
+     * Sets whether the screen is on or off.
+     */
+    public void setScreenOn(boolean on) {
+        if (mScreenOn != on) {
+            if (DEBUG) {
+                Slog.d(TAG, "setScreenOn: on=" + on);
+            }
+
+            mScreenOn = on;
+            mScreenReady = false;
+            scheduleScreenUpdate();
+        }
+    }
+
+    /**
+     * Returns true if the screen is on.
+     */
+    public boolean isScreenOn() {
+        return mScreenOn;
+    }
+
+    /**
+     * Sets the display brightness.
+     *
+     * @param brightness The brightness, ranges from 0 (minimum / off) to 255 (brightest).
+     */
+    public void setScreenBrightness(int brightness) {
+        if (mScreenBrightness != brightness) {
+            if (DEBUG) {
+                Slog.d(TAG, "setScreenBrightness: brightness=" + brightness);
+            }
+
+            mScreenBrightness = brightness;
+            if (mScreenOn) {
+                mScreenReady = false;
+                scheduleScreenUpdate();
+            }
+        }
+    }
+
+    /**
+     * Gets the screen brightness.
+     */
+    public int getScreenBrightness() {
+        return mScreenBrightness;
+    }
+
+    /**
+     * Prepares the electron beam to turn on or off.
+     * This method should be called before starting an animation because it
+     * can take a fair amount of time to prepare the electron beam surface.
+     *
+     * @param mode The electron beam animation mode to prepare.
+     * @return True if the electron beam was prepared.
+     */
+    public boolean prepareElectronBeam(int mode) {
+        if (!mElectronBeam.prepare(mode)) {
+            mElectronBeamPrepared = false;
+            mElectronBeamReady = true;
+            return false;
+        }
+
+        mElectronBeamPrepared = true;
+        mElectronBeamReady = false;
+        scheduleElectronBeamDraw();
+        return true;
+    }
+
+    /**
+     * Dismisses the electron beam surface.
+     */
+    public void dismissElectronBeam() {
+        mElectronBeam.dismiss();
+        mElectronBeamPrepared = false;
+        mElectronBeamReady = true;
+    }
+
+    /**
+     * Sets the level of the electron beam steering current.
+     *
+     * The display is blanked when the level is 0.0.  In normal use, the electron
+     * beam should have a value of 1.0.  The electron beam is unstable in between
+     * these states and the picture quality may be compromised.  For best effect,
+     * the electron beam should be warmed up or cooled off slowly.
+     *
+     * Warning: Electron beam emits harmful radiation.  Avoid direct exposure to
+     * skin or eyes.
+     *
+     * @param level The level, ranges from 0.0 (full off) to 1.0 (full on).
+     */
+    public void setElectronBeamLevel(float level) {
+        if (mElectronBeamLevel != level) {
+            if (DEBUG) {
+                Slog.d(TAG, "setElectronBeamLevel: level=" + level);
+            }
+
+            mElectronBeamLevel = level;
+            if (mScreenOn) {
+                mScreenReady = false;
+                scheduleScreenUpdate(); // update backlight brightness
+            }
+            if (mElectronBeamPrepared) {
+                mElectronBeamReady = false;
+                scheduleElectronBeamDraw();
+            }
+        }
+    }
+
+    /**
+     * Gets the level of the electron beam steering current.
+     */
+    public float getElectronBeamLevel() {
+        return mElectronBeamLevel;
+    }
+
+    /**
+     * Returns true if no properties have been invalidated.
+     * Otherwise, returns false and promises to invoke the specified listener
+     * when the properties have all been applied.
+     * The listener always overrides any previously set listener.
+     */
+    public boolean waitUntilClean(Runnable listener) {
+        if (!mScreenReady || !mElectronBeamReady) {
+            mCleanListener = listener;
+            return false;
+        } else {
+            mCleanListener = null;
+            return true;
+        }
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println();
+        pw.println("Display Power State:");
+        pw.println("  mScreenOn=" + mScreenOn);
+        pw.println("  mScreenBrightness=" + mScreenBrightness);
+        pw.println("  mScreenReady=" + mScreenReady);
+        pw.println("  mScreenUpdatePending=" + mScreenUpdatePending);
+        pw.println("  mElectronBeamPrepared=" + mElectronBeamPrepared);
+        pw.println("  mElectronBeamLevel=" + mElectronBeamLevel);
+        pw.println("  mElectronBeamReady=" + mElectronBeamReady);
+        pw.println("  mElectronBeamDrawPending=" + mElectronBeamDrawPending);
+
+        mPhotonicModulator.dump(pw);
+        mElectronBeam.dump(pw);
+    }
+
+    private void scheduleScreenUpdate() {
+        if (!mScreenUpdatePending) {
+            mScreenUpdatePending = true;
+            postScreenUpdateThreadSafe();
+        }
+    }
+
+    private void postScreenUpdateThreadSafe() {
+        mHandler.removeCallbacks(mScreenUpdateRunnable);
+        mHandler.post(mScreenUpdateRunnable);
+    }
+
+    private void scheduleElectronBeamDraw() {
+        if (!mElectronBeamDrawPending) {
+            mElectronBeamDrawPending = true;
+            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL,
+                    mElectronBeamDrawRunnable, null);
+        }
+    }
+
+    private void invokeCleanListenerIfNeeded() {
+        final Runnable listener = mCleanListener;
+        if (listener != null && mScreenReady && mElectronBeamReady) {
+            mCleanListener = null;
+            listener.run();
+        }
+    }
+
+    private final Runnable mScreenUpdateRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mScreenUpdatePending = false;
+
+            int brightness = mScreenOn && mElectronBeamLevel > 0f ? mScreenBrightness : 0;
+            if (mPhotonicModulator.setState(mScreenOn, brightness)) {
+                mScreenReady = true;
+                invokeCleanListenerIfNeeded();
+            }
+        }
+    };
+
+    private final Runnable mElectronBeamDrawRunnable = new Runnable() {
+        @Override
+        public void run() {
+            mElectronBeamDrawPending = false;
+
+            if (mElectronBeamPrepared) {
+                mElectronBeam.draw(mElectronBeamLevel);
+            }
+
+            mElectronBeamReady = true;
+            invokeCleanListenerIfNeeded();
+        }
+    };
+
+    /**
+     * Updates the state of the screen and backlight asynchronously on a separate thread.
+     */
+    private final class PhotonicModulator {
+        private static final boolean INITIAL_SCREEN_ON = false; // unknown, assume off
+        private static final int INITIAL_BACKLIGHT = -1; // unknown
+
+        private final Object mLock = new Object();
+
+        private boolean mPendingOn = INITIAL_SCREEN_ON;
+        private int mPendingBacklight = INITIAL_BACKLIGHT;
+        private boolean mActualOn = INITIAL_SCREEN_ON;
+        private int mActualBacklight = INITIAL_BACKLIGHT;
+        private boolean mChangeInProgress;
+
+        public boolean setState(boolean on, int backlight) {
+            synchronized (mLock) {
+                if (on != mPendingOn || backlight != mPendingBacklight) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Requesting new screen state: on=" + on
+                                + ", backlight=" + backlight);
+                    }
+
+                    mPendingOn = on;
+                    mPendingBacklight = backlight;
+
+                    if (!mChangeInProgress) {
+                        mChangeInProgress = true;
+                        AsyncTask.THREAD_POOL_EXECUTOR.execute(mTask);
+                    }
+                }
+                return mChangeInProgress;
+            }
+        }
+
+        public void dump(PrintWriter pw) {
+            pw.println();
+            pw.println("Photonic Modulator State:");
+            pw.println("  mPendingOn=" + mPendingOn);
+            pw.println("  mPendingBacklight=" + mPendingBacklight);
+            pw.println("  mActualOn=" + mActualOn);
+            pw.println("  mActualBacklight=" + mActualBacklight);
+            pw.println("  mChangeInProgress=" + mChangeInProgress);
+        }
+
+        private final Runnable mTask = new Runnable() {
+            @Override
+            public void run() {
+                // Apply pending changes until done.
+                for (;;) {
+                    final boolean on;
+                    final boolean onChanged;
+                    final int backlight;
+                    final boolean backlightChanged;
+                    synchronized (mLock) {
+                        on = mPendingOn;
+                        onChanged = (on != mActualOn);
+                        backlight = mPendingBacklight;
+                        backlightChanged = (backlight != mActualBacklight);
+                        if (!onChanged && !backlightChanged) {
+                            mChangeInProgress = false;
+                            break;
+                        }
+                        mActualOn = on;
+                        mActualBacklight = backlight;
+                    }
+
+                    if (DEBUG) {
+                        Slog.d(TAG, "Updating screen state: on=" + on
+                                + ", backlight=" + backlight);
+                    }
+                    if (onChanged && on) {
+                        mDisplayBlanker.unblankAllDisplays();
+                    }
+                    if (backlightChanged) {
+                        mBacklight.setBrightness(backlight);
+                    }
+                    if (onChanged && !on) {
+                        mDisplayBlanker.blankAllDisplays();
+                    }
+                }
+
+                // Let the outer class know that all changes have been applied.
+                postScreenUpdateThreadSafe();
+            }
+        };
+    }
+}
diff --git a/services/core/java/com/android/server/power/ElectronBeam.java b/services/core/java/com/android/server/power/ElectronBeam.java
new file mode 100644
index 0000000..729bd16
--- /dev/null
+++ b/services/core/java/com/android/server/power/ElectronBeam.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import android.graphics.PixelFormat;
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.opengl.GLES10;
+import android.opengl.GLES11Ext;
+import android.os.Looper;
+import android.util.FloatMath;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import com.android.server.display.DisplayManagerService;
+import com.android.server.display.DisplayTransactionListener;
+
+/**
+ * Bzzzoooop!  *crackle*
+ * <p>
+ * Animates a screen transition from on to off or off to on by applying
+ * some GL transformations to a screenshot.
+ * </p><p>
+ * This component must only be created or accessed by the {@link Looper} thread
+ * that belongs to the {@link DisplayPowerController}.
+ * </p>
+ */
+final class ElectronBeam {
+    private static final String TAG = "ElectronBeam";
+
+    private static final boolean DEBUG = false;
+
+    // The layer for the electron beam surface.
+    // This is currently hardcoded to be one layer above the boot animation.
+    private static final int ELECTRON_BEAM_LAYER = 0x40000001;
+
+    // The relative proportion of the animation to spend performing
+    // the horizontal stretch effect.  The remainder is spent performing
+    // the vertical stretch effect.
+    private static final float HSTRETCH_DURATION = 0.5f;
+    private static final float VSTRETCH_DURATION = 1.0f - HSTRETCH_DURATION;
+
+    // The number of frames to draw when preparing the animation so that it will
+    // be ready to run smoothly.  We use 3 frames because we are triple-buffered.
+    // See code for details.
+    private static final int DEJANK_FRAMES = 3;
+
+    // Set to true when the animation context has been fully prepared.
+    private boolean mPrepared;
+    private int mMode;
+
+    private final DisplayManagerService mDisplayManager;
+    private int mDisplayLayerStack; // layer stack associated with primary display
+    private int mDisplayWidth;      // real width, not rotated
+    private int mDisplayHeight;     // real height, not rotated
+    private SurfaceSession mSurfaceSession;
+    private SurfaceControl mSurfaceControl;
+    private Surface mSurface;
+    private NaturalSurfaceLayout mSurfaceLayout;
+    private EGLDisplay mEglDisplay;
+    private EGLConfig mEglConfig;
+    private EGLContext mEglContext;
+    private EGLSurface mEglSurface;
+    private boolean mSurfaceVisible;
+    private float mSurfaceAlpha;
+
+    // Texture names.  We only use one texture, which contains the screenshot.
+    private final int[] mTexNames = new int[1];
+    private boolean mTexNamesGenerated;
+    private final float mTexMatrix[] = new float[16];
+
+    // Vertex and corresponding texture coordinates.
+    // We have 4 2D vertices, so 8 elements.  The vertices form a quad.
+    private final FloatBuffer mVertexBuffer = createNativeFloatBuffer(8);
+    private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8);
+
+    /**
+     * Animates an electron beam warming up.
+     */
+    public static final int MODE_WARM_UP = 0;
+
+    /**
+     * Animates an electron beam shutting off.
+     */
+    public static final int MODE_COOL_DOWN = 1;
+
+    /**
+     * Animates a simple dim layer to fade the contents of the screen in or out progressively.
+     */
+    public static final int MODE_FADE = 2;
+
+
+    public ElectronBeam(DisplayManagerService displayManager) {
+        mDisplayManager = displayManager;
+    }
+
+    /**
+     * Warms up the electron beam in preparation for turning on or off.
+     * This method prepares a GL context, and captures a screen shot.
+     *
+     * @param mode The desired mode for the upcoming animation.
+     * @return True if the electron beam is ready, false if it is uncontrollable.
+     */
+    public boolean prepare(int mode) {
+        if (DEBUG) {
+            Slog.d(TAG, "prepare: mode=" + mode);
+        }
+
+        mMode = mode;
+
+        // Get the display size and layer stack.
+        // This is not expected to change while the electron beam surface is showing.
+        DisplayInfo displayInfo = mDisplayManager.getDisplayInfo(Display.DEFAULT_DISPLAY);
+        mDisplayLayerStack = displayInfo.layerStack;
+        mDisplayWidth = displayInfo.getNaturalWidth();
+        mDisplayHeight = displayInfo.getNaturalHeight();
+
+        // Prepare the surface for drawing.
+        if (!tryPrepare()) {
+            dismiss();
+            return false;
+        }
+
+        // Done.
+        mPrepared = true;
+
+        // Dejanking optimization.
+        // Some GL drivers can introduce a lot of lag in the first few frames as they
+        // initialize their state and allocate graphics buffers for rendering.
+        // Work around this problem by rendering the first frame of the animation a few
+        // times.  The rest of the animation should run smoothly thereafter.
+        // The frames we draw here aren't visible because we are essentially just
+        // painting the screenshot as-is.
+        if (mode == MODE_COOL_DOWN) {
+            for (int i = 0; i < DEJANK_FRAMES; i++) {
+                draw(1.0f);
+            }
+        }
+        return true;
+    }
+
+    private boolean tryPrepare() {
+        if (createSurface()) {
+            if (mMode == MODE_FADE) {
+                return true;
+            }
+            return createEglContext()
+                    && createEglSurface()
+                    && captureScreenshotTextureAndSetViewport();
+        }
+        return false;
+    }
+
+    /**
+     * Dismisses the electron beam animation surface and cleans up.
+     *
+     * To prevent stray photons from leaking out after the electron beam has been
+     * turned off, it is a good idea to defer dismissing the animation until the
+     * electron beam has been turned back on fully.
+     */
+    public void dismiss() {
+        if (DEBUG) {
+            Slog.d(TAG, "dismiss");
+        }
+
+        destroyScreenshotTexture();
+        destroyEglSurface();
+        destroySurface();
+        mPrepared = false;
+    }
+
+    /**
+     * Draws an animation frame showing the electron beam activated at the
+     * specified level.
+     *
+     * @param level The electron beam level.
+     * @return True if successful.
+     */
+    public boolean draw(float level) {
+        if (DEBUG) {
+            Slog.d(TAG, "drawFrame: level=" + level);
+        }
+
+        if (!mPrepared) {
+            return false;
+        }
+
+        if (mMode == MODE_FADE) {
+            return showSurface(1.0f - level);
+        }
+
+        if (!attachEglContext()) {
+            return false;
+        }
+        try {
+            // Clear frame to solid black.
+            GLES10.glClearColor(0f, 0f, 0f, 1f);
+            GLES10.glClear(GLES10.GL_COLOR_BUFFER_BIT);
+
+            // Draw the frame.
+            if (level < HSTRETCH_DURATION) {
+                drawHStretch(1.0f - (level / HSTRETCH_DURATION));
+            } else {
+                drawVStretch(1.0f - ((level - HSTRETCH_DURATION) / VSTRETCH_DURATION));
+            }
+            if (checkGlErrors("drawFrame")) {
+                return false;
+            }
+
+            EGL14.eglSwapBuffers(mEglDisplay, mEglSurface);
+        } finally {
+            detachEglContext();
+        }
+        return showSurface(1.0f);
+    }
+
+    /**
+     * Draws a frame where the content of the electron beam is collapsing inwards upon
+     * itself vertically with red / green / blue channels dispersing and eventually
+     * merging down to a single horizontal line.
+     *
+     * @param stretch The stretch factor.  0.0 is no collapse, 1.0 is full collapse.
+     */
+    private void drawVStretch(float stretch) {
+        // compute interpolation scale factors for each color channel
+        final float ar = scurve(stretch, 7.5f);
+        final float ag = scurve(stretch, 8.0f);
+        final float ab = scurve(stretch, 8.5f);
+        if (DEBUG) {
+            Slog.d(TAG, "drawVStretch: stretch=" + stretch
+                    + ", ar=" + ar + ", ag=" + ag + ", ab=" + ab);
+        }
+
+        // set blending
+        GLES10.glBlendFunc(GLES10.GL_ONE, GLES10.GL_ONE);
+        GLES10.glEnable(GLES10.GL_BLEND);
+
+        // bind vertex buffer
+        GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
+        GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
+
+        // set-up texturing
+        GLES10.glDisable(GLES10.GL_TEXTURE_2D);
+        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+
+        // bind texture and set blending for drawing planes
+        GLES10.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTexNames[0]);
+        GLES10.glTexEnvx(GLES10.GL_TEXTURE_ENV, GLES10.GL_TEXTURE_ENV_MODE,
+                mMode == MODE_WARM_UP ? GLES10.GL_MODULATE : GLES10.GL_REPLACE);
+        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES10.GL_TEXTURE_MAG_FILTER, GLES10.GL_LINEAR);
+        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES10.GL_TEXTURE_MIN_FILTER, GLES10.GL_LINEAR);
+        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES10.GL_TEXTURE_WRAP_S, GLES10.GL_CLAMP_TO_EDGE);
+        GLES10.glTexParameterx(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                GLES10.GL_TEXTURE_WRAP_T, GLES10.GL_CLAMP_TO_EDGE);
+        GLES10.glEnable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+        GLES10.glTexCoordPointer(2, GLES10.GL_FLOAT, 0, mTexCoordBuffer);
+        GLES10.glEnableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
+
+        // draw the red plane
+        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ar);
+        GLES10.glColorMask(true, false, false, true);
+        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
+
+        // draw the green plane
+        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
+        GLES10.glColorMask(false, true, false, true);
+        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
+
+        // draw the blue plane
+        setVStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ab);
+        GLES10.glColorMask(false, false, true, true);
+        GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
+
+        // clean up after drawing planes
+        GLES10.glDisable(GLES11Ext.GL_TEXTURE_EXTERNAL_OES);
+        GLES10.glDisableClientState(GLES10.GL_TEXTURE_COORD_ARRAY);
+        GLES10.glColorMask(true, true, true, true);
+
+        // draw the white highlight (we use the last vertices)
+        if (mMode == MODE_COOL_DOWN) {
+            GLES10.glColor4f(ag, ag, ag, 1.0f);
+            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
+        }
+
+        // clean up
+        GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
+        GLES10.glDisable(GLES10.GL_BLEND);
+    }
+
+    /**
+     * Draws a frame where the electron beam has been stretched out into
+     * a thin white horizontal line that fades as it collapses inwards.
+     *
+     * @param stretch The stretch factor.  0.0 is maximum stretch / no fade,
+     * 1.0 is collapsed / maximum fade.
+     */
+    private void drawHStretch(float stretch) {
+        // compute interpolation scale factor
+        final float ag = scurve(stretch, 8.0f);
+        if (DEBUG) {
+            Slog.d(TAG, "drawHStretch: stretch=" + stretch + ", ag=" + ag);
+        }
+
+        if (stretch < 1.0f) {
+            // bind vertex buffer
+            GLES10.glVertexPointer(2, GLES10.GL_FLOAT, 0, mVertexBuffer);
+            GLES10.glEnableClientState(GLES10.GL_VERTEX_ARRAY);
+
+            // draw narrow fading white line
+            setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag);
+            GLES10.glColor4f(1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f);
+            GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4);
+
+            // clean up
+            GLES10.glDisableClientState(GLES10.GL_VERTEX_ARRAY);
+        }
+    }
+
+    private static void setVStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
+        final float w = dw + (dw * a);
+        final float h = dh - (dh * a);
+        final float x = (dw - w) * 0.5f;
+        final float y = (dh - h) * 0.5f;
+        setQuad(vtx, x, y, w, h);
+    }
+
+    private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) {
+        final float w = 2 * dw * (1.0f - a);
+        final float h = 1.0f;
+        final float x = (dw - w) * 0.5f;
+        final float y = (dh - h) * 0.5f;
+        setQuad(vtx, x, y, w, h);
+    }
+
+    private static void setQuad(FloatBuffer vtx, float x, float y, float w, float h) {
+        if (DEBUG) {
+            Slog.d(TAG, "setQuad: x=" + x + ", y=" + y + ", w=" + w + ", h=" + h);
+        }
+        vtx.put(0, x);
+        vtx.put(1, y);
+        vtx.put(2, x);
+        vtx.put(3, y + h);
+        vtx.put(4, x + w);
+        vtx.put(5, y + h);
+        vtx.put(6, x + w);
+        vtx.put(7, y);
+    }
+
+    private boolean captureScreenshotTextureAndSetViewport() {
+        if (!attachEglContext()) {
+            return false;
+        }
+        try {
+            if (!mTexNamesGenerated) {
+                GLES10.glGenTextures(1, mTexNames, 0);
+                if (checkGlErrors("glGenTextures")) {
+                    return false;
+                }
+                mTexNamesGenerated = true;
+            }
+
+            final SurfaceTexture st = new SurfaceTexture(mTexNames[0]);
+            final Surface s = new Surface(st);
+            try {
+                SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), s);
+            } finally {
+                s.release();
+            }
+
+            st.updateTexImage();
+            st.getTransformMatrix(mTexMatrix);
+
+            // Set up texture coordinates for a quad.
+            // We might need to change this if the texture ends up being
+            // a different size from the display for some reason.
+            mTexCoordBuffer.put(0, 0f); mTexCoordBuffer.put(1, 0f);
+            mTexCoordBuffer.put(2, 0f); mTexCoordBuffer.put(3, 1f);
+            mTexCoordBuffer.put(4, 1f); mTexCoordBuffer.put(5, 1f);
+            mTexCoordBuffer.put(6, 1f); mTexCoordBuffer.put(7, 0f);
+
+            // Set up our viewport.
+            GLES10.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
+            GLES10.glMatrixMode(GLES10.GL_PROJECTION);
+            GLES10.glLoadIdentity();
+            GLES10.glOrthof(0, mDisplayWidth, 0, mDisplayHeight, 0, 1);
+            GLES10.glMatrixMode(GLES10.GL_MODELVIEW);
+            GLES10.glLoadIdentity();
+            GLES10.glMatrixMode(GLES10.GL_TEXTURE);
+            GLES10.glLoadIdentity();
+            GLES10.glLoadMatrixf(mTexMatrix, 0);
+        } finally {
+            detachEglContext();
+        }
+        return true;
+    }
+
+    private void destroyScreenshotTexture() {
+        if (mTexNamesGenerated) {
+            mTexNamesGenerated = false;
+            if (attachEglContext()) {
+                try {
+                    GLES10.glDeleteTextures(1, mTexNames, 0);
+                    checkGlErrors("glDeleteTextures");
+                } finally {
+                    detachEglContext();
+                }
+            }
+        }
+    }
+
+    private boolean createEglContext() {
+        if (mEglDisplay == null) {
+            mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+            if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
+                logEglError("eglGetDisplay");
+                return false;
+            }
+
+            int[] version = new int[2];
+            if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
+                mEglDisplay = null;
+                logEglError("eglInitialize");
+                return false;
+            }
+        }
+
+        if (mEglConfig == null) {
+            int[] eglConfigAttribList = new int[] {
+                    EGL14.EGL_RED_SIZE, 8,
+                    EGL14.EGL_GREEN_SIZE, 8,
+                    EGL14.EGL_BLUE_SIZE, 8,
+                    EGL14.EGL_ALPHA_SIZE, 8,
+                    EGL14.EGL_NONE
+            };
+            int[] numEglConfigs = new int[1];
+            EGLConfig[] eglConfigs = new EGLConfig[1];
+            if (!EGL14.eglChooseConfig(mEglDisplay, eglConfigAttribList, 0,
+                    eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) {
+                logEglError("eglChooseConfig");
+                return false;
+            }
+            mEglConfig = eglConfigs[0];
+        }
+
+        if (mEglContext == null) {
+            int[] eglContextAttribList = new int[] {
+                    EGL14.EGL_NONE
+            };
+            mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig,
+                    EGL14.EGL_NO_CONTEXT, eglContextAttribList, 0);
+            if (mEglContext == null) {
+                logEglError("eglCreateContext");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /* not used because it is too expensive to create / destroy contexts all of the time
+    private void destroyEglContext() {
+        if (mEglContext != null) {
+            if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) {
+                logEglError("eglDestroyContext");
+            }
+            mEglContext = null;
+        }
+    }*/
+
+    private boolean createSurface() {
+        if (mSurfaceSession == null) {
+            mSurfaceSession = new SurfaceSession();
+        }
+
+        SurfaceControl.openTransaction();
+        try {
+            if (mSurfaceControl == null) {
+                try {
+                    int flags;
+                    if (mMode == MODE_FADE) {
+                        flags = SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN;
+                    } else {
+                        flags = SurfaceControl.OPAQUE | SurfaceControl.HIDDEN;
+                    }
+                    mSurfaceControl = new SurfaceControl(mSurfaceSession,
+                            "ElectronBeam", mDisplayWidth, mDisplayHeight,
+                            PixelFormat.OPAQUE, flags);
+                } catch (OutOfResourcesException ex) {
+                    Slog.e(TAG, "Unable to create surface.", ex);
+                    return false;
+                }
+            }
+
+            mSurfaceControl.setLayerStack(mDisplayLayerStack);
+            mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight);
+            mSurface = new Surface();
+            mSurface.copyFrom(mSurfaceControl);
+
+            mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl);
+            mSurfaceLayout.onDisplayTransaction();
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
+        return true;
+    }
+
+    private boolean createEglSurface() {
+        if (mEglSurface == null) {
+            int[] eglSurfaceAttribList = new int[] {
+                    EGL14.EGL_NONE
+            };
+            // turn our SurfaceControl into a Surface
+            mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface,
+                    eglSurfaceAttribList, 0);
+            if (mEglSurface == null) {
+                logEglError("eglCreateWindowSurface");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void destroyEglSurface() {
+        if (mEglSurface != null) {
+            if (!EGL14.eglDestroySurface(mEglDisplay, mEglSurface)) {
+                logEglError("eglDestroySurface");
+            }
+            mEglSurface = null;
+        }
+    }
+
+    private void destroySurface() {
+        if (mSurfaceControl != null) {
+            mSurfaceLayout.dispose();
+            mSurfaceLayout = null;
+            SurfaceControl.openTransaction();
+            try {
+                mSurfaceControl.destroy();
+                mSurface.release();
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+            mSurfaceControl = null;
+            mSurfaceVisible = false;
+            mSurfaceAlpha = 0f;
+        }
+    }
+
+    private boolean showSurface(float alpha) {
+        if (!mSurfaceVisible || mSurfaceAlpha != alpha) {
+            SurfaceControl.openTransaction();
+            try {
+                mSurfaceControl.setLayer(ELECTRON_BEAM_LAYER);
+                mSurfaceControl.setAlpha(alpha);
+                mSurfaceControl.show();
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+            mSurfaceVisible = true;
+            mSurfaceAlpha = alpha;
+        }
+        return true;
+    }
+
+    private boolean attachEglContext() {
+        if (mEglSurface == null) {
+            return false;
+        }
+        if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+            logEglError("eglMakeCurrent");
+            return false;
+        }
+        return true;
+    }
+
+    private void detachEglContext() {
+        if (mEglDisplay != null) {
+            EGL14.eglMakeCurrent(mEglDisplay,
+                    EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
+        }
+    }
+
+    /**
+     * Interpolates a value in the range 0 .. 1 along a sigmoid curve
+     * yielding a result in the range 0 .. 1 scaled such that:
+     * scurve(0) == 0, scurve(0.5) == 0.5, scurve(1) == 1.
+     */
+    private static float scurve(float value, float s) {
+        // A basic sigmoid has the form y = 1.0f / FloatMap.exp(-x * s).
+        // Here we take the input datum and shift it by 0.5 so that the
+        // domain spans the range -0.5 .. 0.5 instead of 0 .. 1.
+        final float x = value - 0.5f;
+
+        // Next apply the sigmoid function to the scaled value
+        // which produces a value in the range 0 .. 1 so we subtract
+        // 0.5 to get a value in the range -0.5 .. 0.5 instead.
+        final float y = sigmoid(x, s) - 0.5f;
+
+        // To obtain the desired boundary conditions we need to scale
+        // the result so that it fills a range of -1 .. 1.
+        final float v = sigmoid(0.5f, s) - 0.5f;
+
+        // And finally remap the value back to a range of 0 .. 1.
+        return y / v * 0.5f + 0.5f;
+    }
+
+    private static float sigmoid(float x, float s) {
+        return 1.0f / (1.0f + FloatMath.exp(-x * s));
+    }
+
+    private static FloatBuffer createNativeFloatBuffer(int size) {
+        ByteBuffer bb = ByteBuffer.allocateDirect(size * 4);
+        bb.order(ByteOrder.nativeOrder());
+        return bb.asFloatBuffer();
+    }
+
+    private static void logEglError(String func) {
+        Slog.e(TAG, func + " failed: error " + EGL14.eglGetError(), new Throwable());
+    }
+
+    private static boolean checkGlErrors(String func) {
+        return checkGlErrors(func, true);
+    }
+
+    private static boolean checkGlErrors(String func, boolean log) {
+        boolean hadError = false;
+        int error;
+        while ((error = GLES10.glGetError()) != GLES10.GL_NO_ERROR) {
+            if (log) {
+                Slog.e(TAG, func + " failed: error " + error, new Throwable());
+            }
+            hadError = true;
+        }
+        return hadError;
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println();
+        pw.println("Electron Beam State:");
+        pw.println("  mPrepared=" + mPrepared);
+        pw.println("  mMode=" + mMode);
+        pw.println("  mDisplayLayerStack=" + mDisplayLayerStack);
+        pw.println("  mDisplayWidth=" + mDisplayWidth);
+        pw.println("  mDisplayHeight=" + mDisplayHeight);
+        pw.println("  mSurfaceVisible=" + mSurfaceVisible);
+        pw.println("  mSurfaceAlpha=" + mSurfaceAlpha);
+    }
+
+    /**
+     * Keeps a surface aligned with the natural orientation of the device.
+     * Updates the position and transformation of the matrix whenever the display
+     * is rotated.  This is a little tricky because the display transaction
+     * callback can be invoked on any thread, not necessarily the thread that
+     * owns the electron beam.
+     */
+    private static final class NaturalSurfaceLayout implements DisplayTransactionListener {
+        private final DisplayManagerService mDisplayManager;
+        private SurfaceControl mSurfaceControl;
+
+        public NaturalSurfaceLayout(DisplayManagerService displayManager, SurfaceControl surfaceControl) {
+            mDisplayManager = displayManager;
+            mSurfaceControl = surfaceControl;
+            mDisplayManager.registerDisplayTransactionListener(this);
+        }
+
+        public void dispose() {
+            synchronized (this) {
+                mSurfaceControl = null;
+            }
+            mDisplayManager.unregisterDisplayTransactionListener(this);
+        }
+
+        @Override
+        public void onDisplayTransaction() {
+            synchronized (this) {
+                if (mSurfaceControl == null) {
+                    return;
+                }
+
+                DisplayInfo displayInfo = mDisplayManager.getDisplayInfo(Display.DEFAULT_DISPLAY);
+                switch (displayInfo.rotation) {
+                    case Surface.ROTATION_0:
+                        mSurfaceControl.setPosition(0, 0);
+                        mSurfaceControl.setMatrix(1, 0, 0, 1);
+                        break;
+                    case Surface.ROTATION_90:
+                        mSurfaceControl.setPosition(0, displayInfo.logicalHeight);
+                        mSurfaceControl.setMatrix(0, -1, 1, 0);
+                        break;
+                    case Surface.ROTATION_180:
+                        mSurfaceControl.setPosition(displayInfo.logicalWidth, displayInfo.logicalHeight);
+                        mSurfaceControl.setMatrix(-1, 0, 0, -1);
+                        break;
+                    case Surface.ROTATION_270:
+                        mSurfaceControl.setPosition(displayInfo.logicalWidth, 0);
+                        mSurfaceControl.setMatrix(0, 1, -1, 0);
+                        break;
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
new file mode 100644
index 0000000..264e2e9
--- /dev/null
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -0,0 +1,546 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.app.AppOpsManager;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.EventLogTags;
+
+import android.app.ActivityManagerNative;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Slog;
+import android.view.WindowManagerPolicy;
+
+/**
+ * Sends broadcasts about important power state changes.
+ * <p>
+ * This methods of this class may be called by the power manager service while
+ * its lock is being held.  Internally it takes care of sending broadcasts to
+ * notify other components of the system or applications asynchronously.
+ * </p><p>
+ * The notifier is designed to collapse unnecessary broadcasts when it is not
+ * possible for the system to have observed an intermediate state.
+ * </p><p>
+ * For example, if the device wakes up, goes to sleep, wakes up again and goes to
+ * sleep again before the wake up notification is sent, then the system will
+ * be told about only one wake up and sleep.  However, we always notify the
+ * fact that at least one transition occurred.  It is especially important to
+ * tell the system when we go to sleep so that it can lock the keyguard if needed.
+ * </p>
+ */
+final class Notifier {
+    private static final String TAG = "PowerManagerNotifier";
+
+    private static final boolean DEBUG = false;
+
+    private static final int POWER_STATE_UNKNOWN = 0;
+    private static final int POWER_STATE_AWAKE = 1;
+    private static final int POWER_STATE_ASLEEP = 2;
+
+    private static final int MSG_USER_ACTIVITY = 1;
+    private static final int MSG_BROADCAST = 2;
+    private static final int MSG_WIRELESS_CHARGING_STARTED = 3;
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final IBatteryStats mBatteryStats;
+    private final IAppOpsService mAppOps;
+    private final SuspendBlocker mSuspendBlocker;
+    private final ScreenOnBlocker mScreenOnBlocker;
+    private final WindowManagerPolicy mPolicy;
+
+    private final NotifierHandler mHandler;
+    private final Intent mScreenOnIntent;
+    private final Intent mScreenOffIntent;
+
+    // The current power state.
+    private int mActualPowerState;
+    private int mLastGoToSleepReason;
+
+    // True if there is a pending transition that needs to be reported.
+    private boolean mPendingWakeUpBroadcast;
+    private boolean mPendingGoToSleepBroadcast;
+
+    // The currently broadcasted power state.  This reflects what other parts of the
+    // system have observed.
+    private int mBroadcastedPowerState;
+    private boolean mBroadcastInProgress;
+    private long mBroadcastStartTime;
+
+    // True if a user activity message should be sent.
+    private boolean mUserActivityPending;
+
+    // True if the screen on blocker has been acquired.
+    private boolean mScreenOnBlockerAcquired;
+
+    public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
+            IAppOpsService appOps, SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker,
+            WindowManagerPolicy policy) {
+        mContext = context;
+        mBatteryStats = batteryStats;
+        mAppOps = appOps;
+        mSuspendBlocker = suspendBlocker;
+        mScreenOnBlocker = screenOnBlocker;
+        mPolicy = policy;
+
+        mHandler = new NotifierHandler(looper);
+        mScreenOnIntent = new Intent(Intent.ACTION_SCREEN_ON);
+        mScreenOnIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mScreenOffIntent = new Intent(Intent.ACTION_SCREEN_OFF);
+        mScreenOffIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+    }
+
+    /**
+     * Called when a wake lock is acquired.
+     */
+    public void onWakeLockAcquired(int flags, String tag, String packageName,
+            int ownerUid, int ownerPid, WorkSource workSource) {
+        if (DEBUG) {
+            Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag
+                    + "\", packageName=" + packageName
+                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+                    + ", workSource=" + workSource);
+        }
+
+        try {
+            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+            if (workSource != null) {
+                mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType);
+            } else {
+                mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType);
+                // XXX need to deal with disabled operations.
+                mAppOps.startOperation(AppOpsManager.getToken(mAppOps),
+                        AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
+            }
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Called when a wake lock is released.
+     */
+    public void onWakeLockReleased(int flags, String tag, String packageName,
+            int ownerUid, int ownerPid, WorkSource workSource) {
+        if (DEBUG) {
+            Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag
+                    + "\", packageName=" + packageName
+                    + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid
+                    + ", workSource=" + workSource);
+        }
+
+        try {
+            final int monitorType = getBatteryStatsWakeLockMonitorType(flags);
+            if (workSource != null) {
+                mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType);
+            } else {
+                mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType);
+                mAppOps.finishOperation(AppOpsManager.getToken(mAppOps),
+                        AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName);
+            }
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+    }
+
+    private static int getBatteryStatsWakeLockMonitorType(int flags) {
+        switch (flags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+            case PowerManager.PARTIAL_WAKE_LOCK:
+            case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                return BatteryStats.WAKE_TYPE_PARTIAL;
+            default:
+                return BatteryStats.WAKE_TYPE_FULL;
+        }
+    }
+
+    /**
+     * Called when the screen is turned on.
+     */
+    public void onScreenOn() {
+        if (DEBUG) {
+            Slog.d(TAG, "onScreenOn");
+        }
+
+        try {
+            mBatteryStats.noteScreenOn();
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Called when the screen is turned off.
+     */
+    public void onScreenOff() {
+        if (DEBUG) {
+            Slog.d(TAG, "onScreenOff");
+        }
+
+        try {
+            mBatteryStats.noteScreenOff();
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Called when the screen changes brightness.
+     */
+    public void onScreenBrightness(int brightness) {
+        if (DEBUG) {
+            Slog.d(TAG, "onScreenBrightness: brightness=" + brightness);
+        }
+
+        try {
+            mBatteryStats.noteScreenBrightness(brightness);
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+    }
+
+    /**
+     * Called when the device is waking up from sleep and the
+     * display is about to be turned on.
+     */
+    public void onWakeUpStarted() {
+        if (DEBUG) {
+            Slog.d(TAG, "onWakeUpStarted");
+        }
+
+        synchronized (mLock) {
+            if (mActualPowerState != POWER_STATE_AWAKE) {
+                mActualPowerState = POWER_STATE_AWAKE;
+                mPendingWakeUpBroadcast = true;
+                if (!mScreenOnBlockerAcquired) {
+                    mScreenOnBlockerAcquired = true;
+                    mScreenOnBlocker.acquire();
+                }
+                updatePendingBroadcastLocked();
+            }
+        }
+    }
+
+    /**
+     * Called when the device has finished waking up from sleep
+     * and the display has been turned on.
+     */
+    public void onWakeUpFinished() {
+        if (DEBUG) {
+            Slog.d(TAG, "onWakeUpFinished");
+        }
+    }
+
+    /**
+     * Called when the device is going to sleep.
+     */
+    public void onGoToSleepStarted(int reason) {
+        if (DEBUG) {
+            Slog.d(TAG, "onGoToSleepStarted");
+        }
+
+        synchronized (mLock) {
+            mLastGoToSleepReason = reason;
+        }
+    }
+
+    /**
+     * Called when the device has finished going to sleep and the
+     * display has been turned off.
+     *
+     * This is a good time to make transitions that we don't want the user to see,
+     * such as bringing the key guard to focus.  There's no guarantee for this,
+     * however because the user could turn the device on again at any time.
+     * Some things may need to be protected by other mechanisms that defer screen on.
+     */
+    public void onGoToSleepFinished() {
+        if (DEBUG) {
+            Slog.d(TAG, "onGoToSleepFinished");
+        }
+
+        synchronized (mLock) {
+            if (mActualPowerState != POWER_STATE_ASLEEP) {
+                mActualPowerState = POWER_STATE_ASLEEP;
+                mPendingGoToSleepBroadcast = true;
+                if (mUserActivityPending) {
+                    mUserActivityPending = false;
+                    mHandler.removeMessages(MSG_USER_ACTIVITY);
+                }
+                updatePendingBroadcastLocked();
+            }
+        }
+    }
+
+    /**
+     * Called when there has been user activity.
+     */
+    public void onUserActivity(int event, int uid) {
+        if (DEBUG) {
+            Slog.d(TAG, "onUserActivity: event=" + event + ", uid=" + uid);
+        }
+
+        try {
+            mBatteryStats.noteUserActivity(uid, event);
+        } catch (RemoteException ex) {
+            // Ignore
+        }
+
+        synchronized (mLock) {
+            if (!mUserActivityPending) {
+                mUserActivityPending = true;
+                Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY);
+                msg.setAsynchronous(true);
+                mHandler.sendMessage(msg);
+            }
+        }
+    }
+
+    /**
+     * Called when wireless charging has started so as to provide user feedback.
+     */
+    public void onWirelessChargingStarted() {
+        if (DEBUG) {
+            Slog.d(TAG, "onWirelessChargingStarted");
+        }
+
+        mSuspendBlocker.acquire();
+        Message msg = mHandler.obtainMessage(MSG_WIRELESS_CHARGING_STARTED);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+    }
+
+    private void updatePendingBroadcastLocked() {
+        if (!mBroadcastInProgress
+                && mActualPowerState != POWER_STATE_UNKNOWN
+                && (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+                        || mActualPowerState != mBroadcastedPowerState)) {
+            mBroadcastInProgress = true;
+            mSuspendBlocker.acquire();
+            Message msg = mHandler.obtainMessage(MSG_BROADCAST);
+            msg.setAsynchronous(true);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    private void finishPendingBroadcastLocked() {
+        mBroadcastInProgress = false;
+        mSuspendBlocker.release();
+    }
+
+    private void sendUserActivity() {
+        synchronized (mLock) {
+            if (!mUserActivityPending) {
+                return;
+            }
+            mUserActivityPending = false;
+        }
+
+        mPolicy.userActivity();
+    }
+
+    private void sendNextBroadcast() {
+        final int powerState;
+        final int goToSleepReason;
+        synchronized (mLock) {
+            if (mBroadcastedPowerState == POWER_STATE_UNKNOWN) {
+                // Broadcasted power state is unknown.  Send wake up.
+                mPendingWakeUpBroadcast = false;
+                mBroadcastedPowerState = POWER_STATE_AWAKE;
+            } else if (mBroadcastedPowerState == POWER_STATE_AWAKE) {
+                // Broadcasted power state is awake.  Send asleep if needed.
+                if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+                        || mActualPowerState == POWER_STATE_ASLEEP) {
+                    mPendingGoToSleepBroadcast = false;
+                    mBroadcastedPowerState = POWER_STATE_ASLEEP;
+                } else {
+                    finishPendingBroadcastLocked();
+                    return;
+                }
+            } else {
+                // Broadcasted power state is asleep.  Send awake if needed.
+                if (mPendingWakeUpBroadcast || mPendingGoToSleepBroadcast
+                        || mActualPowerState == POWER_STATE_AWAKE) {
+                    mPendingWakeUpBroadcast = false;
+                    mBroadcastedPowerState = POWER_STATE_AWAKE;
+                } else {
+                    finishPendingBroadcastLocked();
+                    return;
+                }
+            }
+
+            mBroadcastStartTime = SystemClock.uptimeMillis();
+            powerState = mBroadcastedPowerState;
+            goToSleepReason = mLastGoToSleepReason;
+        }
+
+        EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, 1);
+
+        if (powerState == POWER_STATE_AWAKE) {
+            sendWakeUpBroadcast();
+        } else {
+            sendGoToSleepBroadcast(goToSleepReason);
+        }
+    }
+
+    private void sendWakeUpBroadcast() {
+        if (DEBUG) {
+            Slog.d(TAG, "Sending wake up broadcast.");
+        }
+
+        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, 0, 0, 0);
+
+        mPolicy.screenTurningOn(mScreenOnListener);
+
+        try {
+            ActivityManagerNative.getDefault().wakingUp();
+        } catch (RemoteException e) {
+            // ignore it
+        }
+
+        if (ActivityManagerNative.isSystemReady()) {
+            mContext.sendOrderedBroadcastAsUser(mScreenOnIntent, UserHandle.ALL, null,
+                    mWakeUpBroadcastDone, mHandler, 0, null, null);
+        } else {
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2, 1);
+            sendNextBroadcast();
+        }
+    }
+
+    private final WindowManagerPolicy.ScreenOnListener mScreenOnListener =
+            new WindowManagerPolicy.ScreenOnListener() {
+        @Override
+        public void onScreenOn() {
+            synchronized (mLock) {
+                if (mScreenOnBlockerAcquired && !mPendingWakeUpBroadcast) {
+                    mScreenOnBlockerAcquired = false;
+                    mScreenOnBlocker.release();
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mWakeUpBroadcastDone = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 1,
+                    SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
+            sendNextBroadcast();
+        }
+    };
+
+    private void sendGoToSleepBroadcast(int reason) {
+        if (DEBUG) {
+            Slog.d(TAG, "Sending go to sleep broadcast.");
+        }
+
+        int why = WindowManagerPolicy.OFF_BECAUSE_OF_USER;
+        switch (reason) {
+            case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
+                why = WindowManagerPolicy.OFF_BECAUSE_OF_ADMIN;
+                break;
+            case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
+                why = WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT;
+                break;
+        }
+
+        EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, why, 0, 0);
+
+        mPolicy.screenTurnedOff(why);
+        try {
+            ActivityManagerNative.getDefault().goingToSleep();
+        } catch (RemoteException e) {
+            // ignore it.
+        }
+
+        if (ActivityManagerNative.isSystemReady()) {
+            mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
+                    mGoToSleepBroadcastDone, mHandler, 0, null, null);
+        } else {
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
+            sendNextBroadcast();
+        }
+    }
+
+    private final BroadcastReceiver mGoToSleepBroadcastDone = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_DONE, 0,
+                    SystemClock.uptimeMillis() - mBroadcastStartTime, 1);
+            sendNextBroadcast();
+        }
+    };
+
+    private void playWirelessChargingStartedSound() {
+        final String soundPath = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.WIRELESS_CHARGING_STARTED_SOUND);
+        if (soundPath != null) {
+            final Uri soundUri = Uri.parse("file://" + soundPath);
+            if (soundUri != null) {
+                final Ringtone sfx = RingtoneManager.getRingtone(mContext, soundUri);
+                if (sfx != null) {
+                    sfx.setStreamType(AudioManager.STREAM_SYSTEM);
+                    sfx.play();
+                }
+            }
+        }
+
+        mSuspendBlocker.release();
+    }
+
+    private final class NotifierHandler extends Handler {
+        public NotifierHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_ACTIVITY:
+                    sendUserActivity();
+                    break;
+
+                case MSG_BROADCAST:
+                    sendNextBroadcast();
+                    break;
+
+                case MSG_WIRELESS_CHARGING_STARTED:
+                    playWirelessChargingStartedSound();
+                    break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
new file mode 100644
index 0000000..13f55e2
--- /dev/null
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -0,0 +1,2739 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.BatteryService;
+import com.android.server.EventLogTags;
+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.display.DisplayManagerService;
+import com.android.server.dreams.DreamManagerService;
+
+import android.Manifest;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.SensorManager;
+import android.hardware.SystemSensorManager;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.SystemService;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.WindowManagerPolicy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import libcore.util.Objects;
+
+/**
+ * The power manager service is responsible for coordinating power management
+ * functions on the device.
+ */
+public final class PowerManagerService extends IPowerManager.Stub
+        implements Watchdog.Monitor {
+    private static final String TAG = "PowerManagerService";
+
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_SPEW = DEBUG && true;
+
+    // Message: Sent when a user activity timeout occurs to update the power state.
+    private static final int MSG_USER_ACTIVITY_TIMEOUT = 1;
+    // Message: Sent when the device enters or exits a napping or dreaming state.
+    private static final int MSG_SANDMAN = 2;
+    // Message: Sent when the screen on blocker is released.
+    private static final int MSG_SCREEN_ON_BLOCKER_RELEASED = 3;
+    // Message: Sent to poll whether the boot animation has terminated.
+    private static final int MSG_CHECK_IF_BOOT_ANIMATION_FINISHED = 4;
+
+    // Dirty bit: mWakeLocks changed
+    private static final int DIRTY_WAKE_LOCKS = 1 << 0;
+    // Dirty bit: mWakefulness changed
+    private static final int DIRTY_WAKEFULNESS = 1 << 1;
+    // Dirty bit: user activity was poked or may have timed out
+    private static final int DIRTY_USER_ACTIVITY = 1 << 2;
+    // Dirty bit: actual display power state was updated asynchronously
+    private static final int DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED = 1 << 3;
+    // Dirty bit: mBootCompleted changed
+    private static final int DIRTY_BOOT_COMPLETED = 1 << 4;
+    // Dirty bit: settings changed
+    private static final int DIRTY_SETTINGS = 1 << 5;
+    // Dirty bit: mIsPowered changed
+    private static final int DIRTY_IS_POWERED = 1 << 6;
+    // Dirty bit: mStayOn changed
+    private static final int DIRTY_STAY_ON = 1 << 7;
+    // Dirty bit: battery state changed
+    private static final int DIRTY_BATTERY_STATE = 1 << 8;
+    // Dirty bit: proximity state changed
+    private static final int DIRTY_PROXIMITY_POSITIVE = 1 << 9;
+    // Dirty bit: screen on blocker state became held or unheld
+    private static final int DIRTY_SCREEN_ON_BLOCKER_RELEASED = 1 << 10;
+    // Dirty bit: dock state changed
+    private static final int DIRTY_DOCK_STATE = 1 << 11;
+
+    // Wakefulness: The device is asleep and can only be awoken by a call to wakeUp().
+    // The screen should be off or in the process of being turned off by the display controller.
+    private static final int WAKEFULNESS_ASLEEP = 0;
+    // Wakefulness: The device is fully awake.  It can be put to sleep by a call to goToSleep().
+    // When the user activity timeout expires, the device may start napping or go to sleep.
+    private static final int WAKEFULNESS_AWAKE = 1;
+    // Wakefulness: The device is napping.  It is deciding whether to dream or go to sleep
+    // but hasn't gotten around to it yet.  It can be awoken by a call to wakeUp(), which
+    // ends the nap. User activity may brighten the screen but does not end the nap.
+    private static final int WAKEFULNESS_NAPPING = 2;
+    // Wakefulness: The device is dreaming.  It can be awoken by a call to wakeUp(),
+    // which ends the dream.  The device goes to sleep when goToSleep() is called, when
+    // the dream ends or when unplugged.
+    // User activity may brighten the screen but does not end the dream.
+    private static final int WAKEFULNESS_DREAMING = 3;
+
+    // Summarizes the state of all active wakelocks.
+    private static final int WAKE_LOCK_CPU = 1 << 0;
+    private static final int WAKE_LOCK_SCREEN_BRIGHT = 1 << 1;
+    private static final int WAKE_LOCK_SCREEN_DIM = 1 << 2;
+    private static final int WAKE_LOCK_BUTTON_BRIGHT = 1 << 3;
+    private static final int WAKE_LOCK_PROXIMITY_SCREEN_OFF = 1 << 4;
+    private static final int WAKE_LOCK_STAY_AWAKE = 1 << 5; // only set if already awake
+
+    // Summarizes the user activity state.
+    private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
+    private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1;
+
+    // Default and minimum screen off timeout in milliseconds.
+    private static final int DEFAULT_SCREEN_OFF_TIMEOUT = 15 * 1000;
+    private static final int MINIMUM_SCREEN_OFF_TIMEOUT = 10 * 1000;
+
+    // The screen dim duration, in milliseconds.
+    // This is subtracted from the end of the screen off timeout so the
+    // minimum screen off timeout should be longer than this.
+    private static final int SCREEN_DIM_DURATION = 7 * 1000;
+
+    // The maximum screen dim time expressed as a ratio relative to the screen
+    // off timeout.  If the screen off timeout is very short then we want the
+    // dim timeout to also be quite short so that most of the time is spent on.
+    // Otherwise the user won't get much screen on time before dimming occurs.
+    private static final float MAXIMUM_SCREEN_DIM_RATIO = 0.2f;
+
+    // The name of the boot animation service in init.rc.
+    private static final String BOOT_ANIMATION_SERVICE = "bootanim";
+
+    // Poll interval in milliseconds for watching boot animation finished.
+    private static final int BOOT_ANIMATION_POLL_INTERVAL = 200;
+
+    // If the battery level drops by this percentage and the user activity timeout
+    // has expired, then assume the device is receiving insufficient current to charge
+    // effectively and terminate the dream.
+    private static final int DREAM_BATTERY_LEVEL_DRAIN_CUTOFF = 5;
+
+    private Context mContext;
+    private LightsManager mLightsManager;
+    private BatteryService mBatteryService;
+    private DisplayManagerService mDisplayManagerService;
+    private IBatteryStats mBatteryStats;
+    private IAppOpsService mAppOps;
+    private HandlerThread mHandlerThread;
+    private PowerManagerHandler mHandler;
+    private WindowManagerPolicy mPolicy;
+    private Notifier mNotifier;
+    private DisplayPowerController mDisplayPowerController;
+    private WirelessChargerDetector mWirelessChargerDetector;
+    private SettingsObserver mSettingsObserver;
+    private DreamManagerService mDreamManager;
+    private Light mAttentionLight;
+
+    private final Object mLock = new Object();
+
+    // A bitfield that indicates what parts of the power state have
+    // changed and need to be recalculated.
+    private int mDirty;
+
+    // Indicates whether the device is awake or asleep or somewhere in between.
+    // This is distinct from the screen power state, which is managed separately.
+    private int mWakefulness;
+
+    // True if MSG_SANDMAN has been scheduled.
+    private boolean mSandmanScheduled;
+
+    // Table of all suspend blockers.
+    // There should only be a few of these.
+    private final ArrayList<SuspendBlocker> mSuspendBlockers = new ArrayList<SuspendBlocker>();
+
+    // Table of all wake locks acquired by applications.
+    private final ArrayList<WakeLock> mWakeLocks = new ArrayList<WakeLock>();
+
+    // A bitfield that summarizes the state of all active wakelocks.
+    private int mWakeLockSummary;
+
+    // If true, instructs the display controller to wait for the proximity sensor to
+    // go negative before turning the screen on.
+    private boolean mRequestWaitForNegativeProximity;
+
+    // Timestamp of the last time the device was awoken or put to sleep.
+    private long mLastWakeTime;
+    private long mLastSleepTime;
+
+    // True if we need to send a wake up or go to sleep finished notification
+    // when the display is ready.
+    private boolean mSendWakeUpFinishedNotificationWhenReady;
+    private boolean mSendGoToSleepFinishedNotificationWhenReady;
+
+    // Timestamp of the last call to user activity.
+    private long mLastUserActivityTime;
+    private long mLastUserActivityTimeNoChangeLights;
+
+    // A bitfield that summarizes the effect of the user activity timer.
+    // A zero value indicates that the user activity timer has expired.
+    private int mUserActivitySummary;
+
+    // The desired display power state.  The actual state may lag behind the
+    // requested because it is updated asynchronously by the display power controller.
+    private final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
+
+    // The time the screen was last turned off, in elapsedRealtime() timebase.
+    private long mLastScreenOffEventElapsedRealTime;
+
+    // True if the display power state has been fully applied, which means the display
+    // is actually on or actually off or whatever was requested.
+    private boolean mDisplayReady;
+
+    // The suspend blocker used to keep the CPU alive when an application has acquired
+    // a wake lock.
+    private final SuspendBlocker mWakeLockSuspendBlocker;
+
+    // True if the wake lock suspend blocker has been acquired.
+    private boolean mHoldingWakeLockSuspendBlocker;
+
+    // The suspend blocker used to keep the CPU alive when the display is on, the
+    // display is getting ready or there is user activity (in which case the display
+    // must be on).
+    private final SuspendBlocker mDisplaySuspendBlocker;
+
+    // True if the display suspend blocker has been acquired.
+    private boolean mHoldingDisplaySuspendBlocker;
+
+    // The screen on blocker used to keep the screen from turning on while the lock
+    // screen is coming up.
+    private final ScreenOnBlockerImpl mScreenOnBlocker;
+
+    // The display blanker used to turn the screen on or off.
+    private final DisplayBlankerImpl mDisplayBlanker;
+
+    // True if systemReady() has been called.
+    private boolean mSystemReady;
+
+    // True if boot completed occurred.  We keep the screen on until this happens.
+    private boolean mBootCompleted;
+
+    // True if the device is plugged into a power source.
+    private boolean mIsPowered;
+
+    // The current plug type, such as BatteryManager.BATTERY_PLUGGED_WIRELESS.
+    private int mPlugType;
+
+    // The current battery level percentage.
+    private int mBatteryLevel;
+
+    // The battery level percentage at the time the dream started.
+    // This is used to terminate a dream and go to sleep if the battery is
+    // draining faster than it is charging and the user activity timeout has expired.
+    private int mBatteryLevelWhenDreamStarted;
+
+    // The current dock state.
+    private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+
+    // True if the device should wake up when plugged or unplugged.
+    private boolean mWakeUpWhenPluggedOrUnpluggedConfig;
+
+    // True if the device should suspend when the screen is off due to proximity.
+    private boolean mSuspendWhenScreenOffDueToProximityConfig;
+
+    // True if dreams are supported on this device.
+    private boolean mDreamsSupportedConfig;
+
+    // Default value for dreams enabled
+    private boolean mDreamsEnabledByDefaultConfig;
+
+    // Default value for dreams activate-on-sleep
+    private boolean mDreamsActivatedOnSleepByDefaultConfig;
+
+    // Default value for dreams activate-on-dock
+    private boolean mDreamsActivatedOnDockByDefaultConfig;
+
+    // True if dreams are enabled by the user.
+    private boolean mDreamsEnabledSetting;
+
+    // True if dreams should be activated on sleep.
+    private boolean mDreamsActivateOnSleepSetting;
+
+    // True if dreams should be activated on dock.
+    private boolean mDreamsActivateOnDockSetting;
+
+    // The screen off timeout setting value in milliseconds.
+    private int mScreenOffTimeoutSetting;
+
+    // The maximum allowable screen off timeout according to the device
+    // administration policy.  Overrides other settings.
+    private int mMaximumScreenOffTimeoutFromDeviceAdmin = Integer.MAX_VALUE;
+
+    // The stay on while plugged in setting.
+    // A bitfield of battery conditions under which to make the screen stay on.
+    private int mStayOnWhilePluggedInSetting;
+
+    // True if the device should stay on.
+    private boolean mStayOn;
+
+    // True if the proximity sensor reads a positive result.
+    private boolean mProximityPositive;
+
+    // Screen brightness setting limits.
+    private int mScreenBrightnessSettingMinimum;
+    private int mScreenBrightnessSettingMaximum;
+    private int mScreenBrightnessSettingDefault;
+
+    // The screen brightness setting, from 0 to 255.
+    // Use -1 if no value has been set.
+    private int mScreenBrightnessSetting;
+
+    // The screen auto-brightness adjustment setting, from -1 to 1.
+    // Use 0 if there is no adjustment.
+    private float mScreenAutoBrightnessAdjustmentSetting;
+
+    // The screen brightness mode.
+    // One of the Settings.System.SCREEN_BRIGHTNESS_MODE_* constants.
+    private int mScreenBrightnessModeSetting;
+
+    // The screen brightness setting override from the window manager
+    // to allow the current foreground activity to override the brightness.
+    // Use -1 to disable.
+    private int mScreenBrightnessOverrideFromWindowManager = -1;
+
+    // The user activity timeout override from the window manager
+    // to allow the current foreground activity to override the user activity timeout.
+    // Use -1 to disable.
+    private long mUserActivityTimeoutOverrideFromWindowManager = -1;
+
+    // The screen brightness setting override from the settings application
+    // to temporarily adjust the brightness until next updated,
+    // Use -1 to disable.
+    private int mTemporaryScreenBrightnessSettingOverride = -1;
+
+    // The screen brightness adjustment setting override from the settings
+    // application to temporarily adjust the auto-brightness adjustment factor
+    // until next updated, in the range -1..1.
+    // Use NaN to disable.
+    private float mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN;
+
+    // Time when we last logged a warning about calling userActivity() without permission.
+    private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE;
+
+    private native void nativeInit();
+
+    private static native void nativeSetPowerState(boolean screenOn, boolean screenBright);
+    private static native void nativeAcquireSuspendBlocker(String name);
+    private static native void nativeReleaseSuspendBlocker(String name);
+    private static native void nativeSetInteractive(boolean enable);
+    private static native void nativeSetAutoSuspend(boolean enable);
+
+    public PowerManagerService() {
+        synchronized (mLock) {
+            mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
+            mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
+            mDisplaySuspendBlocker.acquire();
+            mHoldingDisplaySuspendBlocker = true;
+
+            mScreenOnBlocker = new ScreenOnBlockerImpl();
+            mDisplayBlanker = new DisplayBlankerImpl();
+            mWakefulness = WAKEFULNESS_AWAKE;
+        }
+
+        nativeInit();
+        nativeSetPowerState(true, true);
+    }
+
+    /**
+     * Initialize the power manager.
+     * Must be called before any other functions within the power manager are called.
+     */
+    public void init(Context context, LightsManager ls,
+            BatteryService bs, IBatteryStats bss,
+            IAppOpsService appOps, DisplayManagerService dm) {
+        mContext = context;
+        mLightsManager = ls;
+        mBatteryService = bs;
+        mBatteryStats = bss;
+        mAppOps = appOps;
+        mDisplayManagerService = dm;
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new PowerManagerHandler(mHandlerThread.getLooper());
+
+        Watchdog.getInstance().addMonitor(this);
+        Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName());
+
+        // Forcibly turn the screen on at boot so that it is in a known power state.
+        // We do this in init() rather than in the constructor because setting the
+        // screen state requires a call into surface flinger which then needs to call back
+        // into the activity manager to check permissions.  Unfortunately the
+        // activity manager is not running when the constructor is called, so we
+        // have to defer setting the screen state until this point.
+        mDisplayBlanker.unblankAllDisplays();
+    }
+
+    public void setPolicy(WindowManagerPolicy policy) {
+        synchronized (mLock) {
+            mPolicy = policy;
+        }
+    }
+
+    public void systemReady(TwilightManager twilight, DreamManagerService dreamManager) {
+        synchronized (mLock) {
+            mSystemReady = true;
+            mDreamManager = dreamManager;
+
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting();
+            mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting();
+            mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting();
+
+            SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper());
+
+            // The notifier runs on the system server's main looper so as not to interfere
+            // with the animations and other critical functions of the power manager.
+            mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats,
+                    mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
+                    mScreenOnBlocker, mPolicy);
+
+            // 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, mLightsManager, twilight, sensorManager,
+                    mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker,
+                    mDisplayPowerControllerCallbacks, mHandler);
+
+            mWirelessChargerDetector = new WirelessChargerDetector(sensorManager,
+                    createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"),
+                    mHandler);
+            mSettingsObserver = new SettingsObserver(mHandler);
+            mAttentionLight = mLightsManager.getLight(LightsManager.LIGHT_ID_ATTENTION);
+
+            // Register for broadcasts from other components of the system.
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+            mContext.registerReceiver(new BatteryReceiver(), filter, null, mHandler);
+
+            filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_BOOT_COMPLETED);
+            mContext.registerReceiver(new BootCompletedReceiver(), filter, null, mHandler);
+
+            filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_DREAMING_STARTED);
+            filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+            mContext.registerReceiver(new DreamReceiver(), filter, null, mHandler);
+
+            filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_SWITCHED);
+            mContext.registerReceiver(new UserSwitchedReceiver(), filter, null, mHandler);
+
+            filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_DOCK_EVENT);
+            mContext.registerReceiver(new DockReceiver(), filter, null, mHandler);
+
+            // Register for settings changes.
+            final ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SCREENSAVER_ENABLED),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Secure.getUriFor(
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.System.getUriFor(
+                    Settings.System.SCREEN_OFF_TIMEOUT),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            resolver.registerContentObserver(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS_MODE),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+
+            // Go.
+            readConfigurationLocked();
+            updateSettingsLocked();
+            mDirty |= DIRTY_BATTERY_STATE;
+            updatePowerStateLocked();
+        }
+    }
+
+    private void readConfigurationLocked() {
+        final Resources resources = mContext.getResources();
+
+        mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_unplugTurnsOnScreen);
+        mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity);
+        mDreamsSupportedConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsSupported);
+        mDreamsEnabledByDefaultConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsEnabledByDefault);
+        mDreamsActivatedOnSleepByDefaultConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+        mDreamsActivatedOnDockByDefaultConfig = resources.getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+    }
+
+    private void updateSettingsLocked() {
+        final ContentResolver resolver = mContext.getContentResolver();
+
+        mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver,
+                Settings.Secure.SCREENSAVER_ENABLED,
+                mDreamsEnabledByDefaultConfig ? 1 : 0,
+                UserHandle.USER_CURRENT) != 0);
+        mDreamsActivateOnSleepSetting = (Settings.Secure.getIntForUser(resolver,
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                mDreamsActivatedOnSleepByDefaultConfig ? 1 : 0,
+                UserHandle.USER_CURRENT) != 0);
+        mDreamsActivateOnDockSetting = (Settings.Secure.getIntForUser(resolver,
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                mDreamsActivatedOnDockByDefaultConfig ? 1 : 0,
+                UserHandle.USER_CURRENT) != 0);
+        mScreenOffTimeoutSetting = Settings.System.getIntForUser(resolver,
+                Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT,
+                UserHandle.USER_CURRENT);
+        mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver,
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, BatteryManager.BATTERY_PLUGGED_AC);
+
+        final int oldScreenBrightnessSetting = mScreenBrightnessSetting;
+        mScreenBrightnessSetting = Settings.System.getIntForUser(resolver,
+                Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault,
+                UserHandle.USER_CURRENT);
+        if (oldScreenBrightnessSetting != mScreenBrightnessSetting) {
+            mTemporaryScreenBrightnessSettingOverride = -1;
+        }
+
+        final float oldScreenAutoBrightnessAdjustmentSetting =
+                mScreenAutoBrightnessAdjustmentSetting;
+        mScreenAutoBrightnessAdjustmentSetting = Settings.System.getFloatForUser(resolver,
+                Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.0f,
+                UserHandle.USER_CURRENT);
+        if (oldScreenAutoBrightnessAdjustmentSetting != mScreenAutoBrightnessAdjustmentSetting) {
+            mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = Float.NaN;
+        }
+
+        mScreenBrightnessModeSetting = Settings.System.getIntForUser(resolver,
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT);
+
+        mDirty |= DIRTY_SETTINGS;
+    }
+
+    private void handleSettingsChangedLocked() {
+        updateSettingsLocked();
+        updatePowerStateLocked();
+    }
+
+    @Override // Binder call
+    public void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName,
+            int uid) {
+        acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid));
+    }
+
+    @Override // Binder call
+    public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
+            WorkSource ws) {
+        if (lock == null) {
+            throw new IllegalArgumentException("lock must not be null");
+        }
+        if (packageName == null) {
+            throw new IllegalArgumentException("packageName must not be null");
+        }
+        PowerManager.validateWakeLockParameters(flags, tag);
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        if (ws != null && ws.size() != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.UPDATE_DEVICE_STATS, null);
+        } else {
+            ws = null;
+        }
+
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
+            WorkSource ws, int uid, int pid) {
+        synchronized (mLock) {
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "acquireWakeLockInternal: lock=" + Objects.hashCode(lock)
+                        + ", flags=0x" + Integer.toHexString(flags)
+                        + ", tag=\"" + tag + "\", ws=" + ws + ", uid=" + uid + ", pid=" + pid);
+            }
+
+            WakeLock wakeLock;
+            int index = findWakeLockIndexLocked(lock);
+            if (index >= 0) {
+                wakeLock = mWakeLocks.get(index);
+                if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
+                    // Update existing wake lock.  This shouldn't happen but is harmless.
+                    notifyWakeLockReleasedLocked(wakeLock);
+                    wakeLock.updateProperties(flags, tag, packageName, ws, uid, pid);
+                    notifyWakeLockAcquiredLocked(wakeLock);
+                }
+            } else {
+                wakeLock = new WakeLock(lock, flags, tag, packageName, ws, uid, pid);
+                try {
+                    lock.linkToDeath(wakeLock, 0);
+                } catch (RemoteException ex) {
+                    throw new IllegalArgumentException("Wake lock is already dead.");
+                }
+                notifyWakeLockAcquiredLocked(wakeLock);
+                mWakeLocks.add(wakeLock);
+            }
+
+            applyWakeLockFlagsOnAcquireLocked(wakeLock);
+            mDirty |= DIRTY_WAKE_LOCKS;
+            updatePowerStateLocked();
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private static boolean isScreenLock(final WakeLock wakeLock) {
+        switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+            case PowerManager.FULL_WAKE_LOCK:
+            case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+            case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                return true;
+        }
+        return false;
+    }
+
+    private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock) {
+        if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
+                && isScreenLock(wakeLock)) {
+            wakeUpNoUpdateLocked(SystemClock.uptimeMillis());
+        }
+    }
+
+    @Override // Binder call
+    public void releaseWakeLock(IBinder lock, int flags) {
+        if (lock == null) {
+            throw new IllegalArgumentException("lock must not be null");
+        }
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            releaseWakeLockInternal(lock, flags);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void releaseWakeLockInternal(IBinder lock, int flags) {
+        synchronized (mLock) {
+            int index = findWakeLockIndexLocked(lock);
+            if (index < 0) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
+                            + " [not found], flags=0x" + Integer.toHexString(flags));
+                }
+                return;
+            }
+
+            WakeLock wakeLock = mWakeLocks.get(index);
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "releaseWakeLockInternal: lock=" + Objects.hashCode(lock)
+                        + " [" + wakeLock.mTag + "], flags=0x" + Integer.toHexString(flags));
+            }
+
+            mWakeLocks.remove(index);
+            notifyWakeLockReleasedLocked(wakeLock);
+            wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+
+            if ((flags & PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE) != 0) {
+                mRequestWaitForNegativeProximity = true;
+            }
+
+            applyWakeLockFlagsOnReleaseLocked(wakeLock);
+            mDirty |= DIRTY_WAKE_LOCKS;
+            updatePowerStateLocked();
+        }
+    }
+
+    private void handleWakeLockDeath(WakeLock wakeLock) {
+        synchronized (mLock) {
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "handleWakeLockDeath: lock=" + Objects.hashCode(wakeLock.mLock)
+                        + " [" + wakeLock.mTag + "]");
+            }
+
+            int index = mWakeLocks.indexOf(wakeLock);
+            if (index < 0) {
+                return;
+            }
+
+            mWakeLocks.remove(index);
+            notifyWakeLockReleasedLocked(wakeLock);
+
+            applyWakeLockFlagsOnReleaseLocked(wakeLock);
+            mDirty |= DIRTY_WAKE_LOCKS;
+            updatePowerStateLocked();
+        }
+    }
+
+    private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
+        if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
+                && isScreenLock(wakeLock)) {
+            userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+                    PowerManager.USER_ACTIVITY_EVENT_OTHER,
+                    PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
+                    wakeLock.mOwnerUid);
+        }
+    }
+
+    @Override // Binder call
+    public void updateWakeLockUids(IBinder lock, int[] uids) {
+        WorkSource ws = null;
+
+        if (uids != null) {
+            ws = new WorkSource();
+            // XXX should WorkSource have a way to set uids as an int[] instead of adding them
+            // one at a time?
+            for (int i = 0; i < uids.length; i++) {
+                ws.add(uids[i]);
+            }
+        }
+        updateWakeLockWorkSource(lock, ws);
+    }
+
+    @Override // Binder call
+    public void updateWakeLockWorkSource(IBinder lock, WorkSource ws) {
+        if (lock == null) {
+            throw new IllegalArgumentException("lock must not be null");
+        }
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        if (ws != null && ws.size() != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.UPDATE_DEVICE_STATS, null);
+        } else {
+            ws = null;
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            updateWakeLockWorkSourceInternal(lock, ws);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void updateWakeLockWorkSourceInternal(IBinder lock, WorkSource ws) {
+        synchronized (mLock) {
+            int index = findWakeLockIndexLocked(lock);
+            if (index < 0) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock)
+                            + " [not found], ws=" + ws);
+                }
+                throw new IllegalArgumentException("Wake lock not active");
+            }
+
+            WakeLock wakeLock = mWakeLocks.get(index);
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateWakeLockWorkSourceInternal: lock=" + Objects.hashCode(lock)
+                        + " [" + wakeLock.mTag + "], ws=" + ws);
+            }
+
+            if (!wakeLock.hasSameWorkSource(ws)) {
+                notifyWakeLockReleasedLocked(wakeLock);
+                wakeLock.updateWorkSource(ws);
+                notifyWakeLockAcquiredLocked(wakeLock);
+            }
+        }
+    }
+
+    private int findWakeLockIndexLocked(IBinder lock) {
+        final int count = mWakeLocks.size();
+        for (int i = 0; i < count; i++) {
+            if (mWakeLocks.get(i).mLock == lock) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) {
+        if (mSystemReady) {
+            wakeLock.mNotifiedAcquired = true;
+            mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
+                    wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource);
+        }
+    }
+
+    private void notifyWakeLockReleasedLocked(WakeLock wakeLock) {
+        if (mSystemReady && wakeLock.mNotifiedAcquired) {
+            wakeLock.mNotifiedAcquired = false;
+            mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName,
+                    wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource);
+        }
+    }
+
+    @Override // Binder call
+    public boolean isWakeLockLevelSupported(int level) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return isWakeLockLevelSupportedInternal(level);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean isWakeLockLevelSupportedInternal(int level) {
+        synchronized (mLock) {
+            switch (level) {
+                case PowerManager.PARTIAL_WAKE_LOCK:
+                case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                case PowerManager.FULL_WAKE_LOCK:
+                    return true;
+
+                case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                    return mSystemReady && mDisplayPowerController.isProximitySensorAvailable();
+
+                default:
+                    return false;
+            }
+        }
+    }
+
+    @Override // Binder call
+    public void userActivity(long eventTime, int event, int flags) {
+        final long now = SystemClock.uptimeMillis();
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER)
+                != PackageManager.PERMISSION_GRANTED) {
+            // Once upon a time applications could call userActivity().
+            // Now we require the DEVICE_POWER permission.  Log a warning and ignore the
+            // request instead of throwing a SecurityException so we don't break old apps.
+            synchronized (mLock) {
+                if (now >= mLastWarningAboutUserActivityPermission + (5 * 60 * 1000)) {
+                    mLastWarningAboutUserActivityPermission = now;
+                    Slog.w(TAG, "Ignoring call to PowerManager.userActivity() because the "
+                            + "caller does not have DEVICE_POWER permission.  "
+                            + "Please fix your app!  "
+                            + " pid=" + Binder.getCallingPid()
+                            + " uid=" + Binder.getCallingUid());
+                }
+            }
+            return;
+        }
+
+        if (eventTime > SystemClock.uptimeMillis()) {
+            throw new IllegalArgumentException("event time must not be in the future");
+        }
+
+        final int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            userActivityInternal(eventTime, event, flags, uid);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // Called from native code.
+    private void userActivityFromNative(long eventTime, int event, int flags) {
+        userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID);
+    }
+
+    private void userActivityInternal(long eventTime, int event, int flags, int uid) {
+        synchronized (mLock) {
+            if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "userActivityNoUpdateLocked: eventTime=" + eventTime
+                    + ", event=" + event + ", flags=0x" + Integer.toHexString(flags)
+                    + ", uid=" + uid);
+        }
+
+        if (eventTime < mLastSleepTime || eventTime < mLastWakeTime
+                || mWakefulness == WAKEFULNESS_ASLEEP || !mBootCompleted || !mSystemReady) {
+            return false;
+        }
+
+        mNotifier.onUserActivity(event, uid);
+
+        if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
+            if (eventTime > mLastUserActivityTimeNoChangeLights
+                    && eventTime > mLastUserActivityTime) {
+                mLastUserActivityTimeNoChangeLights = eventTime;
+                mDirty |= DIRTY_USER_ACTIVITY;
+                return true;
+            }
+        } else {
+            if (eventTime > mLastUserActivityTime) {
+                mLastUserActivityTime = eventTime;
+                mDirty |= DIRTY_USER_ACTIVITY;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override // Binder call
+    public void wakeUp(long eventTime) {
+        if (eventTime > SystemClock.uptimeMillis()) {
+            throw new IllegalArgumentException("event time must not be in the future");
+        }
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            wakeUpInternal(eventTime);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // Called from native code.
+    private void wakeUpFromNative(long eventTime) {
+        wakeUpInternal(eventTime);
+    }
+
+    private void wakeUpInternal(long eventTime) {
+        synchronized (mLock) {
+            if (wakeUpNoUpdateLocked(eventTime)) {
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    private boolean wakeUpNoUpdateLocked(long eventTime) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "wakeUpNoUpdateLocked: eventTime=" + eventTime);
+        }
+
+        if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE
+                || !mBootCompleted || !mSystemReady) {
+            return false;
+        }
+
+        switch (mWakefulness) {
+            case WAKEFULNESS_ASLEEP:
+                Slog.i(TAG, "Waking up from sleep...");
+                sendPendingNotificationsLocked();
+                mNotifier.onWakeUpStarted();
+                mSendWakeUpFinishedNotificationWhenReady = true;
+                break;
+            case WAKEFULNESS_DREAMING:
+                Slog.i(TAG, "Waking up from dream...");
+                break;
+            case WAKEFULNESS_NAPPING:
+                Slog.i(TAG, "Waking up from nap...");
+                break;
+        }
+
+        mLastWakeTime = eventTime;
+        mWakefulness = WAKEFULNESS_AWAKE;
+        mDirty |= DIRTY_WAKEFULNESS;
+
+        userActivityNoUpdateLocked(
+                eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+        return true;
+    }
+
+    @Override // Binder call
+    public void goToSleep(long eventTime, int reason) {
+        if (eventTime > SystemClock.uptimeMillis()) {
+            throw new IllegalArgumentException("event time must not be in the future");
+        }
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            goToSleepInternal(eventTime, reason);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // Called from native code.
+    private void goToSleepFromNative(long eventTime, int reason) {
+        goToSleepInternal(eventTime, reason);
+    }
+
+    private void goToSleepInternal(long eventTime, int reason) {
+        synchronized (mLock) {
+            if (goToSleepNoUpdateLocked(eventTime, reason)) {
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    private boolean goToSleepNoUpdateLocked(long eventTime, int reason) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason);
+        }
+
+        if (eventTime < mLastWakeTime || mWakefulness == WAKEFULNESS_ASLEEP
+                || !mBootCompleted || !mSystemReady) {
+            return false;
+        }
+
+        switch (reason) {
+            case PowerManager.GO_TO_SLEEP_REASON_DEVICE_ADMIN:
+                Slog.i(TAG, "Going to sleep due to device administration policy...");
+                break;
+            case PowerManager.GO_TO_SLEEP_REASON_TIMEOUT:
+                Slog.i(TAG, "Going to sleep due to screen timeout...");
+                break;
+            default:
+                Slog.i(TAG, "Going to sleep by user request...");
+                reason = PowerManager.GO_TO_SLEEP_REASON_USER;
+                break;
+        }
+
+        sendPendingNotificationsLocked();
+        mNotifier.onGoToSleepStarted(reason);
+        mSendGoToSleepFinishedNotificationWhenReady = true;
+
+        mLastSleepTime = eventTime;
+        mDirty |= DIRTY_WAKEFULNESS;
+        mWakefulness = WAKEFULNESS_ASLEEP;
+
+        // Report the number of wake locks that will be cleared by going to sleep.
+        int numWakeLocksCleared = 0;
+        final int numWakeLocks = mWakeLocks.size();
+        for (int i = 0; i < numWakeLocks; i++) {
+            final WakeLock wakeLock = mWakeLocks.get(i);
+            switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+                case PowerManager.FULL_WAKE_LOCK:
+                case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                    numWakeLocksCleared += 1;
+                    break;
+            }
+        }
+        EventLog.writeEvent(EventLogTags.POWER_SLEEP_REQUESTED, numWakeLocksCleared);
+        return true;
+    }
+
+    @Override // Binder call
+    public void nap(long eventTime) {
+        if (eventTime > SystemClock.uptimeMillis()) {
+            throw new IllegalArgumentException("event time must not be in the future");
+        }
+
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            napInternal(eventTime);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void napInternal(long eventTime) {
+        synchronized (mLock) {
+            if (napNoUpdateLocked(eventTime)) {
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    private boolean napNoUpdateLocked(long eventTime) {
+        if (DEBUG_SPEW) {
+            Slog.d(TAG, "napNoUpdateLocked: eventTime=" + eventTime);
+        }
+
+        if (eventTime < mLastWakeTime || mWakefulness != WAKEFULNESS_AWAKE
+                || !mBootCompleted || !mSystemReady) {
+            return false;
+        }
+
+        Slog.i(TAG, "Nap time...");
+
+        mDirty |= DIRTY_WAKEFULNESS;
+        mWakefulness = WAKEFULNESS_NAPPING;
+        return true;
+    }
+
+    /**
+     * Updates the global power state based on dirty bits recorded in mDirty.
+     *
+     * This is the main function that performs power state transitions.
+     * We centralize them here so that we can recompute the power state completely
+     * each time something important changes, and ensure that we do it the same
+     * way each time.  The point is to gather all of the transition logic here.
+     */
+    private void updatePowerStateLocked() {
+        if (!mSystemReady || mDirty == 0) {
+            return;
+        }
+
+        // Phase 0: Basic state updates.
+        updateIsPoweredLocked(mDirty);
+        updateStayOnLocked(mDirty);
+
+        // Phase 1: Update wakefulness.
+        // Loop because the wake lock and user activity computations are influenced
+        // by changes in wakefulness.
+        final long now = SystemClock.uptimeMillis();
+        int dirtyPhase2 = 0;
+        for (;;) {
+            int dirtyPhase1 = mDirty;
+            dirtyPhase2 |= dirtyPhase1;
+            mDirty = 0;
+
+            updateWakeLockSummaryLocked(dirtyPhase1);
+            updateUserActivitySummaryLocked(now, dirtyPhase1);
+            if (!updateWakefulnessLocked(dirtyPhase1)) {
+                break;
+            }
+        }
+
+        // Phase 2: Update dreams and display power state.
+        updateDreamLocked(dirtyPhase2);
+        updateDisplayPowerStateLocked(dirtyPhase2);
+
+        // Phase 3: Send notifications, if needed.
+        if (mDisplayReady) {
+            sendPendingNotificationsLocked();
+        }
+
+        // Phase 4: Update suspend blocker.
+        // Because we might release the last suspend blocker here, we need to make sure
+        // we finished everything else first!
+        updateSuspendBlockerLocked();
+    }
+
+    private void sendPendingNotificationsLocked() {
+        if (mSendWakeUpFinishedNotificationWhenReady) {
+            mSendWakeUpFinishedNotificationWhenReady = false;
+            mNotifier.onWakeUpFinished();
+        }
+        if (mSendGoToSleepFinishedNotificationWhenReady) {
+            mSendGoToSleepFinishedNotificationWhenReady = false;
+            mNotifier.onGoToSleepFinished();
+        }
+    }
+
+    /**
+     * Updates the value of mIsPowered.
+     * Sets DIRTY_IS_POWERED if a change occurred.
+     */
+    private void updateIsPoweredLocked(int dirty) {
+        if ((dirty & DIRTY_BATTERY_STATE) != 0) {
+            final boolean wasPowered = mIsPowered;
+            final int oldPlugType = mPlugType;
+            mIsPowered = mBatteryService.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
+            mPlugType = mBatteryService.getPlugType();
+            mBatteryLevel = mBatteryService.getBatteryLevel();
+
+            if (DEBUG) {
+                Slog.d(TAG, "updateIsPoweredLocked: wasPowered=" + wasPowered
+                        + ", mIsPowered=" + mIsPowered
+                        + ", oldPlugType=" + oldPlugType
+                        + ", mPlugType=" + mPlugType
+                        + ", mBatteryLevel=" + mBatteryLevel);
+            }
+
+            if (wasPowered != mIsPowered || oldPlugType != mPlugType) {
+                mDirty |= DIRTY_IS_POWERED;
+
+                // Update wireless dock detection state.
+                final boolean dockedOnWirelessCharger = mWirelessChargerDetector.update(
+                        mIsPowered, mPlugType, mBatteryLevel);
+
+                // Treat plugging and unplugging the devices as a user activity.
+                // Users find it disconcerting when they plug or unplug the device
+                // and it shuts off right away.
+                // Some devices also wake the device when plugged or unplugged because
+                // they don't have a charging LED.
+                final long now = SystemClock.uptimeMillis();
+                if (shouldWakeUpWhenPluggedOrUnpluggedLocked(wasPowered, oldPlugType,
+                        dockedOnWirelessCharger)) {
+                    wakeUpNoUpdateLocked(now);
+                }
+                userActivityNoUpdateLocked(
+                        now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+
+                // Tell the notifier whether wireless charging has started so that
+                // it can provide feedback to the user.
+                if (dockedOnWirelessCharger) {
+                    mNotifier.onWirelessChargingStarted();
+                }
+            }
+        }
+    }
+
+    private boolean shouldWakeUpWhenPluggedOrUnpluggedLocked(
+            boolean wasPowered, int oldPlugType, boolean dockedOnWirelessCharger) {
+        // Don't wake when powered unless configured to do so.
+        if (!mWakeUpWhenPluggedOrUnpluggedConfig) {
+            return false;
+        }
+
+        // Don't wake when undocked from wireless charger.
+        // See WirelessChargerDetector for justification.
+        if (wasPowered && !mIsPowered
+                && oldPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+            return false;
+        }
+
+        // Don't wake when docked on wireless charger unless we are certain of it.
+        // See WirelessChargerDetector for justification.
+        if (!wasPowered && mIsPowered
+                && mPlugType == BatteryManager.BATTERY_PLUGGED_WIRELESS
+                && !dockedOnWirelessCharger) {
+            return false;
+        }
+
+        // If already dreaming and becoming powered, then don't wake.
+        if (mIsPowered && (mWakefulness == WAKEFULNESS_NAPPING
+                || mWakefulness == WAKEFULNESS_DREAMING)) {
+            return false;
+        }
+
+        // Otherwise wake up!
+        return true;
+    }
+
+    /**
+     * Updates the value of mStayOn.
+     * Sets DIRTY_STAY_ON if a change occurred.
+     */
+    private void updateStayOnLocked(int dirty) {
+        if ((dirty & (DIRTY_BATTERY_STATE | DIRTY_SETTINGS)) != 0) {
+            final boolean wasStayOn = mStayOn;
+            if (mStayOnWhilePluggedInSetting != 0
+                    && !isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
+                mStayOn = mBatteryService.isPowered(mStayOnWhilePluggedInSetting);
+            } else {
+                mStayOn = false;
+            }
+
+            if (mStayOn != wasStayOn) {
+                mDirty |= DIRTY_STAY_ON;
+            }
+        }
+    }
+
+    /**
+     * Updates the value of mWakeLockSummary to summarize the state of all active wake locks.
+     * Note that most wake-locks are ignored when the system is asleep.
+     *
+     * This function must have no other side-effects.
+     */
+    @SuppressWarnings("deprecation")
+    private void updateWakeLockSummaryLocked(int dirty) {
+        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) {
+            mWakeLockSummary = 0;
+
+            final int numWakeLocks = mWakeLocks.size();
+            for (int i = 0; i < numWakeLocks; i++) {
+                final WakeLock wakeLock = mWakeLocks.get(i);
+                switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+                    case PowerManager.PARTIAL_WAKE_LOCK:
+                        mWakeLockSummary |= WAKE_LOCK_CPU;
+                        break;
+                    case PowerManager.FULL_WAKE_LOCK:
+                        if (mWakefulness != WAKEFULNESS_ASLEEP) {
+                            mWakeLockSummary |= WAKE_LOCK_CPU
+                                    | WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
+                            if (mWakefulness == WAKEFULNESS_AWAKE) {
+                                mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE;
+                            }
+                        }
+                        break;
+                    case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                        if (mWakefulness != WAKEFULNESS_ASLEEP) {
+                            mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_BRIGHT;
+                            if (mWakefulness == WAKEFULNESS_AWAKE) {
+                                mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE;
+                            }
+                        }
+                        break;
+                    case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                        if (mWakefulness != WAKEFULNESS_ASLEEP) {
+                            mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_SCREEN_DIM;
+                            if (mWakefulness == WAKEFULNESS_AWAKE) {
+                                mWakeLockSummary |= WAKE_LOCK_STAY_AWAKE;
+                            }
+                        }
+                        break;
+                    case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                        if (mWakefulness != WAKEFULNESS_ASLEEP) {
+                            mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF;
+                        }
+                        break;
+                }
+            }
+
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateWakeLockSummaryLocked: mWakefulness="
+                        + wakefulnessToString(mWakefulness)
+                        + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
+            }
+        }
+    }
+
+    /**
+     * Updates the value of mUserActivitySummary to summarize the user requested
+     * state of the system such as whether the screen should be bright or dim.
+     * Note that user activity is ignored when the system is asleep.
+     *
+     * This function must have no other side-effects.
+     */
+    private void updateUserActivitySummaryLocked(long now, int dirty) {
+        // Update the status of the user activity timeout timer.
+        if ((dirty & (DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
+            mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
+
+            long nextTimeout = 0;
+            if (mWakefulness != WAKEFULNESS_ASLEEP) {
+                final int screenOffTimeout = getScreenOffTimeoutLocked();
+                final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+
+                mUserActivitySummary = 0;
+                if (mLastUserActivityTime >= mLastWakeTime) {
+                    nextTimeout = mLastUserActivityTime
+                            + screenOffTimeout - screenDimDuration;
+                    if (now < nextTimeout) {
+                        mUserActivitySummary |= USER_ACTIVITY_SCREEN_BRIGHT;
+                    } else {
+                        nextTimeout = mLastUserActivityTime + screenOffTimeout;
+                        if (now < nextTimeout) {
+                            mUserActivitySummary |= USER_ACTIVITY_SCREEN_DIM;
+                        }
+                    }
+                }
+                if (mUserActivitySummary == 0
+                        && mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
+                    nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
+                    if (now < nextTimeout
+                            && mDisplayPowerRequest.screenState
+                                    != DisplayPowerRequest.SCREEN_STATE_OFF) {
+                        mUserActivitySummary = mDisplayPowerRequest.screenState
+                                == DisplayPowerRequest.SCREEN_STATE_BRIGHT ?
+                                USER_ACTIVITY_SCREEN_BRIGHT : USER_ACTIVITY_SCREEN_DIM;
+                    }
+                }
+                if (mUserActivitySummary != 0) {
+                    Message msg = mHandler.obtainMessage(MSG_USER_ACTIVITY_TIMEOUT);
+                    msg.setAsynchronous(true);
+                    mHandler.sendMessageAtTime(msg, nextTimeout);
+                }
+            } else {
+                mUserActivitySummary = 0;
+            }
+
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateUserActivitySummaryLocked: mWakefulness="
+                        + wakefulnessToString(mWakefulness)
+                        + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
+                        + ", nextTimeout=" + TimeUtils.formatUptime(nextTimeout));
+            }
+        }
+    }
+
+    /**
+     * Called when a user activity timeout has occurred.
+     * Simply indicates that something about user activity has changed so that the new
+     * state can be recomputed when the power state is updated.
+     *
+     * This function must have no other side-effects besides setting the dirty
+     * bit and calling update power state.  Wakefulness transitions are handled elsewhere.
+     */
+    private void handleUserActivityTimeout() { // runs on handler thread
+        synchronized (mLock) {
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "handleUserActivityTimeout");
+            }
+
+            mDirty |= DIRTY_USER_ACTIVITY;
+            updatePowerStateLocked();
+        }
+    }
+
+    private int getScreenOffTimeoutLocked() {
+        int timeout = mScreenOffTimeoutSetting;
+        if (isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked()) {
+            timeout = Math.min(timeout, mMaximumScreenOffTimeoutFromDeviceAdmin);
+        }
+        if (mUserActivityTimeoutOverrideFromWindowManager >= 0) {
+            timeout = (int)Math.min(timeout, mUserActivityTimeoutOverrideFromWindowManager);
+        }
+        return Math.max(timeout, MINIMUM_SCREEN_OFF_TIMEOUT);
+    }
+
+    private int getScreenDimDurationLocked(int screenOffTimeout) {
+        return Math.min(SCREEN_DIM_DURATION,
+                (int)(screenOffTimeout * MAXIMUM_SCREEN_DIM_RATIO));
+    }
+
+    /**
+     * Updates the wakefulness of the device.
+     *
+     * This is the function that decides whether the device should start napping
+     * based on the current wake locks and user activity state.  It may modify mDirty
+     * if the wakefulness changes.
+     *
+     * Returns true if the wakefulness changed and we need to restart power state calculation.
+     */
+    private boolean updateWakefulnessLocked(int dirty) {
+        boolean changed = false;
+        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
+                | DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
+                | DIRTY_DOCK_STATE)) != 0) {
+            if (mWakefulness == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
+                if (DEBUG_SPEW) {
+                    Slog.d(TAG, "updateWakefulnessLocked: Bed time...");
+                }
+                final long time = SystemClock.uptimeMillis();
+                if (shouldNapAtBedTimeLocked()) {
+                    changed = napNoUpdateLocked(time);
+                } else {
+                    changed = goToSleepNoUpdateLocked(time,
+                            PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+                }
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Returns true if the device should automatically nap and start dreaming when the user
+     * activity timeout has expired and it's bedtime.
+     */
+    private boolean shouldNapAtBedTimeLocked() {
+        return mDreamsActivateOnSleepSetting
+                || (mDreamsActivateOnDockSetting
+                        && mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED);
+    }
+
+    /**
+     * Returns true if the device should go to sleep now.
+     * Also used when exiting a dream to determine whether we should go back
+     * to being fully awake or else go to sleep for good.
+     */
+    private boolean isItBedTimeYetLocked() {
+        return mBootCompleted && !isBeingKeptAwakeLocked();
+    }
+
+    /**
+     * Returns true if the device is being kept awake by a wake lock, user activity
+     * or the stay on while powered setting.  We also keep the phone awake when
+     * the proximity sensor returns a positive result so that the device does not
+     * lock while in a phone call.  This function only controls whether the device
+     * will go to sleep or dream which is independent of whether it will be allowed
+     * to suspend.
+     */
+    private boolean isBeingKeptAwakeLocked() {
+        return mStayOn
+                || mProximityPositive
+                || (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0
+                || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
+                        | USER_ACTIVITY_SCREEN_DIM)) != 0;
+    }
+
+    /**
+     * Determines whether to post a message to the sandman to update the dream state.
+     */
+    private void updateDreamLocked(int dirty) {
+        if ((dirty & (DIRTY_WAKEFULNESS
+                | DIRTY_USER_ACTIVITY
+                | DIRTY_WAKE_LOCKS
+                | DIRTY_BOOT_COMPLETED
+                | DIRTY_SETTINGS
+                | DIRTY_IS_POWERED
+                | DIRTY_STAY_ON
+                | DIRTY_PROXIMITY_POSITIVE
+                | DIRTY_BATTERY_STATE)) != 0) {
+            scheduleSandmanLocked();
+        }
+    }
+
+    private void scheduleSandmanLocked() {
+        if (!mSandmanScheduled) {
+            mSandmanScheduled = true;
+            Message msg = mHandler.obtainMessage(MSG_SANDMAN);
+            msg.setAsynchronous(true);
+            mHandler.sendMessage(msg);
+        }
+    }
+
+    /**
+     * Called when the device enters or exits a napping or dreaming state.
+     *
+     * We do this asynchronously because we must call out of the power manager to start
+     * the dream and we don't want to hold our lock while doing so.  There is a risk that
+     * the device will wake or go to sleep in the meantime so we have to handle that case.
+     */
+    private void handleSandman() { // runs on handler thread
+        // Handle preconditions.
+        boolean startDreaming = false;
+        synchronized (mLock) {
+            mSandmanScheduled = false;
+            boolean canDream = canDreamLocked();
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "handleSandman: canDream=" + canDream
+                        + ", mWakefulness=" + wakefulnessToString(mWakefulness));
+            }
+
+            if (canDream && mWakefulness == WAKEFULNESS_NAPPING) {
+                startDreaming = true;
+            }
+        }
+
+        // Start dreaming if needed.
+        // We only control the dream on the handler thread, so we don't need to worry about
+        // concurrent attempts to start or stop the dream.
+        boolean isDreaming = false;
+        if (mDreamManager != null) {
+            if (startDreaming) {
+                mDreamManager.startDream();
+            }
+            isDreaming = mDreamManager.isDreaming();
+        }
+
+        // Update dream state.
+        // We might need to stop the dream again if the preconditions changed.
+        boolean continueDreaming = false;
+        synchronized (mLock) {
+            if (isDreaming && canDreamLocked()) {
+                if (mWakefulness == WAKEFULNESS_NAPPING) {
+                    mWakefulness = WAKEFULNESS_DREAMING;
+                    mDirty |= DIRTY_WAKEFULNESS;
+                    mBatteryLevelWhenDreamStarted = mBatteryLevel;
+                    updatePowerStateLocked();
+                    continueDreaming = true;
+                } else if (mWakefulness == WAKEFULNESS_DREAMING) {
+                    if (!isBeingKeptAwakeLocked()
+                            && mBatteryLevel < mBatteryLevelWhenDreamStarted
+                                    - DREAM_BATTERY_LEVEL_DRAIN_CUTOFF) {
+                        // If the user activity timeout expired and the battery appears
+                        // to be draining faster than it is charging then stop dreaming
+                        // and go to sleep.
+                        Slog.i(TAG, "Stopping dream because the battery appears to "
+                                + "be draining faster than it is charging.  "
+                                + "Battery level when dream started: "
+                                + mBatteryLevelWhenDreamStarted + "%.  "
+                                + "Battery level now: " + mBatteryLevel + "%.");
+                    } else {
+                        continueDreaming = true;
+                    }
+                }
+            }
+            if (!continueDreaming) {
+                handleDreamFinishedLocked();
+            }
+        }
+
+        // Stop dreaming if needed.
+        // It's possible that something else changed to make us need to start the dream again.
+        // If so, then the power manager will have posted another message to the handler
+        // to take care of it later.
+        if (mDreamManager != null) {
+            if (!continueDreaming) {
+                mDreamManager.stopDream();
+            }
+        }
+    }
+
+    /**
+     * Returns true if the device is allowed to dream in its current state
+     * assuming that it is currently napping or dreaming.
+     */
+    private boolean canDreamLocked() {
+        return mDreamsSupportedConfig
+                && mDreamsEnabledSetting
+                && mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF
+                && mBootCompleted
+                && (mIsPowered || isBeingKeptAwakeLocked());
+    }
+
+    /**
+     * Called when a dream is ending to figure out what to do next.
+     */
+    private void handleDreamFinishedLocked() {
+        if (mWakefulness == WAKEFULNESS_NAPPING
+                || mWakefulness == WAKEFULNESS_DREAMING) {
+            if (isItBedTimeYetLocked()) {
+                goToSleepNoUpdateLocked(SystemClock.uptimeMillis(),
+                        PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
+                updatePowerStateLocked();
+            } else {
+                wakeUpNoUpdateLocked(SystemClock.uptimeMillis());
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    private void handleScreenOnBlockerReleased() {
+        synchronized (mLock) {
+            mDirty |= DIRTY_SCREEN_ON_BLOCKER_RELEASED;
+            updatePowerStateLocked();
+        }
+    }
+
+    /**
+     * Updates the display power state asynchronously.
+     * When the update is finished, mDisplayReady will be set to true.  The display
+     * controller posts a message to tell us when the actual display power state
+     * has been updated so we come back here to double-check and finish up.
+     *
+     * This function recalculates the display power state each time.
+     */
+    private void updateDisplayPowerStateLocked(int dirty) {
+        if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS
+                | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED
+                | DIRTY_SETTINGS | DIRTY_SCREEN_ON_BLOCKER_RELEASED)) != 0) {
+            int newScreenState = getDesiredScreenPowerStateLocked();
+            if (newScreenState != mDisplayPowerRequest.screenState) {
+                if (newScreenState == DisplayPowerRequest.SCREEN_STATE_OFF
+                        && mDisplayPowerRequest.screenState
+                                != DisplayPowerRequest.SCREEN_STATE_OFF) {
+                    mLastScreenOffEventElapsedRealTime = SystemClock.elapsedRealtime();
+                }
+
+                mDisplayPowerRequest.screenState = newScreenState;
+                nativeSetPowerState(
+                        newScreenState != DisplayPowerRequest.SCREEN_STATE_OFF,
+                        newScreenState == DisplayPowerRequest.SCREEN_STATE_BRIGHT);
+            }
+
+            int screenBrightness = mScreenBrightnessSettingDefault;
+            float screenAutoBrightnessAdjustment = 0.0f;
+            boolean autoBrightness = (mScreenBrightnessModeSetting ==
+                    Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+            if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) {
+                screenBrightness = mScreenBrightnessOverrideFromWindowManager;
+                autoBrightness = false;
+            } else if (isValidBrightness(mTemporaryScreenBrightnessSettingOverride)) {
+                screenBrightness = mTemporaryScreenBrightnessSettingOverride;
+            } else if (isValidBrightness(mScreenBrightnessSetting)) {
+                screenBrightness = mScreenBrightnessSetting;
+            }
+            if (autoBrightness) {
+                screenBrightness = mScreenBrightnessSettingDefault;
+                if (isValidAutoBrightnessAdjustment(
+                        mTemporaryScreenAutoBrightnessAdjustmentSettingOverride)) {
+                    screenAutoBrightnessAdjustment =
+                            mTemporaryScreenAutoBrightnessAdjustmentSettingOverride;
+                } else if (isValidAutoBrightnessAdjustment(
+                        mScreenAutoBrightnessAdjustmentSetting)) {
+                    screenAutoBrightnessAdjustment = mScreenAutoBrightnessAdjustmentSetting;
+                }
+            }
+            screenBrightness = Math.max(Math.min(screenBrightness,
+                    mScreenBrightnessSettingMaximum), mScreenBrightnessSettingMinimum);
+            screenAutoBrightnessAdjustment = Math.max(Math.min(
+                    screenAutoBrightnessAdjustment, 1.0f), -1.0f);
+            mDisplayPowerRequest.screenBrightness = screenBrightness;
+            mDisplayPowerRequest.screenAutoBrightnessAdjustment =
+                    screenAutoBrightnessAdjustment;
+            mDisplayPowerRequest.useAutoBrightness = autoBrightness;
+
+            mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked();
+
+            mDisplayPowerRequest.blockScreenOn = mScreenOnBlocker.isHeld();
+
+            mDisplayReady = mDisplayPowerController.requestPowerState(mDisplayPowerRequest,
+                    mRequestWaitForNegativeProximity);
+            mRequestWaitForNegativeProximity = false;
+
+            if (DEBUG_SPEW) {
+                Slog.d(TAG, "updateScreenStateLocked: mDisplayReady=" + mDisplayReady
+                        + ", newScreenState=" + newScreenState
+                        + ", mWakefulness=" + mWakefulness
+                        + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary)
+                        + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary)
+                        + ", mBootCompleted=" + mBootCompleted);
+            }
+        }
+    }
+
+    private static boolean isValidBrightness(int value) {
+        return value >= 0 && value <= 255;
+    }
+
+    private static boolean isValidAutoBrightnessAdjustment(float value) {
+        // Handles NaN by always returning false.
+        return value >= -1.0f && value <= 1.0f;
+    }
+
+    private int getDesiredScreenPowerStateLocked() {
+        if (mWakefulness == WAKEFULNESS_ASLEEP) {
+            return DisplayPowerRequest.SCREEN_STATE_OFF;
+        }
+
+        if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
+                || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
+                || !mBootCompleted) {
+            return DisplayPowerRequest.SCREEN_STATE_BRIGHT;
+        }
+
+        return DisplayPowerRequest.SCREEN_STATE_DIM;
+    }
+
+    private final DisplayPowerController.Callbacks mDisplayPowerControllerCallbacks =
+            new DisplayPowerController.Callbacks() {
+        @Override
+        public void onStateChanged() {
+            synchronized (mLock) {
+                mDirty |= DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED;
+                updatePowerStateLocked();
+            }
+        }
+
+        @Override
+        public void onProximityPositive() {
+            synchronized (mLock) {
+                mProximityPositive = true;
+                mDirty |= DIRTY_PROXIMITY_POSITIVE;
+                updatePowerStateLocked();
+            }
+        }
+
+        @Override
+        public void onProximityNegative() {
+            synchronized (mLock) {
+                mProximityPositive = false;
+                mDirty |= DIRTY_PROXIMITY_POSITIVE;
+                userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
+                        PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+                updatePowerStateLocked();
+            }
+        }
+    };
+
+    private boolean shouldUseProximitySensorLocked() {
+        return (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0;
+    }
+
+    /**
+     * Updates the suspend blocker that keeps the CPU alive.
+     *
+     * This function must have no other side-effects.
+     */
+    private void updateSuspendBlockerLocked() {
+        final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
+        final boolean needDisplaySuspendBlocker = needDisplaySuspendBlocker();
+
+        // First acquire suspend blockers if needed.
+        if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
+            mWakeLockSuspendBlocker.acquire();
+            mHoldingWakeLockSuspendBlocker = true;
+        }
+        if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
+            mDisplaySuspendBlocker.acquire();
+            mHoldingDisplaySuspendBlocker = true;
+        }
+
+        // Then release suspend blockers if needed.
+        if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
+            mWakeLockSuspendBlocker.release();
+            mHoldingWakeLockSuspendBlocker = false;
+        }
+        if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
+            mDisplaySuspendBlocker.release();
+            mHoldingDisplaySuspendBlocker = false;
+        }
+    }
+
+    /**
+     * Return true if we must keep a suspend blocker active on behalf of the display.
+     * We do so if the screen is on or is in transition between states.
+     */
+    private boolean needDisplaySuspendBlocker() {
+        if (!mDisplayReady) {
+            return true;
+        }
+        if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) {
+            // If we asked for the screen to be on but it is off due to the proximity
+            // sensor then we may suspend but only if the configuration allows it.
+            // On some hardware it may not be safe to suspend because the proximity
+            // sensor may not be correctly configured as a wake-up source.
+            if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
+                    || !mSuspendWhenScreenOffDueToProximityConfig) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override // Binder call
+    public boolean isScreenOn() {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return isScreenOnInternal();
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private boolean isScreenOnInternal() {
+        synchronized (mLock) {
+            return !mSystemReady
+                    || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF;
+        }
+    }
+
+    private void handleBatteryStateChangedLocked() {
+        mDirty |= DIRTY_BATTERY_STATE;
+        updatePowerStateLocked();
+    }
+
+    private void startWatchingForBootAnimationFinished() {
+        mHandler.sendEmptyMessage(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED);
+    }
+
+    private void checkIfBootAnimationFinished() {
+        if (DEBUG) {
+            Slog.d(TAG, "Check if boot animation finished...");
+        }
+
+        if (SystemService.isRunning(BOOT_ANIMATION_SERVICE)) {
+            mHandler.sendEmptyMessageDelayed(MSG_CHECK_IF_BOOT_ANIMATION_FINISHED,
+                    BOOT_ANIMATION_POLL_INTERVAL);
+            return;
+        }
+
+        synchronized (mLock) {
+            if (!mBootCompleted) {
+                Slog.i(TAG, "Boot animation finished.");
+                handleBootCompletedLocked();
+            }
+        }
+    }
+
+    private void handleBootCompletedLocked() {
+        final long now = SystemClock.uptimeMillis();
+        mBootCompleted = true;
+        mDirty |= DIRTY_BOOT_COMPLETED;
+        userActivityNoUpdateLocked(
+                now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
+        updatePowerStateLocked();
+    }
+
+    /**
+     * Reboots the device.
+     *
+     * @param confirm If true, shows a reboot confirmation dialog.
+     * @param reason The reason for the reboot, or null if none.
+     * @param wait If true, this call waits for the reboot to complete and does not return.
+     */
+    @Override // Binder call
+    public void reboot(boolean confirm, String reason, boolean wait) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            shutdownOrRebootInternal(false, confirm, reason, wait);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Shuts down the device.
+     *
+     * @param confirm If true, shows a shutdown confirmation dialog.
+     * @param wait If true, this call waits for the shutdown to complete and does not return.
+     */
+    @Override // Binder call
+    public void shutdown(boolean confirm, boolean wait) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            shutdownOrRebootInternal(true, confirm, null, wait);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void shutdownOrRebootInternal(final boolean shutdown, final boolean confirm,
+            final String reason, boolean wait) {
+        if (mHandler == null || !mSystemReady) {
+            throw new IllegalStateException("Too early to call shutdown() or reboot()");
+        }
+
+        Runnable runnable = new Runnable() {
+            @Override
+            public void run() {
+                synchronized (this) {
+                    if (shutdown) {
+                        ShutdownThread.shutdown(mContext, confirm);
+                    } else {
+                        ShutdownThread.reboot(mContext, reason, confirm);
+                    }
+                }
+            }
+        };
+
+        // ShutdownThread must run on a looper capable of displaying the UI.
+        Message msg = Message.obtain(mHandler, runnable);
+        msg.setAsynchronous(true);
+        mHandler.sendMessage(msg);
+
+        // PowerManager.reboot() is documented not to return so just wait for the inevitable.
+        if (wait) {
+            synchronized (runnable) {
+                while (true) {
+                    try {
+                        runnable.wait();
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Crash the runtime (causing a complete restart of the Android framework).
+     * Requires REBOOT permission.  Mostly for testing.  Should not return.
+     */
+    @Override // Binder call
+    public void crash(String message) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            crashInternal(message);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void crashInternal(final String message) {
+        Thread t = new Thread("PowerManagerService.crash()") {
+            @Override
+            public void run() {
+                throw new RuntimeException(message);
+            }
+        };
+        try {
+            t.start();
+            t.join();
+        } catch (InterruptedException e) {
+            Log.wtf(TAG, e);
+        }
+    }
+
+    /**
+     * Set the setting that determines whether the device stays on when plugged in.
+     * The argument is a bit string, with each bit specifying a power source that,
+     * when the device is connected to that source, causes the device to stay on.
+     * See {@link android.os.BatteryManager} for the list of power sources that
+     * can be specified. Current values include {@link android.os.BatteryManager#BATTERY_PLUGGED_AC}
+     * and {@link android.os.BatteryManager#BATTERY_PLUGGED_USB}
+     *
+     * Used by "adb shell svc power stayon ..."
+     *
+     * @param val an {@code int} containing the bits that specify which power sources
+     * should cause the device to stay on.
+     */
+    @Override // Binder call
+    public void setStayOnSetting(int val) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setStayOnSettingInternal(val);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setStayOnSettingInternal(int val) {
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val);
+    }
+
+    /**
+     * Used by device administration to set the maximum screen off timeout.
+     *
+     * This method must only be called by the device administration policy manager.
+     */
+    @Override // Binder call
+    public void setMaximumScreenOffTimeoutFromDeviceAdmin(int timeMs) {
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setMaximumScreenOffTimeoutFromDeviceAdminInternal(timeMs);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setMaximumScreenOffTimeoutFromDeviceAdminInternal(int timeMs) {
+        synchronized (mLock) {
+            mMaximumScreenOffTimeoutFromDeviceAdmin = timeMs;
+            mDirty |= DIRTY_SETTINGS;
+            updatePowerStateLocked();
+        }
+    }
+
+    private boolean isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() {
+        return mMaximumScreenOffTimeoutFromDeviceAdmin >= 0
+                && mMaximumScreenOffTimeoutFromDeviceAdmin < Integer.MAX_VALUE;
+    }
+
+    /**
+     * Used by the phone application to make the attention LED flash when ringing.
+     */
+    @Override // Binder call
+    public void setAttentionLight(boolean on, int color) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setAttentionLightInternal(on, color);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setAttentionLightInternal(boolean on, int color) {
+        Light light;
+        synchronized (mLock) {
+            if (!mSystemReady) {
+                return;
+            }
+            light = mAttentionLight;
+        }
+
+        // Control light outside of lock.
+        light.setFlashing(color, Light.LIGHT_FLASH_HARDWARE, (on ? 3 : 0), 0);
+    }
+
+    /**
+     * Used by the Watchdog.
+     */
+    public long timeSinceScreenWasLastOn() {
+        synchronized (mLock) {
+            if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) {
+                return 0;
+            }
+            return SystemClock.elapsedRealtime() - mLastScreenOffEventElapsedRealTime;
+        }
+    }
+
+    /**
+     * Used by the window manager to override the screen brightness based on the
+     * current foreground activity.
+     *
+     * This method must only be called by the window manager.
+     *
+     * @param brightness The overridden brightness, or -1 to disable the override.
+     */
+    public void setScreenBrightnessOverrideFromWindowManager(int brightness) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setScreenBrightnessOverrideFromWindowManagerInternal(brightness);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setScreenBrightnessOverrideFromWindowManagerInternal(int brightness) {
+        synchronized (mLock) {
+            if (mScreenBrightnessOverrideFromWindowManager != brightness) {
+                mScreenBrightnessOverrideFromWindowManager = brightness;
+                mDirty |= DIRTY_SETTINGS;
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    /**
+     * Used by the window manager to override the button brightness based on the
+     * current foreground activity.
+     *
+     * This method must only be called by the window manager.
+     *
+     * @param brightness The overridden brightness, or -1 to disable the override.
+     */
+    public void setButtonBrightnessOverrideFromWindowManager(int brightness) {
+        // Do nothing.
+        // Button lights are not currently supported in the new implementation.
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+    }
+
+    /**
+     * Used by the window manager to override the user activity timeout based on the
+     * current foreground activity.  It can only be used to make the timeout shorter
+     * than usual, not longer.
+     *
+     * This method must only be called by the window manager.
+     *
+     * @param timeoutMillis The overridden timeout, or -1 to disable the override.
+     */
+    public void setUserActivityTimeoutOverrideFromWindowManager(long timeoutMillis) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setUserActivityTimeoutOverrideFromWindowManagerInternal(timeoutMillis);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setUserActivityTimeoutOverrideFromWindowManagerInternal(long timeoutMillis) {
+        synchronized (mLock) {
+            if (mUserActivityTimeoutOverrideFromWindowManager != timeoutMillis) {
+                mUserActivityTimeoutOverrideFromWindowManager = timeoutMillis;
+                mDirty |= DIRTY_SETTINGS;
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    /**
+     * Used by the settings application and brightness control widgets to
+     * temporarily override the current screen brightness setting so that the
+     * user can observe the effect of an intended settings change without applying
+     * it immediately.
+     *
+     * The override will be canceled when the setting value is next updated.
+     *
+     * @param brightness The overridden brightness.
+     *
+     * @see android.provider.Settings.System#SCREEN_BRIGHTNESS
+     */
+    @Override // Binder call
+    public void setTemporaryScreenBrightnessSettingOverride(int brightness) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setTemporaryScreenBrightnessSettingOverrideInternal(brightness);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setTemporaryScreenBrightnessSettingOverrideInternal(int brightness) {
+        synchronized (mLock) {
+            if (mTemporaryScreenBrightnessSettingOverride != brightness) {
+                mTemporaryScreenBrightnessSettingOverride = brightness;
+                mDirty |= DIRTY_SETTINGS;
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    /**
+     * Used by the settings application and brightness control widgets to
+     * temporarily override the current screen auto-brightness adjustment setting so that the
+     * user can observe the effect of an intended settings change without applying
+     * it immediately.
+     *
+     * The override will be canceled when the setting value is next updated.
+     *
+     * @param adj The overridden brightness, or Float.NaN to disable the override.
+     *
+     * @see Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ
+     */
+    @Override // Binder call
+    public void setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(float adj) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER, null);
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(adj);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setTemporaryScreenAutoBrightnessAdjustmentSettingOverrideInternal(float adj) {
+        synchronized (mLock) {
+            // Note: This condition handles NaN because NaN is not equal to any other
+            // value, including itself.
+            if (mTemporaryScreenAutoBrightnessAdjustmentSettingOverride != adj) {
+                mTemporaryScreenAutoBrightnessAdjustmentSettingOverride = adj;
+                mDirty |= DIRTY_SETTINGS;
+                updatePowerStateLocked();
+            }
+        }
+    }
+
+    /**
+     * Low-level function turn the device off immediately, without trying
+     * to be clean.  Most people should use {@link ShutdownThread} for a clean shutdown.
+     */
+    public static void lowLevelShutdown() {
+        SystemProperties.set("sys.powerctl", "shutdown");
+    }
+
+    /**
+     * Low-level function to reboot the device. On success, this function
+     * doesn't return. If more than 5 seconds passes from the time,
+     * a reboot is requested, this method returns.
+     *
+     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
+     */
+    public static void lowLevelReboot(String reason) {
+        if (reason == null) {
+            reason = "";
+        }
+        SystemProperties.set("sys.powerctl", "reboot," + reason);
+        try {
+            Thread.sleep(20000);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+    }
+
+    @Override // Watchdog.Monitor implementation
+    public void monitor() {
+        // Grab and release lock for watchdog monitor to detect deadlocks.
+        synchronized (mLock) {
+        }
+    }
+
+    @Override // Binder call
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump PowerManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        pw.println("POWER MANAGER (dumpsys power)\n");
+
+        final DisplayPowerController dpc;
+        final WirelessChargerDetector wcd;
+        synchronized (mLock) {
+            pw.println("Power Manager State:");
+            pw.println("  mDirty=0x" + Integer.toHexString(mDirty));
+            pw.println("  mWakefulness=" + wakefulnessToString(mWakefulness));
+            pw.println("  mIsPowered=" + mIsPowered);
+            pw.println("  mPlugType=" + mPlugType);
+            pw.println("  mBatteryLevel=" + mBatteryLevel);
+            pw.println("  mBatteryLevelWhenDreamStarted=" + mBatteryLevelWhenDreamStarted);
+            pw.println("  mDockState=" + mDockState);
+            pw.println("  mStayOn=" + mStayOn);
+            pw.println("  mProximityPositive=" + mProximityPositive);
+            pw.println("  mBootCompleted=" + mBootCompleted);
+            pw.println("  mSystemReady=" + mSystemReady);
+            pw.println("  mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary));
+            pw.println("  mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary));
+            pw.println("  mRequestWaitForNegativeProximity=" + mRequestWaitForNegativeProximity);
+            pw.println("  mSandmanScheduled=" + mSandmanScheduled);
+            pw.println("  mLastWakeTime=" + TimeUtils.formatUptime(mLastWakeTime));
+            pw.println("  mLastSleepTime=" + TimeUtils.formatUptime(mLastSleepTime));
+            pw.println("  mSendWakeUpFinishedNotificationWhenReady="
+                    + mSendWakeUpFinishedNotificationWhenReady);
+            pw.println("  mSendGoToSleepFinishedNotificationWhenReady="
+                    + mSendGoToSleepFinishedNotificationWhenReady);
+            pw.println("  mLastUserActivityTime=" + TimeUtils.formatUptime(mLastUserActivityTime));
+            pw.println("  mLastUserActivityTimeNoChangeLights="
+                    + TimeUtils.formatUptime(mLastUserActivityTimeNoChangeLights));
+            pw.println("  mDisplayReady=" + mDisplayReady);
+            pw.println("  mHoldingWakeLockSuspendBlocker=" + mHoldingWakeLockSuspendBlocker);
+            pw.println("  mHoldingDisplaySuspendBlocker=" + mHoldingDisplaySuspendBlocker);
+
+            pw.println();
+            pw.println("Settings and Configuration:");
+            pw.println("  mWakeUpWhenPluggedOrUnpluggedConfig="
+                    + mWakeUpWhenPluggedOrUnpluggedConfig);
+            pw.println("  mSuspendWhenScreenOffDueToProximityConfig="
+                    + mSuspendWhenScreenOffDueToProximityConfig);
+            pw.println("  mDreamsSupportedConfig=" + mDreamsSupportedConfig);
+            pw.println("  mDreamsEnabledByDefaultConfig=" + mDreamsEnabledByDefaultConfig);
+            pw.println("  mDreamsActivatedOnSleepByDefaultConfig="
+                    + mDreamsActivatedOnSleepByDefaultConfig);
+            pw.println("  mDreamsActivatedOnDockByDefaultConfig="
+                    + mDreamsActivatedOnDockByDefaultConfig);
+            pw.println("  mDreamsEnabledSetting=" + mDreamsEnabledSetting);
+            pw.println("  mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting);
+            pw.println("  mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting);
+            pw.println("  mScreenOffTimeoutSetting=" + mScreenOffTimeoutSetting);
+            pw.println("  mMaximumScreenOffTimeoutFromDeviceAdmin="
+                    + mMaximumScreenOffTimeoutFromDeviceAdmin + " (enforced="
+                    + isMaximumScreenOffTimeoutFromDeviceAdminEnforcedLocked() + ")");
+            pw.println("  mStayOnWhilePluggedInSetting=" + mStayOnWhilePluggedInSetting);
+            pw.println("  mScreenBrightnessSetting=" + mScreenBrightnessSetting);
+            pw.println("  mScreenAutoBrightnessAdjustmentSetting="
+                    + mScreenAutoBrightnessAdjustmentSetting);
+            pw.println("  mScreenBrightnessModeSetting=" + mScreenBrightnessModeSetting);
+            pw.println("  mScreenBrightnessOverrideFromWindowManager="
+                    + mScreenBrightnessOverrideFromWindowManager);
+            pw.println("  mUserActivityTimeoutOverrideFromWindowManager="
+                    + mUserActivityTimeoutOverrideFromWindowManager);
+            pw.println("  mTemporaryScreenBrightnessSettingOverride="
+                    + mTemporaryScreenBrightnessSettingOverride);
+            pw.println("  mTemporaryScreenAutoBrightnessAdjustmentSettingOverride="
+                    + mTemporaryScreenAutoBrightnessAdjustmentSettingOverride);
+            pw.println("  mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum);
+            pw.println("  mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum);
+            pw.println("  mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault);
+
+            final int screenOffTimeout = getScreenOffTimeoutLocked();
+            final int screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+            pw.println();
+            pw.println("Screen off timeout: " + screenOffTimeout + " ms");
+            pw.println("Screen dim duration: " + screenDimDuration + " ms");
+
+            pw.println();
+            pw.println("Wake Locks: size=" + mWakeLocks.size());
+            for (WakeLock wl : mWakeLocks) {
+                pw.println("  " + wl);
+            }
+
+            pw.println();
+            pw.println("Suspend Blockers: size=" + mSuspendBlockers.size());
+            for (SuspendBlocker sb : mSuspendBlockers) {
+                pw.println("  " + sb);
+            }
+
+            pw.println();
+            pw.println("Screen On Blocker: " + mScreenOnBlocker);
+
+            pw.println();
+            pw.println("Display Blanker: " + mDisplayBlanker);
+
+            dpc = mDisplayPowerController;
+            wcd = mWirelessChargerDetector;
+        }
+
+        if (dpc != null) {
+            dpc.dump(pw);
+        }
+
+        if (wcd != null) {
+            wcd.dump(pw);
+        }
+    }
+
+    private SuspendBlocker createSuspendBlockerLocked(String name) {
+        SuspendBlocker suspendBlocker = new SuspendBlockerImpl(name);
+        mSuspendBlockers.add(suspendBlocker);
+        return suspendBlocker;
+    }
+
+    private static String wakefulnessToString(int wakefulness) {
+        switch (wakefulness) {
+            case WAKEFULNESS_ASLEEP:
+                return "Asleep";
+            case WAKEFULNESS_AWAKE:
+                return "Awake";
+            case WAKEFULNESS_DREAMING:
+                return "Dreaming";
+            case WAKEFULNESS_NAPPING:
+                return "Napping";
+            default:
+                return Integer.toString(wakefulness);
+        }
+    }
+
+    private static WorkSource copyWorkSource(WorkSource workSource) {
+        return workSource != null ? new WorkSource(workSource) : null;
+    }
+
+    private final class BatteryReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                handleBatteryStateChangedLocked();
+            }
+        }
+    }
+
+    private final class BootCompletedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // This is our early signal that the system thinks it has finished booting.
+            // However, the boot animation may still be running for a few more seconds
+            // since it is ultimately in charge of when it terminates.
+            // Defer transitioning into the boot completed state until the animation exits.
+            // We do this so that the screen does not start to dim prematurely before
+            // the user has actually had a chance to interact with the device.
+            startWatchingForBootAnimationFinished();
+        }
+    }
+
+    private final class DreamReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                scheduleSandmanLocked();
+            }
+        }
+    }
+
+    private final class UserSwitchedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                handleSettingsChangedLocked();
+            }
+        }
+    }
+
+    private final class DockReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (mLock) {
+                int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                if (mDockState != dockState) {
+                    mDockState = dockState;
+                    mDirty |= DIRTY_DOCK_STATE;
+                    updatePowerStateLocked();
+                }
+            }
+        }
+    }
+
+    private final class SettingsObserver extends ContentObserver {
+        public SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (mLock) {
+                handleSettingsChangedLocked();
+            }
+        }
+    }
+
+    /**
+     * Handler for asynchronous operations performed by the power manager.
+     */
+    private final class PowerManagerHandler extends Handler {
+        public PowerManagerHandler(Looper looper) {
+            super(looper, null, true /*async*/);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_ACTIVITY_TIMEOUT:
+                    handleUserActivityTimeout();
+                    break;
+                case MSG_SANDMAN:
+                    handleSandman();
+                    break;
+                case MSG_SCREEN_ON_BLOCKER_RELEASED:
+                    handleScreenOnBlockerReleased();
+                    break;
+                case MSG_CHECK_IF_BOOT_ANIMATION_FINISHED:
+                    checkIfBootAnimationFinished();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Represents a wake lock that has been acquired by an application.
+     */
+    private final class WakeLock implements IBinder.DeathRecipient {
+        public final IBinder mLock;
+        public int mFlags;
+        public String mTag;
+        public final String mPackageName;
+        public WorkSource mWorkSource;
+        public final int mOwnerUid;
+        public final int mOwnerPid;
+        public boolean mNotifiedAcquired;
+
+        public WakeLock(IBinder lock, int flags, String tag, String packageName,
+                WorkSource workSource, int ownerUid, int ownerPid) {
+            mLock = lock;
+            mFlags = flags;
+            mTag = tag;
+            mPackageName = packageName;
+            mWorkSource = copyWorkSource(workSource);
+            mOwnerUid = ownerUid;
+            mOwnerPid = ownerPid;
+        }
+
+        @Override
+        public void binderDied() {
+            PowerManagerService.this.handleWakeLockDeath(this);
+        }
+
+        public boolean hasSameProperties(int flags, String tag, WorkSource workSource,
+                int ownerUid, int ownerPid) {
+            return mFlags == flags
+                    && mTag.equals(tag)
+                    && hasSameWorkSource(workSource)
+                    && mOwnerUid == ownerUid
+                    && mOwnerPid == ownerPid;
+        }
+
+        public void updateProperties(int flags, String tag, String packageName,
+                WorkSource workSource, int ownerUid, int ownerPid) {
+            if (!mPackageName.equals(packageName)) {
+                throw new IllegalStateException("Existing wake lock package name changed: "
+                        + mPackageName + " to " + packageName);
+            }
+            if (mOwnerUid != ownerUid) {
+                throw new IllegalStateException("Existing wake lock uid changed: "
+                        + mOwnerUid + " to " + ownerUid);
+            }
+            if (mOwnerPid != ownerPid) {
+                throw new IllegalStateException("Existing wake lock pid changed: "
+                        + mOwnerPid + " to " + ownerPid);
+            }
+            mFlags = flags;
+            mTag = tag;
+            updateWorkSource(workSource);
+        }
+
+        public boolean hasSameWorkSource(WorkSource workSource) {
+            return Objects.equal(mWorkSource, workSource);
+        }
+
+        public void updateWorkSource(WorkSource workSource) {
+            mWorkSource = copyWorkSource(workSource);
+        }
+
+        @Override
+        public String toString() {
+            return getLockLevelString()
+                    + " '" + mTag + "'" + getLockFlagsString()
+                    + " (uid=" + mOwnerUid + ", pid=" + mOwnerPid + ", ws=" + mWorkSource + ")";
+        }
+
+        private String getLockLevelString() {
+            switch (mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
+                case PowerManager.FULL_WAKE_LOCK:
+                    return "FULL_WAKE_LOCK                ";
+                case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
+                    return "SCREEN_BRIGHT_WAKE_LOCK       ";
+                case PowerManager.SCREEN_DIM_WAKE_LOCK:
+                    return "SCREEN_DIM_WAKE_LOCK          ";
+                case PowerManager.PARTIAL_WAKE_LOCK:
+                    return "PARTIAL_WAKE_LOCK             ";
+                case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
+                    return "PROXIMITY_SCREEN_OFF_WAKE_LOCK";
+                default:
+                    return "???                           ";
+            }
+        }
+
+        private String getLockFlagsString() {
+            String result = "";
+            if ((mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0) {
+                result += " ACQUIRE_CAUSES_WAKEUP";
+            }
+            if ((mFlags & PowerManager.ON_AFTER_RELEASE) != 0) {
+                result += " ON_AFTER_RELEASE";
+            }
+            return result;
+        }
+    }
+
+    private final class SuspendBlockerImpl implements SuspendBlocker {
+        private final String mName;
+        private int mReferenceCount;
+
+        public SuspendBlockerImpl(String name) {
+            mName = name;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                if (mReferenceCount != 0) {
+                    Log.wtf(TAG, "Suspend blocker \"" + mName
+                            + "\" was finalized without being released!");
+                    mReferenceCount = 0;
+                    nativeReleaseSuspendBlocker(mName);
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+
+        @Override
+        public void acquire() {
+            synchronized (this) {
+                mReferenceCount += 1;
+                if (mReferenceCount == 1) {
+                    if (DEBUG_SPEW) {
+                        Slog.d(TAG, "Acquiring suspend blocker \"" + mName + "\".");
+                    }
+                    nativeAcquireSuspendBlocker(mName);
+                }
+            }
+        }
+
+        @Override
+        public void release() {
+            synchronized (this) {
+                mReferenceCount -= 1;
+                if (mReferenceCount == 0) {
+                    if (DEBUG_SPEW) {
+                        Slog.d(TAG, "Releasing suspend blocker \"" + mName + "\".");
+                    }
+                    nativeReleaseSuspendBlocker(mName);
+                } else if (mReferenceCount < 0) {
+                    Log.wtf(TAG, "Suspend blocker \"" + mName
+                            + "\" was released without being acquired!", new Throwable());
+                    mReferenceCount = 0;
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            synchronized (this) {
+                return mName + ": ref count=" + mReferenceCount;
+            }
+        }
+    }
+
+    private final class ScreenOnBlockerImpl implements ScreenOnBlocker {
+        private int mNestCount;
+
+        public boolean isHeld() {
+            synchronized (this) {
+                return mNestCount != 0;
+            }
+        }
+
+        @Override
+        public void acquire() {
+            synchronized (this) {
+                mNestCount += 1;
+                if (DEBUG) {
+                    Slog.d(TAG, "Screen on blocked: mNestCount=" + mNestCount);
+                }
+            }
+        }
+
+        @Override
+        public void release() {
+            synchronized (this) {
+                mNestCount -= 1;
+                if (mNestCount < 0) {
+                    Log.wtf(TAG, "Screen on blocker was released without being acquired!",
+                            new Throwable());
+                    mNestCount = 0;
+                }
+                if (mNestCount == 0) {
+                    mHandler.sendEmptyMessage(MSG_SCREEN_ON_BLOCKER_RELEASED);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, "Screen on unblocked: mNestCount=" + mNestCount);
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            synchronized (this) {
+                return "held=" + (mNestCount != 0) + ", mNestCount=" + mNestCount;
+            }
+        }
+    }
+
+    private final class DisplayBlankerImpl implements DisplayBlanker {
+        private boolean mBlanked;
+
+        @Override
+        public void blankAllDisplays() {
+            synchronized (this) {
+                mBlanked = true;
+                mDisplayManagerService.blankAllDisplaysFromPowerManager();
+                nativeSetInteractive(false);
+                nativeSetAutoSuspend(true);
+            }
+        }
+
+        @Override
+        public void unblankAllDisplays() {
+            synchronized (this) {
+                nativeSetAutoSuspend(false);
+                nativeSetInteractive(true);
+                mDisplayManagerService.unblankAllDisplaysFromPowerManager();
+                mBlanked = false;
+            }
+        }
+
+        @Override
+        public String toString() {
+            synchronized (this) {
+                return "blanked=" + mBlanked;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/power/RampAnimator.java b/services/core/java/com/android/server/power/RampAnimator.java
new file mode 100644
index 0000000..4a4f080
--- /dev/null
+++ b/services/core/java/com/android/server/power/RampAnimator.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import android.animation.ValueAnimator;
+import android.util.IntProperty;
+import android.view.Choreographer;
+
+/**
+ * A custom animator that progressively updates a property value at
+ * a given variable rate until it reaches a particular target value.
+ */
+final class RampAnimator<T> {
+    private final T mObject;
+    private final IntProperty<T> mProperty;
+    private final Choreographer mChoreographer;
+
+    private int mCurrentValue;
+    private int mTargetValue;
+    private int mRate;
+
+    private boolean mAnimating;
+    private float mAnimatedValue; // higher precision copy of mCurrentValue
+    private long mLastFrameTimeNanos;
+
+    private boolean mFirstTime = true;
+
+    public RampAnimator(T object, IntProperty<T> property) {
+        mObject = object;
+        mProperty = property;
+        mChoreographer = Choreographer.getInstance();
+    }
+
+    /**
+     * Starts animating towards the specified value.
+     *
+     * If this is the first time the property is being set, the value jumps
+     * directly to the target.
+     *
+     * @param target The target value.
+     * @param rate The convergence rate, in units per second.
+     * @return True if the target differs from the previous target.
+     */
+    public boolean animateTo(int target, int rate) {
+        // Immediately jump to the target the first time.
+        if (mFirstTime) {
+            mFirstTime = false;
+            mProperty.setValue(mObject, target);
+            mCurrentValue = target;
+            return true;
+        }
+
+        // Adjust the rate based on the closest target.
+        // If a faster rate is specified, then use the new rate so that we converge
+        // more rapidly based on the new request.
+        // If a slower rate is specified, then use the new rate only if the current
+        // value is somewhere in between the new and the old target meaning that
+        // we will be ramping in a different direction to get there.
+        // Otherwise, continue at the previous rate.
+        if (!mAnimating
+                || rate > mRate
+                || (target <= mCurrentValue && mCurrentValue <= mTargetValue)
+                || (mTargetValue <= mCurrentValue && mCurrentValue <= target)) {
+            mRate = rate;
+        }
+
+        final boolean changed = (mTargetValue != target);
+        mTargetValue = target;
+
+        // Start animating.
+        if (!mAnimating && target != mCurrentValue) {
+            mAnimating = true;
+            mAnimatedValue = mCurrentValue;
+            mLastFrameTimeNanos = System.nanoTime();
+            postCallback();
+        }
+
+        return changed;
+    }
+
+    private void postCallback() {
+        mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mCallback, null);
+    }
+
+    private final Runnable mCallback = new Runnable() {
+        @Override // Choreographer callback
+        public void run() {
+            final long frameTimeNanos = mChoreographer.getFrameTimeNanos();
+            final float timeDelta = (frameTimeNanos - mLastFrameTimeNanos)
+                    * 0.000000001f;
+            mLastFrameTimeNanos = frameTimeNanos;
+
+            // Advance the animated value towards the target at the specified rate
+            // and clamp to the target. This gives us the new current value but
+            // we keep the animated value around to allow for fractional increments
+            // towards the target.
+            final float scale = ValueAnimator.getDurationScale();
+            if (scale == 0) {
+                // Animation off.
+                mAnimatedValue = mTargetValue;
+            } else {
+                final float amount = timeDelta * mRate / scale;
+                if (mTargetValue > mCurrentValue) {
+                    mAnimatedValue = Math.min(mAnimatedValue + amount, mTargetValue);
+                } else {
+                    mAnimatedValue = Math.max(mAnimatedValue - amount, mTargetValue);
+                }
+            }
+            final int oldCurrentValue = mCurrentValue;
+            mCurrentValue = Math.round(mAnimatedValue);
+
+            if (oldCurrentValue != mCurrentValue) {
+                mProperty.setValue(mObject, mCurrentValue);
+            }
+
+            if (mTargetValue != mCurrentValue) {
+                postCallback();
+            } else {
+                mAnimating = false;
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/power/ScreenOnBlocker.java b/services/core/java/com/android/server/power/ScreenOnBlocker.java
new file mode 100644
index 0000000..dbbbc6d
--- /dev/null
+++ b/services/core/java/com/android/server/power/ScreenOnBlocker.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+/**
+ * Low-level screen on blocker mechanism which is used to keep the screen off
+ * or the contents of the screen hidden until the window manager is ready to show new content.
+ */
+interface ScreenOnBlocker {
+    /**
+     * Acquires the screen on blocker.
+     * Prevents the screen from turning on.
+     *
+     * Calls to acquire() nest and must be matched by the same number
+     * of calls to release().
+     */
+    void acquire();
+
+    /**
+     * Releases the screen on blocker.
+     * Allows the screen to turn on.
+     *
+     * It is an error to call release() if the screen on blocker has not been acquired.
+     * The system may crash.
+     */
+    void release();
+}
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
new file mode 100644
index 0000000..88a27f5
--- /dev/null
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -0,0 +1,518 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ 
+package com.android.server.power;
+
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.IActivityManager;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetoothManager;
+import android.nfc.NfcAdapter;
+import android.nfc.INfcAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.os.SystemVibrator;
+import android.os.storage.IMountService;
+import android.os.storage.IMountShutdownObserver;
+
+import com.android.internal.telephony.ITelephony;
+
+import android.util.Log;
+import android.view.WindowManager;
+
+public final class ShutdownThread extends Thread {
+    // constants
+    private static final String TAG = "ShutdownThread";
+    private static final int PHONE_STATE_POLL_SLEEP_MSEC = 500;
+    // maximum time we wait for the shutdown broadcast before going on.
+    private static final int MAX_BROADCAST_TIME = 10*1000;
+    private static final int MAX_SHUTDOWN_WAIT_TIME = 20*1000;
+    private static final int MAX_RADIO_WAIT_TIME = 12*1000;
+
+    // length of vibration before shutting down
+    private static final int SHUTDOWN_VIBRATE_MS = 500;
+    
+    // state tracking
+    private static Object sIsStartedGuard = new Object();
+    private static boolean sIsStarted = false;
+    
+    private static boolean mReboot;
+    private static boolean mRebootSafeMode;
+    private static String mRebootReason;
+
+    // Provides shutdown assurance in case the system_server is killed
+    public static final String SHUTDOWN_ACTION_PROPERTY = "sys.shutdown.requested";
+
+    // Indicates whether we are rebooting into safe mode
+    public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
+
+    // static instance of this thread
+    private static final ShutdownThread sInstance = new ShutdownThread();
+    
+    private final Object mActionDoneSync = new Object();
+    private boolean mActionDone;
+    private Context mContext;
+    private PowerManager mPowerManager;
+    private PowerManager.WakeLock mCpuWakeLock;
+    private PowerManager.WakeLock mScreenWakeLock;
+    private Handler mHandler;
+
+    private static AlertDialog sConfirmDialog;
+    
+    private ShutdownThread() {
+    }
+ 
+    /**
+     * Request a clean shutdown, waiting for subsystems to clean up their
+     * state etc.  Must be called from a Looper thread in which its UI
+     * is shown.
+     *
+     * @param context Context used to display the shutdown progress dialog.
+     * @param confirm true if user confirmation is needed before shutting down.
+     */
+    public static void shutdown(final Context context, boolean confirm) {
+        mReboot = false;
+        mRebootSafeMode = false;
+        shutdownInner(context, confirm);
+    }
+
+    static void shutdownInner(final Context context, boolean confirm) {
+        // ensure that only one thread is trying to power down.
+        // any additional calls are just returned
+        synchronized (sIsStartedGuard) {
+            if (sIsStarted) {
+                Log.d(TAG, "Request to shutdown already running, returning.");
+                return;
+            }
+        }
+
+        final int longPressBehavior = context.getResources().getInteger(
+                        com.android.internal.R.integer.config_longPressOnPowerBehavior);
+        final int resourceId = mRebootSafeMode
+                ? com.android.internal.R.string.reboot_safemode_confirm
+                : (longPressBehavior == 2
+                        ? com.android.internal.R.string.shutdown_confirm_question
+                        : com.android.internal.R.string.shutdown_confirm);
+
+        Log.d(TAG, "Notifying thread to start shutdown longPressBehavior=" + longPressBehavior);
+
+        if (confirm) {
+            final CloseDialogReceiver closer = new CloseDialogReceiver(context);
+            if (sConfirmDialog != null) {
+                sConfirmDialog.dismiss();
+            }
+            sConfirmDialog = new AlertDialog.Builder(context)
+                    .setTitle(mRebootSafeMode
+                            ? com.android.internal.R.string.reboot_safemode_title
+                            : com.android.internal.R.string.power_off)
+                    .setMessage(resourceId)
+                    .setPositiveButton(com.android.internal.R.string.yes, new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            beginShutdownSequence(context);
+                        }
+                    })
+                    .setNegativeButton(com.android.internal.R.string.no, null)
+                    .create();
+            closer.dialog = sConfirmDialog;
+            sConfirmDialog.setOnDismissListener(closer);
+            sConfirmDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+            sConfirmDialog.show();
+        } else {
+            beginShutdownSequence(context);
+        }
+    }
+
+    private static class CloseDialogReceiver extends BroadcastReceiver
+            implements DialogInterface.OnDismissListener {
+        private Context mContext;
+        public Dialog dialog;
+
+        CloseDialogReceiver(Context context) {
+            mContext = context;
+            IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+            context.registerReceiver(this, filter);
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            dialog.cancel();
+        }
+
+        public void onDismiss(DialogInterface unused) {
+            mContext.unregisterReceiver(this);
+        }
+    }
+
+    /**
+     * Request a clean shutdown, waiting for subsystems to clean up their
+     * state etc.  Must be called from a Looper thread in which its UI
+     * is shown.
+     *
+     * @param context Context used to display the shutdown progress dialog.
+     * @param reason code to pass to the kernel (e.g. "recovery"), or null.
+     * @param confirm true if user confirmation is needed before shutting down.
+     */
+    public static void reboot(final Context context, String reason, boolean confirm) {
+        mReboot = true;
+        mRebootSafeMode = false;
+        mRebootReason = reason;
+        shutdownInner(context, confirm);
+    }
+
+    /**
+     * Request a reboot into safe mode.  Must be called from a Looper thread in which its UI
+     * is shown.
+     *
+     * @param context Context used to display the shutdown progress dialog.
+     * @param confirm true if user confirmation is needed before shutting down.
+     */
+    public static void rebootSafeMode(final Context context, boolean confirm) {
+        mReboot = true;
+        mRebootSafeMode = true;
+        mRebootReason = null;
+        shutdownInner(context, confirm);
+    }
+
+    private static void beginShutdownSequence(Context context) {
+        synchronized (sIsStartedGuard) {
+            if (sIsStarted) {
+                Log.d(TAG, "Shutdown sequence already running, returning.");
+                return;
+            }
+            sIsStarted = true;
+        }
+
+        // throw up an indeterminate system dialog to indicate radio is
+        // shutting down.
+        ProgressDialog pd = new ProgressDialog(context);
+        pd.setTitle(context.getText(com.android.internal.R.string.power_off));
+        pd.setMessage(context.getText(com.android.internal.R.string.shutdown_progress));
+        pd.setIndeterminate(true);
+        pd.setCancelable(false);
+        pd.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+
+        pd.show();
+
+        sInstance.mContext = context;
+        sInstance.mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+
+        // make sure we never fall asleep again
+        sInstance.mCpuWakeLock = null;
+        try {
+            sInstance.mCpuWakeLock = sInstance.mPowerManager.newWakeLock(
+                    PowerManager.PARTIAL_WAKE_LOCK, TAG + "-cpu");
+            sInstance.mCpuWakeLock.setReferenceCounted(false);
+            sInstance.mCpuWakeLock.acquire();
+        } catch (SecurityException e) {
+            Log.w(TAG, "No permission to acquire wake lock", e);
+            sInstance.mCpuWakeLock = null;
+        }
+
+        // also make sure the screen stays on for better user experience
+        sInstance.mScreenWakeLock = null;
+        if (sInstance.mPowerManager.isScreenOn()) {
+            try {
+                sInstance.mScreenWakeLock = sInstance.mPowerManager.newWakeLock(
+                        PowerManager.FULL_WAKE_LOCK, TAG + "-screen");
+                sInstance.mScreenWakeLock.setReferenceCounted(false);
+                sInstance.mScreenWakeLock.acquire();
+            } catch (SecurityException e) {
+                Log.w(TAG, "No permission to acquire wake lock", e);
+                sInstance.mScreenWakeLock = null;
+            }
+        }
+
+        // start the thread that initiates shutdown
+        sInstance.mHandler = new Handler() {
+        };
+        sInstance.start();
+    }
+
+    void actionDone() {
+        synchronized (mActionDoneSync) {
+            mActionDone = true;
+            mActionDoneSync.notifyAll();
+        }
+    }
+
+    /**
+     * Makes sure we handle the shutdown gracefully.
+     * Shuts off power regardless of radio and bluetooth state if the alloted time has passed.
+     */
+    public void run() {
+        BroadcastReceiver br = new BroadcastReceiver() {
+            @Override public void onReceive(Context context, Intent intent) {
+                // We don't allow apps to cancel this, so ignore the result.
+                actionDone();
+            }
+        };
+
+        /*
+         * Write a system property in case the system_server reboots before we
+         * get to the actual hardware restart. If that happens, we'll retry at
+         * the beginning of the SystemServer startup.
+         */
+        {
+            String reason = (mReboot ? "1" : "0") + (mRebootReason != null ? mRebootReason : "");
+            SystemProperties.set(SHUTDOWN_ACTION_PROPERTY, reason);
+        }
+
+        /*
+         * If we are rebooting into safe mode, write a system property
+         * indicating so.
+         */
+        if (mRebootSafeMode) {
+            SystemProperties.set(REBOOT_SAFEMODE_PROPERTY, "1");
+        }
+
+        Log.i(TAG, "Sending shutdown broadcast...");
+        
+        // First send the high-level shut down broadcast.
+        mActionDone = false;
+        Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        mContext.sendOrderedBroadcastAsUser(intent,
+                UserHandle.ALL, null, br, mHandler, 0, null, null);
+        
+        final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
+        synchronized (mActionDoneSync) {
+            while (!mActionDone) {
+                long delay = endTime - SystemClock.elapsedRealtime();
+                if (delay <= 0) {
+                    Log.w(TAG, "Shutdown broadcast timed out");
+                    break;
+                }
+                try {
+                    mActionDoneSync.wait(delay);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+        
+        Log.i(TAG, "Shutting down activity manager...");
+        
+        final IActivityManager am =
+            ActivityManagerNative.asInterface(ServiceManager.checkService("activity"));
+        if (am != null) {
+            try {
+                am.shutdown(MAX_BROADCAST_TIME);
+            } catch (RemoteException e) {
+            }
+        }
+
+        // Shutdown radios.
+        shutdownRadios(MAX_RADIO_WAIT_TIME);
+
+        // Shutdown MountService to ensure media is in a safe state
+        IMountShutdownObserver observer = new IMountShutdownObserver.Stub() {
+            public void onShutDownComplete(int statusCode) throws RemoteException {
+                Log.w(TAG, "Result code " + statusCode + " from MountService.shutdown");
+                actionDone();
+            }
+        };
+
+        Log.i(TAG, "Shutting down MountService");
+
+        // Set initial variables and time out time.
+        mActionDone = false;
+        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
+        synchronized (mActionDoneSync) {
+            try {
+                final IMountService mount = IMountService.Stub.asInterface(
+                        ServiceManager.checkService("mount"));
+                if (mount != null) {
+                    mount.shutdown(observer);
+                } else {
+                    Log.w(TAG, "MountService unavailable for shutdown");
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Exception during MountService shutdown", e);
+            }
+            while (!mActionDone) {
+                long delay = endShutTime - SystemClock.elapsedRealtime();
+                if (delay <= 0) {
+                    Log.w(TAG, "Shutdown wait timed out");
+                    break;
+                }
+                try {
+                    mActionDoneSync.wait(delay);
+                } catch (InterruptedException e) {
+                }
+            }
+        }
+
+        rebootOrShutdown(mReboot, mRebootReason);
+    }
+
+    private void shutdownRadios(int timeout) {
+        // If a radio is wedged, disabling it may hang so we do this work in another thread,
+        // just in case.
+        final long endTime = SystemClock.elapsedRealtime() + timeout;
+        final boolean[] done = new boolean[1];
+        Thread t = new Thread() {
+            public void run() {
+                boolean nfcOff;
+                boolean bluetoothOff;
+                boolean radioOff;
+
+                final INfcAdapter nfc =
+                        INfcAdapter.Stub.asInterface(ServiceManager.checkService("nfc"));
+                final ITelephony phone =
+                        ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
+                final IBluetoothManager bluetooth =
+                        IBluetoothManager.Stub.asInterface(ServiceManager.checkService(
+                                BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE));
+
+                try {
+                    nfcOff = nfc == null ||
+                             nfc.getState() == NfcAdapter.STATE_OFF;
+                    if (!nfcOff) {
+                        Log.w(TAG, "Turning off NFC...");
+                        nfc.disable(false); // Don't persist new state
+                    }
+                } catch (RemoteException ex) {
+                Log.e(TAG, "RemoteException during NFC shutdown", ex);
+                    nfcOff = true;
+                }
+
+                try {
+                    bluetoothOff = bluetooth == null || !bluetooth.isEnabled();
+                    if (!bluetoothOff) {
+                        Log.w(TAG, "Disabling Bluetooth...");
+                        bluetooth.disable(false);  // disable but don't persist new state
+                    }
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
+                    bluetoothOff = true;
+                }
+
+                try {
+                    radioOff = phone == null || !phone.isRadioOn();
+                    if (!radioOff) {
+                        Log.w(TAG, "Turning off radio...");
+                        phone.setRadio(false);
+                    }
+                } catch (RemoteException ex) {
+                    Log.e(TAG, "RemoteException during radio shutdown", ex);
+                    radioOff = true;
+                }
+
+                Log.i(TAG, "Waiting for NFC, Bluetooth and Radio...");
+
+                while (SystemClock.elapsedRealtime() < endTime) {
+                    if (!bluetoothOff) {
+                        try {
+                            bluetoothOff = !bluetooth.isEnabled();
+                        } catch (RemoteException ex) {
+                            Log.e(TAG, "RemoteException during bluetooth shutdown", ex);
+                            bluetoothOff = true;
+                        }
+                        if (bluetoothOff) {
+                            Log.i(TAG, "Bluetooth turned off.");
+                        }
+                    }
+                    if (!radioOff) {
+                        try {
+                            radioOff = !phone.isRadioOn();
+                        } catch (RemoteException ex) {
+                            Log.e(TAG, "RemoteException during radio shutdown", ex);
+                            radioOff = true;
+                        }
+                        if (radioOff) {
+                            Log.i(TAG, "Radio turned off.");
+                        }
+                    }
+                    if (!nfcOff) {
+                        try {
+                            nfcOff = nfc.getState() == NfcAdapter.STATE_OFF;
+                        } catch (RemoteException ex) {
+                            Log.e(TAG, "RemoteException during NFC shutdown", ex);
+                            nfcOff = true;
+                        }
+                        if (radioOff) {
+                            Log.i(TAG, "NFC turned off.");
+                        }
+                    }
+
+                    if (radioOff && bluetoothOff && nfcOff) {
+                        Log.i(TAG, "NFC, Radio and Bluetooth shutdown complete.");
+                        done[0] = true;
+                        break;
+                    }
+                    SystemClock.sleep(PHONE_STATE_POLL_SLEEP_MSEC);
+                }
+            }
+        };
+
+        t.start();
+        try {
+            t.join(timeout);
+        } catch (InterruptedException ex) {
+        }
+        if (!done[0]) {
+            Log.w(TAG, "Timed out waiting for NFC, Radio and Bluetooth shutdown.");
+        }
+    }
+
+    /**
+     * Do not call this directly. Use {@link #reboot(Context, String, boolean)}
+     * or {@link #shutdown(Context, boolean)} instead.
+     *
+     * @param reboot true to reboot or false to shutdown
+     * @param reason reason for reboot
+     */
+    public static void rebootOrShutdown(boolean reboot, String reason) {
+        if (reboot) {
+            Log.i(TAG, "Rebooting, reason: " + reason);
+            PowerManagerService.lowLevelReboot(reason);
+            Log.e(TAG, "Reboot failed, will attempt shutdown instead");
+        } else if (SHUTDOWN_VIBRATE_MS > 0) {
+            // vibrate before shutting down
+            Vibrator vibrator = new SystemVibrator();
+            try {
+                vibrator.vibrate(SHUTDOWN_VIBRATE_MS);
+            } catch (Exception e) {
+                // Failure to vibrate shouldn't interrupt shutdown.  Just log it.
+                Log.w(TAG, "Failed to vibrate during shutdown.", e);
+            }
+
+            // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
+            try {
+                Thread.sleep(SHUTDOWN_VIBRATE_MS);
+            } catch (InterruptedException unused) {
+            }
+        }
+
+        // Shutdown power
+        Log.i(TAG, "Performing low-level shutdown...");
+        PowerManagerService.lowLevelShutdown();
+    }
+}
diff --git a/services/core/java/com/android/server/power/SuspendBlocker.java b/services/core/java/com/android/server/power/SuspendBlocker.java
new file mode 100644
index 0000000..70b278a
--- /dev/null
+++ b/services/core/java/com/android/server/power/SuspendBlocker.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+/**
+ * Low-level suspend blocker mechanism equivalent to holding a partial wake lock.
+ *
+ * This interface is used internally to avoid introducing internal dependencies
+ * on the high-level wake lock mechanism.
+ */
+interface SuspendBlocker {
+    /**
+     * Acquires the suspend blocker.
+     * Prevents the CPU from going to sleep.
+     *
+     * Calls to acquire() nest and must be matched by the same number
+     * of calls to release().
+     */
+    void acquire();
+
+    /**
+     * Releases the suspend blocker.
+     * Allows the CPU to go to sleep if no other suspend blockers are held.
+     *
+     * It is an error to call release() if the suspend blocker has not been acquired.
+     * The system may crash.
+     */
+    void release();
+}
diff --git a/services/core/java/com/android/server/power/WirelessChargerDetector.java b/services/core/java/com/android/server/power/WirelessChargerDetector.java
new file mode 100644
index 0000000..38f5d77
--- /dev/null
+++ b/services/core/java/com/android/server/power/WirelessChargerDetector.java
@@ -0,0 +1,357 @@
+/*
+ * 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.power;
+
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Implements heuristics to detect docking or undocking from a wireless charger.
+ * <p>
+ * Some devices have wireless charging circuits that are unable to detect when the
+ * device is resting on a wireless charger except when the device is actually
+ * receiving power from the charger.  The device may stop receiving power
+ * if the battery is already nearly full or if it is too hot.  As a result, we cannot
+ * always rely on the battery service wireless plug signal to accurately indicate
+ * whether the device has been docked or undocked from a wireless charger.
+ * </p><p>
+ * This is a problem because the power manager typically wakes up the screen and
+ * plays a tone when the device is docked in a wireless charger.  It is important
+ * for the system to suppress spurious docking and undocking signals because they
+ * can be intrusive for the user (especially if they cause a tone to be played
+ * late at night for no apparent reason).
+ * </p><p>
+ * To avoid spurious signals, we apply some special policies to wireless chargers.
+ * </p><p>
+ * 1. Don't wake the device when undocked from the wireless charger because
+ * it might be that the device is still resting on the wireless charger
+ * but is not receiving power anymore because the battery is full.
+ * Ideally we would wake the device if we could be certain that the user had
+ * picked it up from the wireless charger but due to hardware limitations we
+ * must be more conservative.
+ * </p><p>
+ * 2. Don't wake the device when docked on a wireless charger if the
+ * battery already appears to be mostly full.  This situation may indicate
+ * that the device was resting on the charger the whole time and simply
+ * wasn't receiving power because the battery was already full.  We can't tell
+ * whether the device was just placed on the charger or whether it has
+ * been there for half of the night slowly discharging until it reached
+ * the point where it needed to start charging again.  So we suppress docking
+ * signals that occur when the battery level is above a given threshold.
+ * </p><p>
+ * 3. Don't wake the device when docked on a wireless charger if it does
+ * not appear to have moved since it was last undocked because it may
+ * be that the prior undocking signal was spurious.  We use the gravity
+ * sensor to detect this case.
+ * </p>
+ */
+final class WirelessChargerDetector {
+    private static final String TAG = "WirelessChargerDetector";
+    private static final boolean DEBUG = false;
+
+    // The minimum amount of time to spend watching the sensor before making
+    // a determination of whether movement occurred.
+    private static final long SETTLE_TIME_MILLIS = 800;
+
+    // The sensor sampling interval.
+    private static final int SAMPLING_INTERVAL_MILLIS = 50;
+
+    // The minimum number of samples that must be collected.
+    private static final int MIN_SAMPLES = 3;
+
+    // Upper bound on the battery charge percentage in order to consider turning
+    // the screen on when the device starts charging wirelessly.
+    private static final int WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT = 95;
+
+    // To detect movement, we compute the angle between the gravity vector
+    // at rest and the current gravity vector.  This field specifies the
+    // cosine of the maximum angle variance that we tolerate while at rest.
+    private static final double MOVEMENT_ANGLE_COS_THRESHOLD = Math.cos(5 * Math.PI / 180);
+
+    // Sanity thresholds for the gravity vector.
+    private static final double MIN_GRAVITY = SensorManager.GRAVITY_EARTH - 1.0f;
+    private static final double MAX_GRAVITY = SensorManager.GRAVITY_EARTH + 1.0f;
+
+    private final Object mLock = new Object();
+
+    private final SensorManager mSensorManager;
+    private final SuspendBlocker mSuspendBlocker;
+    private final Handler mHandler;
+
+    // The gravity sensor, or null if none.
+    private Sensor mGravitySensor;
+
+    // Previously observed wireless power state.
+    private boolean mPoweredWirelessly;
+
+    // True if the device is thought to be at rest on a wireless charger.
+    private boolean mAtRest;
+
+    // The gravity vector most recently observed while at rest.
+    private float mRestX, mRestY, mRestZ;
+
+    /* These properties are only meaningful while detection is in progress. */
+
+    // True if detection is in progress.
+    // The suspend blocker is held while this is the case.
+    private boolean mDetectionInProgress;
+
+    // The time when detection was last performed.
+    private long mDetectionStartTime;
+
+    // True if the rest position should be updated if at rest.
+    // Otherwise, the current rest position is simply checked and cleared if movement
+    // is detected but no new rest position is stored.
+    private boolean mMustUpdateRestPosition;
+
+    // The total number of samples collected.
+    private int mTotalSamples;
+
+    // The number of samples collected that showed evidence of not being at rest.
+    private int mMovingSamples;
+
+    // The value of the first sample that was collected.
+    private float mFirstSampleX, mFirstSampleY, mFirstSampleZ;
+
+    // The value of the last sample that was collected.
+    private float mLastSampleX, mLastSampleY, mLastSampleZ;
+
+    public WirelessChargerDetector(SensorManager sensorManager,
+            SuspendBlocker suspendBlocker, Handler handler) {
+        mSensorManager = sensorManager;
+        mSuspendBlocker = suspendBlocker;
+        mHandler = handler;
+
+        mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);
+    }
+
+    public void dump(PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("Wireless Charger Detector State:");
+            pw.println("  mGravitySensor=" + mGravitySensor);
+            pw.println("  mPoweredWirelessly=" + mPoweredWirelessly);
+            pw.println("  mAtRest=" + mAtRest);
+            pw.println("  mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ);
+            pw.println("  mDetectionInProgress=" + mDetectionInProgress);
+            pw.println("  mDetectionStartTime=" + (mDetectionStartTime == 0 ? "0 (never)"
+                    : TimeUtils.formatUptime(mDetectionStartTime)));
+            pw.println("  mMustUpdateRestPosition=" + mMustUpdateRestPosition);
+            pw.println("  mTotalSamples=" + mTotalSamples);
+            pw.println("  mMovingSamples=" + mMovingSamples);
+            pw.println("  mFirstSampleX=" + mFirstSampleX
+                    + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ);
+            pw.println("  mLastSampleX=" + mLastSampleX
+                    + ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ);
+        }
+    }
+
+    /**
+     * Updates the charging state and returns true if docking was detected.
+     *
+     * @param isPowered True if the device is powered.
+     * @param plugType The current plug type.
+     * @param batteryLevel The current battery level.
+     * @return True if the device is determined to have just been docked on a wireless
+     * charger, after suppressing spurious docking or undocking signals.
+     */
+    public boolean update(boolean isPowered, int plugType, int batteryLevel) {
+        synchronized (mLock) {
+            final boolean wasPoweredWirelessly = mPoweredWirelessly;
+
+            if (isPowered && plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+                // The device is receiving power from the wireless charger.
+                // Update the rest position asynchronously.
+                mPoweredWirelessly = true;
+                mMustUpdateRestPosition = true;
+                startDetectionLocked();
+            } else {
+                // The device may or may not be on the wireless charger depending on whether
+                // the unplug signal that we received was spurious.
+                mPoweredWirelessly = false;
+                if (mAtRest) {
+                    if (plugType != 0 && plugType != BatteryManager.BATTERY_PLUGGED_WIRELESS) {
+                        // The device was plugged into a new non-wireless power source.
+                        // It's safe to assume that it is no longer on the wireless charger.
+                        mMustUpdateRestPosition = false;
+                        clearAtRestLocked();
+                    } else {
+                        // The device may still be on the wireless charger but we don't know.
+                        // Check whether the device has remained at rest on the charger
+                        // so that we will know to ignore the next wireless plug event
+                        // if needed.
+                        startDetectionLocked();
+                    }
+                }
+            }
+
+            // Report that the device has been docked only if the device just started
+            // receiving power wirelessly, has a high enough battery level that we
+            // can be assured that charging was not delayed due to the battery previously
+            // having been full, and the device is not known to already be at rest
+            // on the wireless charger from earlier.
+            return mPoweredWirelessly && !wasPoweredWirelessly
+                    && batteryLevel < WIRELESS_CHARGER_TURN_ON_BATTERY_LEVEL_LIMIT
+                    && !mAtRest;
+        }
+    }
+
+    private void startDetectionLocked() {
+        if (!mDetectionInProgress && mGravitySensor != null) {
+            if (mSensorManager.registerListener(mListener, mGravitySensor,
+                    SAMPLING_INTERVAL_MILLIS * 1000)) {
+                mSuspendBlocker.acquire();
+                mDetectionInProgress = true;
+                mDetectionStartTime = SystemClock.uptimeMillis();
+                mTotalSamples = 0;
+                mMovingSamples = 0;
+
+                Message msg = Message.obtain(mHandler, mSensorTimeout);
+                msg.setAsynchronous(true);
+                mHandler.sendMessageDelayed(msg, SETTLE_TIME_MILLIS);
+            }
+        }
+    }
+
+    private void finishDetectionLocked() {
+        if (mDetectionInProgress) {
+            mSensorManager.unregisterListener(mListener);
+            mHandler.removeCallbacks(mSensorTimeout);
+
+            if (mMustUpdateRestPosition) {
+                clearAtRestLocked();
+                if (mTotalSamples < MIN_SAMPLES) {
+                    Slog.w(TAG, "Wireless charger detector is broken.  Only received "
+                            + mTotalSamples + " samples from the gravity sensor but we "
+                            + "need at least " + MIN_SAMPLES + " and we expect to see "
+                            + "about " + SETTLE_TIME_MILLIS / SAMPLING_INTERVAL_MILLIS
+                            + " on average.");
+                } else if (mMovingSamples == 0) {
+                    mAtRest = true;
+                    mRestX = mLastSampleX;
+                    mRestY = mLastSampleY;
+                    mRestZ = mLastSampleZ;
+                }
+                mMustUpdateRestPosition = false;
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "New state: mAtRest=" + mAtRest
+                        + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
+                        + ", mTotalSamples=" + mTotalSamples
+                        + ", mMovingSamples=" + mMovingSamples);
+            }
+
+            mDetectionInProgress = false;
+            mSuspendBlocker.release();
+        }
+    }
+
+    private void processSampleLocked(float x, float y, float z) {
+        if (mDetectionInProgress) {
+            mLastSampleX = x;
+            mLastSampleY = y;
+            mLastSampleZ = z;
+
+            mTotalSamples += 1;
+            if (mTotalSamples == 1) {
+                // Save information about the first sample collected.
+                mFirstSampleX = x;
+                mFirstSampleY = y;
+                mFirstSampleZ = z;
+            } else {
+                // Determine whether movement has occurred relative to the first sample.
+                if (hasMoved(mFirstSampleX, mFirstSampleY, mFirstSampleZ, x, y, z)) {
+                    mMovingSamples += 1;
+                }
+            }
+
+            // Clear the at rest flag if movement has occurred relative to the rest sample.
+            if (mAtRest && hasMoved(mRestX, mRestY, mRestZ, x, y, z)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "No longer at rest: "
+                            + "mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ
+                            + ", x=" + x + ", y=" + y + ", z=" + z);
+                }
+                clearAtRestLocked();
+            }
+        }
+    }
+
+    private void clearAtRestLocked() {
+        mAtRest = false;
+        mRestX = 0;
+        mRestY = 0;
+        mRestZ = 0;
+    }
+
+    private static boolean hasMoved(float x1, float y1, float z1,
+            float x2, float y2, float z2) {
+        final double dotProduct = (x1 * x2) + (y1 * y2) + (z1 * z2);
+        final double mag1 = Math.sqrt((x1 * x1) + (y1 * y1) + (z1 * z1));
+        final double mag2 = Math.sqrt((x2 * x2) + (y2 * y2) + (z2 * z2));
+        if (mag1 < MIN_GRAVITY || mag1 > MAX_GRAVITY
+                || mag2 < MIN_GRAVITY || mag2 > MAX_GRAVITY) {
+            if (DEBUG) {
+                Slog.d(TAG, "Weird gravity vector: mag1=" + mag1 + ", mag2=" + mag2);
+            }
+            return true;
+        }
+        final boolean moved = (dotProduct < mag1 * mag2 * MOVEMENT_ANGLE_COS_THRESHOLD);
+        if (DEBUG) {
+            Slog.d(TAG, "Check: moved=" + moved
+                    + ", x1=" + x1 + ", y1=" + y1 + ", z1=" + z1
+                    + ", x2=" + x2 + ", y2=" + y2 + ", z2=" + z2
+                    + ", angle=" + (Math.acos(dotProduct / mag1 / mag2) * 180 / Math.PI)
+                    + ", dotProduct=" + dotProduct
+                    + ", mag1=" + mag1 + ", mag2=" + mag2);
+        }
+        return moved;
+    }
+
+    private final SensorEventListener mListener = new SensorEventListener() {
+        @Override
+        public void onSensorChanged(SensorEvent event) {
+            synchronized (mLock) {
+                processSampleLocked(event.values[0], event.values[1], event.values[2]);
+            }
+        }
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        }
+    };
+
+    private final Runnable mSensorTimeout = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                finishDetectionLocked();
+            }
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/search/SearchManagerService.java b/services/core/java/com/android/server/search/SearchManagerService.java
new file mode 100644
index 0000000..b5d81d1
--- /dev/null
+++ b/services/core/java/com/android/server/search/SearchManagerService.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.search;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.ISearchManager;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * The search manager service handles the search UI, and maintains a registry of searchable
+ * activities.
+ */
+public class SearchManagerService extends ISearchManager.Stub {
+
+    // general debugging support
+    private static final String TAG = "SearchManagerService";
+
+    // Context that the service is running in.
+    private final Context mContext;
+
+    // This field is initialized lazily in getSearchables(), and then never modified.
+    private final SparseArray<Searchables> mSearchables = new SparseArray<Searchables>();
+
+    /**
+     * Initializes the Search Manager service in the provided system context.
+     * Only one instance of this object should be created!
+     *
+     * @param context to use for accessing DB, window manager, etc.
+     */
+    public SearchManagerService(Context context)  {
+        mContext = context;
+        mContext.registerReceiver(new BootCompletedReceiver(),
+                new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+        mContext.registerReceiver(new UserReceiver(),
+                new IntentFilter(Intent.ACTION_USER_REMOVED));
+        new MyPackageMonitor().register(context, null, UserHandle.ALL, true);
+    }
+
+    private Searchables getSearchables(int userId) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            boolean userExists = ((UserManager) mContext.getSystemService(Context.USER_SERVICE))
+                    .getUserInfo(userId) != null;
+            if (!userExists) return null;
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+        synchronized (mSearchables) {
+            Searchables searchables = mSearchables.get(userId);
+
+            if (searchables == null) {
+                //Log.i(TAG, "Building list of searchable activities for userId=" + userId);
+                searchables = new Searchables(mContext, userId);
+                searchables.buildSearchableList();
+                mSearchables.append(userId, searchables);
+            }
+            return searchables;
+        }
+    }
+
+    private void onUserRemoved(int userId) {
+        if (userId != UserHandle.USER_OWNER) {
+            synchronized (mSearchables) {
+                mSearchables.remove(userId);
+            }
+        }
+    }
+
+    /**
+     * Creates the initial searchables list after boot.
+     */
+    private final class BootCompletedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            new Thread() {
+                @Override
+                public void run() {
+                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+                    mContext.unregisterReceiver(BootCompletedReceiver.this);
+                    getSearchables(0);
+                }
+            }.start();
+        }
+    }
+
+    private final class UserReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            onUserRemoved(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_OWNER));
+        }
+    }
+
+    /**
+     * Refreshes the "searchables" list when packages are added/removed.
+     */
+    class MyPackageMonitor extends PackageMonitor {
+
+        @Override
+        public void onSomePackagesChanged() {
+            updateSearchables();
+        }
+
+        @Override
+        public void onPackageModified(String pkg) {
+            updateSearchables();
+        }
+
+        private void updateSearchables() {
+            final int changingUserId = getChangingUserId();
+            synchronized (mSearchables) {
+                // Update list of searchable activities
+                for (int i = 0; i < mSearchables.size(); i++) {
+                    if (changingUserId == mSearchables.keyAt(i)) {
+                        getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+                        break;
+                    }
+                }
+            }
+            // Inform all listeners that the list of searchables has been updated.
+            Intent intent = new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
+                    | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcastAsUser(intent, new UserHandle(changingUserId));
+        }
+    }
+
+    class GlobalSearchProviderObserver extends ContentObserver {
+        private final ContentResolver mResolver;
+
+        public GlobalSearchProviderObserver(ContentResolver resolver) {
+            super(null);
+            mResolver = resolver;
+            mResolver.registerContentObserver(
+                    Settings.Secure.getUriFor(Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY),
+                    false /* notifyDescendants */,
+                    this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            synchronized (mSearchables) {
+                for (int i = 0; i < mSearchables.size(); i++) {
+                    getSearchables(mSearchables.keyAt(i)).buildSearchableList();
+                }
+            }
+            Intent intent = new Intent(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+    }
+
+    //
+    // Searchable activities API
+    //
+
+    /**
+     * Returns the SearchableInfo for a given activity.
+     *
+     * @param launchActivity The activity from which we're launching this search.
+     * @return Returns a SearchableInfo record describing the parameters of the search,
+     * or null if no searchable metadata was available.
+     */
+    public SearchableInfo getSearchableInfo(final ComponentName launchActivity) {
+        if (launchActivity == null) {
+            Log.e(TAG, "getSearchableInfo(), activity == null");
+            return null;
+        }
+        return getSearchables(UserHandle.getCallingUserId()).getSearchableInfo(launchActivity);
+    }
+
+    /**
+     * Returns a list of the searchable activities that can be included in global search.
+     */
+    public List<SearchableInfo> getSearchablesInGlobalSearch() {
+        return getSearchables(UserHandle.getCallingUserId()).getSearchablesInGlobalSearchList();
+    }
+
+    public List<ResolveInfo> getGlobalSearchActivities() {
+        return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivities();
+    }
+
+    /**
+     * Gets the name of the global search activity.
+     */
+    public ComponentName getGlobalSearchActivity() {
+        return getSearchables(UserHandle.getCallingUserId()).getGlobalSearchActivity();
+    }
+
+    /**
+     * Gets the name of the web search activity.
+     */
+    public ComponentName getWebSearchActivity() {
+        return getSearchables(UserHandle.getCallingUserId()).getWebSearchActivity();
+    }
+
+    @Override
+    public ComponentName getAssistIntent(int userHandle) {
+        try {
+            userHandle = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                    Binder.getCallingUid(), userHandle, true, false, "getAssistIntent", null);
+            IPackageManager pm = AppGlobals.getPackageManager();
+            Intent assistIntent = new Intent(Intent.ACTION_ASSIST);
+            ResolveInfo info =
+                    pm.resolveIntent(assistIntent,
+                    assistIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    PackageManager.MATCH_DEFAULT_ONLY, userHandle);
+            if (info != null) {
+                return new ComponentName(
+                        info.activityInfo.applicationInfo.packageName,
+                        info.activityInfo.name);
+            }
+        } catch (RemoteException re) {
+            // Local call
+            Log.e(TAG, "RemoteException in getAssistIntent: " + re);
+        } catch (Exception e) {
+            Log.e(TAG, "Exception in getAssistIntent: " + e);
+        }
+        return null;
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        synchronized (mSearchables) {
+            for (int i = 0; i < mSearchables.size(); i++) {
+                ipw.print("\nUser: "); ipw.println(mSearchables.keyAt(i));
+                ipw.increaseIndent();
+                mSearchables.valueAt(i).dump(fd, ipw, args);
+                ipw.decreaseIndent();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/search/Searchables.java b/services/core/java/com/android/server/search/Searchables.java
new file mode 100644
index 0000000..0ffbb7d
--- /dev/null
+++ b/services/core/java/com/android/server/search/Searchables.java
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.search;
+
+import android.app.AppGlobals;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This class maintains the information about all searchable activities.
+ * This is a hidden class.
+ */
+public class Searchables {
+
+    private static final String LOG_TAG = "Searchables";
+
+    // static strings used for XML lookups, etc.
+    // TODO how should these be documented for the developer, in a more structured way than
+    // the current long wordy javadoc in SearchManager.java ?
+    private static final String MD_LABEL_DEFAULT_SEARCHABLE = "android.app.default_searchable";
+    private static final String MD_SEARCHABLE_SYSTEM_SEARCH = "*";
+
+    private Context mContext;
+
+    private HashMap<ComponentName, SearchableInfo> mSearchablesMap = null;
+    private ArrayList<SearchableInfo> mSearchablesList = null;
+    private ArrayList<SearchableInfo> mSearchablesInGlobalSearchList = null;
+    // Contains all installed activities that handle the global search
+    // intent.
+    private List<ResolveInfo> mGlobalSearchActivities;
+    private ComponentName mCurrentGlobalSearchActivity = null;
+    private ComponentName mWebSearchActivity = null;
+
+    public static String GOOGLE_SEARCH_COMPONENT_NAME =
+            "com.android.googlesearch/.GoogleSearch";
+    public static String ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME =
+            "com.google.android.providers.enhancedgooglesearch/.Launcher";
+
+    // Cache the package manager instance
+    final private IPackageManager mPm;
+    // User for which this Searchables caches information
+    private int mUserId;
+
+    /**
+     *
+     * @param context Context to use for looking up activities etc.
+     */
+    public Searchables (Context context, int userId) {
+        mContext = context;
+        mUserId = userId;
+        mPm = AppGlobals.getPackageManager();
+    }
+
+    /**
+     * Look up, or construct, based on the activity.
+     *
+     * The activities fall into three cases, based on meta-data found in
+     * the manifest entry:
+     * <ol>
+     * <li>The activity itself implements search.  This is indicated by the
+     * presence of a "android.app.searchable" meta-data attribute.
+     * The value is a reference to an XML file containing search information.</li>
+     * <li>A related activity implements search.  This is indicated by the
+     * presence of a "android.app.default_searchable" meta-data attribute.
+     * The value is a string naming the activity implementing search.  In this
+     * case the factory will "redirect" and return the searchable data.</li>
+     * <li>No searchability data is provided.  We return null here and other
+     * code will insert the "default" (e.g. contacts) search.
+     *
+     * TODO: cache the result in the map, and check the map first.
+     * TODO: it might make sense to implement the searchable reference as
+     * an application meta-data entry.  This way we don't have to pepper each
+     * and every activity.
+     * TODO: can we skip the constructor step if it's a non-searchable?
+     * TODO: does it make sense to plug the default into a slot here for
+     * automatic return?  Probably not, but it's one way to do it.
+     *
+     * @param activity The name of the current activity, or null if the
+     * activity does not define any explicit searchable metadata.
+     */
+    public SearchableInfo getSearchableInfo(ComponentName activity) {
+        // Step 1.  Is the result already hashed?  (case 1)
+        SearchableInfo result;
+        synchronized (this) {
+            result = mSearchablesMap.get(activity);
+            if (result != null) return result;
+        }
+
+        // Step 2.  See if the current activity references a searchable.
+        // Note:  Conceptually, this could be a while(true) loop, but there's
+        // no point in implementing reference chaining here and risking a loop.
+        // References must point directly to searchable activities.
+
+        ActivityInfo ai = null;
+        try {
+            ai = mPm.getActivityInfo(activity, PackageManager.GET_META_DATA, mUserId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error getting activity info " + re);
+            return null;
+        }
+        String refActivityName = null;
+
+        // First look for activity-specific reference
+        Bundle md = ai.metaData;
+        if (md != null) {
+            refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+        }
+        // If not found, try for app-wide reference
+        if (refActivityName == null) {
+            md = ai.applicationInfo.metaData;
+            if (md != null) {
+                refActivityName = md.getString(MD_LABEL_DEFAULT_SEARCHABLE);
+            }
+        }
+
+        // Irrespective of source, if a reference was found, follow it.
+        if (refActivityName != null)
+        {
+            // This value is deprecated, return null
+            if (refActivityName.equals(MD_SEARCHABLE_SYSTEM_SEARCH)) {
+                return null;
+            }
+            String pkg = activity.getPackageName();
+            ComponentName referredActivity;
+            if (refActivityName.charAt(0) == '.') {
+                referredActivity = new ComponentName(pkg, pkg + refActivityName);
+            } else {
+                referredActivity = new ComponentName(pkg, refActivityName);
+            }
+
+            // Now try the referred activity, and if found, cache
+            // it against the original name so we can skip the check
+            synchronized (this) {
+                result = mSearchablesMap.get(referredActivity);
+                if (result != null) {
+                    mSearchablesMap.put(activity, result);
+                    return result;
+                }
+            }
+        }
+
+        // Step 3.  None found. Return null.
+        return null;
+
+    }
+
+    /**
+     * Builds an entire list (suitable for display) of
+     * activities that are searchable, by iterating the entire set of
+     * ACTION_SEARCH & ACTION_WEB_SEARCH intents.
+     *
+     * Also clears the hash of all activities -> searches which will
+     * refill as the user clicks "search".
+     *
+     * This should only be done at startup and again if we know that the
+     * list has changed.
+     *
+     * TODO: every activity that provides a ACTION_SEARCH intent should
+     * also provide searchability meta-data.  There are a bunch of checks here
+     * that, if data is not found, silently skip to the next activity.  This
+     * won't help a developer trying to figure out why their activity isn't
+     * showing up in the list, but an exception here is too rough.  I would
+     * like to find a better notification mechanism.
+     *
+     * TODO: sort the list somehow?  UI choice.
+     */
+    public void buildSearchableList() {
+        // These will become the new values at the end of the method
+        HashMap<ComponentName, SearchableInfo> newSearchablesMap
+                                = new HashMap<ComponentName, SearchableInfo>();
+        ArrayList<SearchableInfo> newSearchablesList
+                                = new ArrayList<SearchableInfo>();
+        ArrayList<SearchableInfo> newSearchablesInGlobalSearchList
+                                = new ArrayList<SearchableInfo>();
+
+        // Use intent resolver to generate list of ACTION_SEARCH & ACTION_WEB_SEARCH receivers.
+        List<ResolveInfo> searchList;
+        final Intent intent = new Intent(Intent.ACTION_SEARCH);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            searchList = queryIntentActivities(intent, PackageManager.GET_META_DATA);
+
+            List<ResolveInfo> webSearchInfoList;
+            final Intent webSearchIntent = new Intent(Intent.ACTION_WEB_SEARCH);
+            webSearchInfoList = queryIntentActivities(webSearchIntent, PackageManager.GET_META_DATA);
+
+            // analyze each one, generate a Searchables record, and record
+            if (searchList != null || webSearchInfoList != null) {
+                int search_count = (searchList == null ? 0 : searchList.size());
+                int web_search_count = (webSearchInfoList == null ? 0 : webSearchInfoList.size());
+                int count = search_count + web_search_count;
+                for (int ii = 0; ii < count; ii++) {
+                    // for each component, try to find metadata
+                    ResolveInfo info = (ii < search_count)
+                            ? searchList.get(ii)
+                            : webSearchInfoList.get(ii - search_count);
+                    ActivityInfo ai = info.activityInfo;
+                    // Check first to avoid duplicate entries.
+                    if (newSearchablesMap.get(new ComponentName(ai.packageName, ai.name)) == null) {
+                        SearchableInfo searchable = SearchableInfo.getActivityMetaData(mContext, ai,
+                                mUserId);
+                        if (searchable != null) {
+                            newSearchablesList.add(searchable);
+                            newSearchablesMap.put(searchable.getSearchActivity(), searchable);
+                            if (searchable.shouldIncludeInGlobalSearch()) {
+                                newSearchablesInGlobalSearchList.add(searchable);
+                            }
+                        }
+                    }
+                }
+            }
+
+            List<ResolveInfo> newGlobalSearchActivities = findGlobalSearchActivities();
+
+            // Find the global search activity
+            ComponentName newGlobalSearchActivity = findGlobalSearchActivity(
+                    newGlobalSearchActivities);
+
+            // Find the web search activity
+            ComponentName newWebSearchActivity = findWebSearchActivity(newGlobalSearchActivity);
+
+            // Store a consistent set of new values
+            synchronized (this) {
+                mSearchablesMap = newSearchablesMap;
+                mSearchablesList = newSearchablesList;
+                mSearchablesInGlobalSearchList = newSearchablesInGlobalSearchList;
+                mGlobalSearchActivities = newGlobalSearchActivities;
+                mCurrentGlobalSearchActivity = newGlobalSearchActivity;
+                mWebSearchActivity = newWebSearchActivity;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Returns a sorted list of installed search providers as per
+     * the following heuristics:
+     *
+     * (a) System apps are given priority over non system apps.
+     * (b) Among system apps and non system apps, the relative ordering
+     * is defined by their declared priority.
+     */
+    private List<ResolveInfo> findGlobalSearchActivities() {
+        // Step 1 : Query the package manager for a list
+        // of activities that can handle the GLOBAL_SEARCH intent.
+        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+        List<ResolveInfo> activities =
+                    queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (activities != null && !activities.isEmpty()) {
+            // Step 2: Rank matching activities according to our heuristics.
+            Collections.sort(activities, GLOBAL_SEARCH_RANKER);
+        }
+
+        return activities;
+    }
+
+    /**
+     * Finds the global search activity.
+     */
+    private ComponentName findGlobalSearchActivity(List<ResolveInfo> installed) {
+        // Fetch the global search provider from the system settings,
+        // and if it's still installed, return it.
+        final String searchProviderSetting = getGlobalSearchProviderSetting();
+        if (!TextUtils.isEmpty(searchProviderSetting)) {
+            final ComponentName globalSearchComponent = ComponentName.unflattenFromString(
+                    searchProviderSetting);
+            if (globalSearchComponent != null && isInstalled(globalSearchComponent)) {
+                return globalSearchComponent;
+            }
+        }
+
+        return getDefaultGlobalSearchProvider(installed);
+    }
+
+    /**
+     * Checks whether the global search provider with a given
+     * component name is installed on the system or not. This deals with
+     * cases such as the removal of an installed provider.
+     */
+    private boolean isInstalled(ComponentName globalSearch) {
+        Intent intent = new Intent(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
+        intent.setComponent(globalSearch);
+
+        List<ResolveInfo> activities = queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (activities != null && !activities.isEmpty()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private static final Comparator<ResolveInfo> GLOBAL_SEARCH_RANKER =
+            new Comparator<ResolveInfo>() {
+        @Override
+        public int compare(ResolveInfo lhs, ResolveInfo rhs) {
+            if (lhs == rhs) {
+                return 0;
+            }
+            boolean lhsSystem = isSystemApp(lhs);
+            boolean rhsSystem = isSystemApp(rhs);
+
+            if (lhsSystem && !rhsSystem) {
+                return -1;
+            } else if (rhsSystem && !lhsSystem) {
+                return 1;
+            } else {
+                // Either both system engines, or both non system
+                // engines.
+                //
+                // Note, this isn't a typo. Higher priority numbers imply
+                // higher priority, but are "lower" in the sort order.
+                return rhs.priority - lhs.priority;
+            }
+        }
+    };
+
+    /**
+     * @return true iff. the resolve info corresponds to a system application.
+     */
+    private static final boolean isSystemApp(ResolveInfo res) {
+        return (res.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+    }
+
+    /**
+     * Returns the highest ranked search provider as per the
+     * ranking defined in {@link #getGlobalSearchActivities()}.
+     */
+    private ComponentName getDefaultGlobalSearchProvider(List<ResolveInfo> providerList) {
+        if (providerList != null && !providerList.isEmpty()) {
+            ActivityInfo ai = providerList.get(0).activityInfo;
+            return new ComponentName(ai.packageName, ai.name);
+        }
+
+        Log.w(LOG_TAG, "No global search activity found");
+        return null;
+    }
+
+    private String getGlobalSearchProviderSetting() {
+        return Settings.Secure.getString(mContext.getContentResolver(),
+                Settings.Secure.SEARCH_GLOBAL_SEARCH_ACTIVITY);
+    }
+
+    /**
+     * Finds the web search activity.
+     *
+     * Only looks in the package of the global search activity.
+     */
+    private ComponentName findWebSearchActivity(ComponentName globalSearchActivity) {
+        if (globalSearchActivity == null) {
+            return null;
+        }
+        Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
+        intent.setPackage(globalSearchActivity.getPackageName());
+        List<ResolveInfo> activities =
+                queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+
+        if (activities != null && !activities.isEmpty()) {
+            ActivityInfo ai = activities.get(0).activityInfo;
+            // TODO: do some sanity checks here?
+            return new ComponentName(ai.packageName, ai.name);
+        }
+        Log.w(LOG_TAG, "No web search activity found");
+        return null;
+    }
+
+    private List<ResolveInfo> queryIntentActivities(Intent intent, int flags) {
+        List<ResolveInfo> activities = null;
+        try {
+            activities =
+                    mPm.queryIntentActivities(intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    flags, mUserId);
+        } catch (RemoteException re) {
+            // Local call
+        }
+        return activities;
+    }
+
+    /**
+     * Returns the list of searchable activities.
+     */
+    public synchronized ArrayList<SearchableInfo> getSearchablesList() {
+        ArrayList<SearchableInfo> result = new ArrayList<SearchableInfo>(mSearchablesList);
+        return result;
+    }
+
+    /**
+     * Returns a list of the searchable activities that can be included in global search.
+     */
+    public synchronized ArrayList<SearchableInfo> getSearchablesInGlobalSearchList() {
+        return new ArrayList<SearchableInfo>(mSearchablesInGlobalSearchList);
+    }
+
+    /**
+     * Returns a list of activities that handle the global search intent.
+     */
+    public synchronized ArrayList<ResolveInfo> getGlobalSearchActivities() {
+        return new ArrayList<ResolveInfo>(mGlobalSearchActivities);
+    }
+
+    /**
+     * Gets the name of the global search activity.
+     */
+    public synchronized ComponentName getGlobalSearchActivity() {
+        return mCurrentGlobalSearchActivity;
+    }
+
+    /**
+     * Gets the name of the web search activity.
+     */
+    public synchronized ComponentName getWebSearchActivity() {
+        return mWebSearchActivity;
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("Searchable authorities:");
+        synchronized (this) {
+            if (mSearchablesList != null) {
+                for (SearchableInfo info: mSearchablesList) {
+                    pw.print("  "); pw.println(info.getSuggestAuthority());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
new file mode 100644
index 0000000..4f75189
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
new file mode 100644
index 0000000..2ae467e
--- /dev/null
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -0,0 +1,701 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.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.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;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A note on locking:  We rely on the fact that calls onto mBar are oneway or
+ * if they are local, that they just enqueue messages to not deadlock.
+ */
+public class StatusBarManagerService extends IStatusBarService.Stub
+    implements WindowManagerService.OnHardKeyboardStatusChangeListener
+{
+    private static final String TAG = "StatusBarManagerService";
+    private static final boolean SPEW = false;
+
+    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
+    private final ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>();
+    private IBinder mSysUiVisToken = new Binder();
+    private int mDisabled = 0;
+
+    private Object mLock = new Object();
+    // encompasses lights-out mode and other flags defined on View
+    private int mSystemUiVisibility = 0;
+    private boolean mMenuVisible = false;
+    private int mImeWindowVis = 0;
+    private int mImeBackDisposition;
+    private IBinder mImeToken = null;
+    private int mCurrentUserId;
+
+    private class DisableRecord implements IBinder.DeathRecipient {
+        int userId;
+        String pkg;
+        int what;
+        IBinder token;
+
+        public void binderDied() {
+            Slog.i(TAG, "binder died for pkg=" + pkg);
+            disableInternal(userId, 0, token, pkg);
+            token.unlinkToDeath(this, 0);
+        }
+    }
+
+    /**
+     * Construct the service, add the status bar view to the window manager
+     */
+    public StatusBarManagerService(Context context, WindowManagerService windowManager) {
+        mContext = context;
+        mWindowManager = windowManager;
+        mWindowManager.setOnHardKeyboardStatusChangeListener(this);
+
+        final Resources res = context.getResources();
+        mIcons.defineSlots(res.getStringArray(com.android.internal.R.array.config_statusBarIcons));
+
+        LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
+    }
+
+    /**
+     * Private API used by NotificationManagerService.
+     */
+    private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
+        @Override
+        public void setNotificationDelegate(NotificationDelegate delegate) {
+            synchronized (mNotifications) {
+                mNotificationDelegate = delegate;
+            }
+        }
+
+        @Override
+        public IBinder addNotification(StatusBarNotification notification) {
+            synchronized (mNotifications) {
+                IBinder key = new Binder();
+                mNotifications.put(key, notification);
+                if (mBar != null) {
+                    try {
+                        mBar.addNotification(key, notification);
+                    } catch (RemoteException ex) {
+                    }
+                }
+                return key;
+            }
+        }
+
+        @Override
+        public void updateNotification(IBinder key, StatusBarNotification notification) {
+            synchronized (mNotifications) {
+                if (!mNotifications.containsKey(key)) {
+                    throw new IllegalArgumentException("updateNotification key not found: " + key);
+                }
+                mNotifications.put(key, notification);
+                if (mBar != null) {
+                    try {
+                        mBar.updateNotification(key, notification);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void removeNotification(IBinder key) {
+            synchronized (mNotifications) {
+                final StatusBarNotification n = mNotifications.remove(key);
+                if (n == null) {
+                    Slog.e(TAG, "removeNotification key not found: " + key);
+                    return;
+                }
+                if (mBar != null) {
+                    try {
+                        mBar.removeNotification(key);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+    };
+
+    // ================================================================================
+    // From IStatusBarService
+    // ================================================================================
+    @Override
+    public void expandNotificationsPanel() {
+        enforceExpandStatusBar();
+
+        if (mBar != null) {
+            try {
+                mBar.animateExpandNotificationsPanel();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
+    public void collapsePanels() {
+        enforceExpandStatusBar();
+
+        if (mBar != null) {
+            try {
+                mBar.animateCollapsePanels();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
+    public void expandSettingsPanel() {
+        enforceExpandStatusBar();
+
+        if (mBar != null) {
+            try {
+                mBar.animateExpandSettingsPanel();
+            } catch (RemoteException ex) {
+            }
+        }
+    }
+
+    @Override
+    public void disable(int what, IBinder token, String pkg) {
+        disableInternal(mCurrentUserId, what, token, pkg);
+    }
+
+    private void disableInternal(int userId, int what, IBinder token, String pkg) {
+        enforceStatusBar();
+
+        synchronized (mLock) {
+            disableLocked(userId, what, token, pkg);
+        }
+    }
+
+    private void disableLocked(int userId, int what, IBinder token, String pkg) {
+        // It's important that the the callback and the call to mBar get done
+        // in the same order when multiple threads are calling this function
+        // so they are paired correctly.  The messages on the handler will be
+        // handled in the order they were enqueued, but will be outside the lock.
+        manageDisableListLocked(userId, what, token, pkg);
+
+        // Ensure state for the current user is applied, even if passed a non-current user.
+        final int net = gatherDisableActionsLocked(mCurrentUserId);
+        if (net != mDisabled) {
+            mDisabled = net;
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        mNotificationDelegate.onSetDisabled(net);
+                    }
+                });
+            if (mBar != null) {
+                try {
+                    mBar.disable(net);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setIcon(String slot, String iconPackage, int iconId, int iconLevel,
+            String contentDescription) {
+        enforceStatusBar();
+
+        synchronized (mIcons) {
+            int index = mIcons.getSlotIndex(slot);
+            if (index < 0) {
+                throw new SecurityException("invalid status bar icon slot: " + slot);
+            }
+
+            StatusBarIcon icon = new StatusBarIcon(iconPackage, UserHandle.OWNER, iconId,
+                    iconLevel, 0,
+                    contentDescription);
+            //Slog.d(TAG, "setIcon slot=" + slot + " index=" + index + " icon=" + icon);
+            mIcons.setIcon(index, icon);
+
+            if (mBar != null) {
+                try {
+                    mBar.setIcon(index, icon);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setIconVisibility(String slot, boolean visible) {
+        enforceStatusBar();
+
+        synchronized (mIcons) {
+            int index = mIcons.getSlotIndex(slot);
+            if (index < 0) {
+                throw new SecurityException("invalid status bar icon slot: " + slot);
+            }
+
+            StatusBarIcon icon = mIcons.getIcon(index);
+            if (icon == null) {
+                return;
+            }
+
+            if (icon.visible != visible) {
+                icon.visible = visible;
+
+                if (mBar != null) {
+                    try {
+                        mBar.setIcon(index, icon);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void removeIcon(String slot) {
+        enforceStatusBar();
+
+        synchronized (mIcons) {
+            int index = mIcons.getSlotIndex(slot);
+            if (index < 0) {
+                throw new SecurityException("invalid status bar icon slot: " + slot);
+            }
+
+            mIcons.removeIcon(index);
+
+            if (mBar != null) {
+                try {
+                    mBar.removeIcon(index);
+                } catch (RemoteException ex) {
+                }
+            }
+        }
+    }
+
+    /** 
+     * Hide or show the on-screen Menu key. Only call this from the window manager, typically in
+     * response to a window with FLAG_NEEDS_MENU_KEY set.
+     */
+    @Override
+    public void topAppWindowChanged(final boolean menuVisible) {
+        enforceStatusBar();
+
+        if (SPEW) Slog.d(TAG, (menuVisible?"showing":"hiding") + " MENU key");
+
+        synchronized(mLock) {
+            mMenuVisible = menuVisible;
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mBar != null) {
+                            try {
+                                mBar.topAppWindowChanged(menuVisible);
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                    }
+                });
+        }
+    }
+
+    @Override
+    public void setImeWindowStatus(final IBinder token, final int vis, final int backDisposition) {
+        enforceStatusBar();
+
+        if (SPEW) {
+            Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition);
+        }
+
+        synchronized(mLock) {
+            // In case of IME change, we need to call up setImeWindowStatus() regardless of
+            // mImeWindowVis because mImeWindowVis may not have been set to false when the
+            // previous IME was destroyed.
+            mImeWindowVis = vis;
+            mImeBackDisposition = backDisposition;
+            mImeToken = token;
+            mHandler.post(new Runnable() {
+                public void run() {
+                    if (mBar != null) {
+                        try {
+                            mBar.setImeWindowStatus(token, vis, backDisposition);
+                        } catch (RemoteException ex) {
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void setSystemUiVisibility(int vis, int mask) {
+        // also allows calls from window manager which is in this process.
+        enforceStatusBarService();
+
+        if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")");
+
+        synchronized (mLock) {
+            updateUiVisibilityLocked(vis, mask);
+            disableLocked(
+                    mCurrentUserId,
+                    vis & StatusBarManager.DISABLE_MASK,
+                    mSysUiVisToken,
+                    "WindowManager.LayoutParams");
+        }
+    }
+
+    private void updateUiVisibilityLocked(final int vis, final int mask) {
+        if (mSystemUiVisibility != vis) {
+            mSystemUiVisibility = vis;
+            mHandler.post(new Runnable() {
+                    public void run() {
+                        if (mBar != null) {
+                            try {
+                                mBar.setSystemUiVisibility(vis, mask);
+                            } catch (RemoteException ex) {
+                            }
+                        }
+                    }
+                });
+        }
+    }
+
+    @Override
+    public void setHardKeyboardEnabled(final boolean enabled) {
+        mHandler.post(new Runnable() {
+            public void run() {
+                mWindowManager.setHardKeyboardEnabled(enabled);
+            }
+        });
+    }
+
+    @Override
+    public void onHardKeyboardStatusChange(final boolean available, final boolean enabled) {
+        mHandler.post(new Runnable() {
+            public void run() {
+                if (mBar != null) {
+                    try {
+                        mBar.setHardKeyboardStatus(available, enabled);
+                    } catch (RemoteException ex) {
+                    }
+                }
+            }
+        });
+    }
+
+    @Override
+    public void toggleRecentApps() {
+        if (mBar != null) {
+            try {
+                mBar.toggleRecentApps();
+            } catch (RemoteException ex) {}
+        }
+    }
+
+    @Override
+    public void preloadRecentApps() {
+        if (mBar != null) {
+            try {
+                mBar.preloadRecentApps();
+            } catch (RemoteException ex) {}
+        }
+    }
+
+    @Override
+    public void cancelPreloadRecentApps() {
+        if (mBar != null) {
+            try {
+                mBar.cancelPreloadRecentApps();
+            } catch (RemoteException ex) {}
+        }
+    }
+
+    @Override
+    public void setCurrentUser(int newUserId) {
+        if (SPEW) Slog.d(TAG, "Setting current user to user " + newUserId);
+        mCurrentUserId = newUserId;
+    }
+
+    @Override
+    public void setWindowState(int window, int state) {
+        if (mBar != null) {
+            try {
+                mBar.setWindowState(window, state);
+            } catch (RemoteException ex) {}
+        }
+    }
+
+    private void enforceStatusBar() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR,
+                "StatusBarManagerService");
+    }
+
+    private void enforceExpandStatusBar() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.EXPAND_STATUS_BAR,
+                "StatusBarManagerService");
+    }
+
+    private void enforceStatusBarService() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
+                "StatusBarManagerService");
+    }
+
+    // ================================================================================
+    // Callbacks from the status bar service.
+    // ================================================================================
+    @Override
+    public void registerStatusBar(IStatusBar bar, StatusBarIconList iconList,
+            List<IBinder> notificationKeys, List<StatusBarNotification> notifications,
+            int switches[], List<IBinder> binders) {
+        enforceStatusBarService();
+
+        Slog.i(TAG, "registerStatusBar bar=" + bar);
+        mBar = bar;
+        synchronized (mIcons) {
+            iconList.copyFrom(mIcons);
+        }
+        synchronized (mNotifications) {
+            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
+                notificationKeys.add(e.getKey());
+                notifications.add(e.getValue());
+            }
+        }
+        synchronized (mLock) {
+            switches[0] = gatherDisableActionsLocked(mCurrentUserId);
+            switches[1] = mSystemUiVisibility;
+            switches[2] = mMenuVisible ? 1 : 0;
+            switches[3] = mImeWindowVis;
+            switches[4] = mImeBackDisposition;
+            binders.add(mImeToken);
+        }
+        switches[5] = mWindowManager.isHardKeyboardAvailable() ? 1 : 0;
+        switches[6] = mWindowManager.isHardKeyboardEnabled() ? 1 : 0;
+    }
+
+    /**
+     * The status bar service should call this each time the user brings the panel from
+     * invisible to visible in order to clear the notification light.
+     */
+    @Override
+    public void onPanelRevealed() {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // tell the notification manager to turn off the lights.
+            mNotificationDelegate.onPanelRevealed();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationClick(String pkg, String tag, int id) {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationClick(pkg, tag, id);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationError(String pkg, String tag, int id,
+            int uid, int initialPid, String message) {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            // WARNING: this will call back into us to do the remove.  Don't hold any locks.
+            mNotificationDelegate.onNotificationError(pkg, tag, id, uid, initialPid, message);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationClear(String pkg, String tag, int id) {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationClear(pkg, tag, id);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onClearAllNotifications() {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onClearAll();
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+
+    // ================================================================================
+    // Can be called from any thread
+    // ================================================================================
+
+    // lock on mDisableRecords
+    void manageDisableListLocked(int userId, int what, IBinder token, String pkg) {
+        if (SPEW) {
+            Slog.d(TAG, "manageDisableList userId=" + userId
+                    + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
+        }
+        // update the list
+        final int N = mDisableRecords.size();
+        DisableRecord tok = null;
+        int i;
+        for (i=0; i<N; i++) {
+            DisableRecord t = mDisableRecords.get(i);
+            if (t.token == token && t.userId == userId) {
+                tok = t;
+                break;
+            }
+        }
+        if (what == 0 || !token.isBinderAlive()) {
+            if (tok != null) {
+                mDisableRecords.remove(i);
+                tok.token.unlinkToDeath(tok, 0);
+            }
+        } else {
+            if (tok == null) {
+                tok = new DisableRecord();
+                tok.userId = userId;
+                try {
+                    token.linkToDeath(tok, 0);
+                }
+                catch (RemoteException ex) {
+                    return; // give up
+                }
+                mDisableRecords.add(tok);
+            }
+            tok.what = what;
+            tok.token = token;
+            tok.pkg = pkg;
+        }
+    }
+
+    // lock on mDisableRecords
+    int gatherDisableActionsLocked(int userId) {
+        final int N = mDisableRecords.size();
+        // gather the new net flags
+        int net = 0;
+        for (int i=0; i<N; i++) {
+            final DisableRecord rec = mDisableRecords.get(i);
+            if (rec.userId == userId) {
+                net |= rec.what;
+            }
+        }
+        return net;
+    }
+
+    // ================================================================================
+    // Always called from UI thread
+    // ================================================================================
+
+    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 StatusBar from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mIcons) {
+            mIcons.dump(pw);
+        }
+
+        synchronized (mNotifications) {
+            int i=0;
+            pw.println("Notification list:");
+            for (Map.Entry<IBinder,StatusBarNotification> e: mNotifications.entrySet()) {
+                pw.printf("  %2d: %s\n", i, e.getValue().toString());
+                i++;
+            }
+        }
+
+        synchronized (mLock) {
+            pw.println("  mDisabled=0x" + Integer.toHexString(mDisabled));
+            final int N = mDisableRecords.size();
+            pw.println("  mDisableRecords.size=" + N);
+            for (int i=0; i<N; i++) {
+                DisableRecord tok = mDisableRecords.get(i);
+                pw.println("    [" + i + "] userId=" + tok.userId
+                                + " what=0x" + Integer.toHexString(tok.what)
+                                + " pkg=" + tok.pkg
+                                + " token=" + tok.token);
+            }
+        }
+    }
+
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)
+                    || Intent.ACTION_SCREEN_OFF.equals(action)) {
+                collapsePanels();
+            }
+            /*
+            else if (Telephony.Intents.SPN_STRINGS_UPDATED_ACTION.equals(action)) {
+                updateNetworkName(intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_SPN, false),
+                        intent.getStringExtra(Telephony.Intents.EXTRA_SPN),
+                        intent.getBooleanExtra(Telephony.Intents.EXTRA_SHOW_PLMN, false),
+                        intent.getStringExtra(Telephony.Intents.EXTRA_PLMN));
+            }
+            else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) {
+                updateResources();
+            }
+            */
+        }
+    };
+
+}
diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorInternal.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorInternal.java
new file mode 100644
index 0000000..a91a81b
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
new file mode 100644
index 0000000..8805084
--- /dev/null
+++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2007-2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.storage;
+
+import com.android.server.EventLogTags;
+import com.android.server.SystemService;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.FileObserver;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.provider.Settings;
+import android.text.format.Formatter;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.TimeUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * This class implements a service to monitor the amount of disk
+ * storage space on the device.  If the free storage on device is less
+ * than a tunable threshold value (a secure settings parameter;
+ * default 10%) a low memory notification is displayed to alert the
+ * user. If the user clicks on the low memory notification the
+ * Application Manager application gets launched to let the user free
+ * storage space.
+ *
+ * Event log events: A low memory event with the free storage on
+ * device in bytes is logged to the event log when the device goes low
+ * on storage space.  The amount of free storage on the device is
+ * periodically logged to the event log. The log interval is a secure
+ * settings parameter with a default value of 12 hours.  When the free
+ * storage differential goes below a threshold (again a secure
+ * settings parameter with a default value of 2MB), the free memory is
+ * logged to the event log.
+ */
+public class DeviceStorageMonitorService extends SystemService {
+    static final String TAG = "DeviceStorageMonitorService";
+
+    static final boolean DEBUG = false;
+    static final boolean localLOGV = false;
+
+    static final int DEVICE_MEMORY_WHAT = 1;
+    private static final int MONITOR_INTERVAL = 1; //in minutes
+    private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
+
+    private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
+    private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
+    private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
+
+    private long mFreeMem;  // on /data
+    private long mFreeMemAfterLastCacheClear;  // on /data
+    private long mLastReportedFreeMem;
+    private long mLastReportedFreeMemTime;
+    boolean mLowMemFlag=false;
+    private boolean mMemFullFlag=false;
+    private ContentResolver mResolver;
+    private long mTotalMemory;  // on /data
+    private StatFs mDataFileStats;
+    private StatFs mSystemFileStats;
+    private StatFs mCacheFileStats;
+
+    private static final File DATA_PATH = Environment.getDataDirectory();
+    private static final File SYSTEM_PATH = Environment.getRootDirectory();
+    private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
+
+    private long mThreadStartTime = -1;
+    boolean mClearSucceeded = false;
+    boolean mClearingCache;
+    private Intent mStorageLowIntent;
+    private Intent mStorageOkIntent;
+    private Intent mStorageFullIntent;
+    private Intent mStorageNotFullIntent;
+    private CachePackageDataObserver mClearCacheObserver;
+    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.
+    long mMemLowThreshold;
+    // This is the threshold at which we start trying to flush caches
+    // to get below the low threshold limit.  It is less than the low
+    // threshold; we will allow storage to get a bit beyond the limit
+    // before flushing and checking if we are actually low.
+    private long mMemCacheStartTrimThreshold;
+    // This is the threshold that we try to get to when deleting cache
+    // files.  This is greater than the low threshold so that we will flush
+    // more files than absolutely needed, to reduce the frequency that
+    // flushing takes place.
+    private long mMemCacheTrimToThreshold;
+    private long mMemFullThreshold;
+
+    /**
+     * This string is used for ServiceManager access to this class.
+     */
+    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
+    */
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            //don't handle an invalid message
+            if (msg.what != DEVICE_MEMORY_WHAT) {
+                Slog.e(TAG, "Will not process invalid message");
+                return;
+            }
+            checkMemory(msg.arg1 == _TRUE);
+        }
+    };
+
+    private class CachePackageDataObserver extends IPackageDataObserver.Stub {
+        public void onRemoveCompleted(String packageName, boolean succeeded) {
+            mClearSucceeded = succeeded;
+            mClearingCache = false;
+            if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
+                    +", mClearingCache:"+mClearingCache+" Forcing memory check");
+            postCheckMemoryMsg(false, 0);
+        }
+    }
+
+    private void restatDataDir() {
+        try {
+            mDataFileStats.restat(DATA_PATH.getAbsolutePath());
+            mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
+                mDataFileStats.getBlockSize();
+        } catch (IllegalArgumentException e) {
+            // use the old value of mFreeMem
+        }
+        // Allow freemem to be overridden by debug.freemem for testing
+        String debugFreeMem = SystemProperties.get("debug.freemem");
+        if (!"".equals(debugFreeMem)) {
+            mFreeMem = Long.parseLong(debugFreeMem);
+        }
+        // Read the log interval from secure settings
+        long freeMemLogInterval = Settings.Global.getLong(mResolver,
+                Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
+                DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
+        //log the amount of free memory in event log
+        long currTime = SystemClock.elapsedRealtime();
+        if((mLastReportedFreeMemTime == 0) ||
+           (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
+            mLastReportedFreeMemTime = currTime;
+            long mFreeSystem = -1, mFreeCache = -1;
+            try {
+                mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath());
+                mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
+                    mSystemFileStats.getBlockSize();
+            } catch (IllegalArgumentException e) {
+                // ignore; report -1
+            }
+            try {
+                mCacheFileStats.restat(CACHE_PATH.getAbsolutePath());
+                mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
+                    mCacheFileStats.getBlockSize();
+            } catch (IllegalArgumentException e) {
+                // ignore; report -1
+            }
+            EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
+                                mFreeMem, mFreeSystem, mFreeCache);
+        }
+        // Read the reporting threshold from secure settings
+        long threshold = Settings.Global.getLong(mResolver,
+                Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+                DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
+        // If mFree changed significantly log the new value
+        long delta = mFreeMem - mLastReportedFreeMem;
+        if (delta > threshold || delta < -threshold) {
+            mLastReportedFreeMem = mFreeMem;
+            EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
+        }
+    }
+
+    private void clearCache() {
+        if (mClearCacheObserver == null) {
+            // Lazy instantiation
+            mClearCacheObserver = new CachePackageDataObserver();
+        }
+        mClearingCache = true;
+        try {
+            if (localLOGV) Slog.i(TAG, "Clearing cache");
+            IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
+                    freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
+            mClearingCache = false;
+            mClearSucceeded = false;
+        }
+    }
+
+    void checkMemory(boolean checkCache) {
+        //if the thread that was started to clear cache is still running do nothing till its
+        //finished clearing cache. Ideally this flag could be modified by clearCache
+        // and should be accessed via a lock but even if it does this test will fail now and
+        //hopefully the next time this flag will be set to the correct value.
+        if(mClearingCache) {
+            if(localLOGV) Slog.i(TAG, "Thread already running just skip");
+            //make sure the thread is not hung for too long
+            long diffTime = System.currentTimeMillis() - mThreadStartTime;
+            if(diffTime > (10*60*1000)) {
+                Slog.w(TAG, "Thread that clears cache file seems to run for ever");
+            }
+        } else {
+            restatDataDir();
+            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
+
+            //post intent to NotificationManager to display icon if necessary
+            if (mFreeMem < mMemLowThreshold) {
+                if (checkCache) {
+                    // We are allowed to clear cache files at this point to
+                    // try to get down below the limit, because this is not
+                    // the initial call after a cache clear has been attempted.
+                    // In this case we will try a cache clear if our free
+                    // space has gone below the cache clear limit.
+                    if (mFreeMem < mMemCacheStartTrimThreshold) {
+                        // We only clear the cache if the free storage has changed
+                        // a significant amount since the last time.
+                        if ((mFreeMemAfterLastCacheClear-mFreeMem)
+                                >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
+                            // See if clearing cache helps
+                            // Note that clearing cache is asynchronous and so we do a
+                            // memory check again once the cache has been cleared.
+                            mThreadStartTime = System.currentTimeMillis();
+                            mClearSucceeded = false;
+                            clearCache();
+                        }
+                    }
+                } else {
+                    // This is a call from after clearing the cache.  Note
+                    // the amount of free storage at this point.
+                    mFreeMemAfterLastCacheClear = mFreeMem;
+                    if (!mLowMemFlag) {
+                        // We tried to clear the cache, but that didn't get us
+                        // below the low storage limit.  Tell the user.
+                        Slog.i(TAG, "Running low on memory. Sending notification");
+                        sendNotification();
+                        mLowMemFlag = true;
+                    } else {
+                        if (localLOGV) Slog.v(TAG, "Running low on memory " +
+                                "notification already sent. do nothing");
+                    }
+                }
+            } else {
+                mFreeMemAfterLastCacheClear = mFreeMem;
+                if (mLowMemFlag) {
+                    Slog.i(TAG, "Memory available. Cancelling notification");
+                    cancelNotification();
+                    mLowMemFlag = false;
+                }
+            }
+            if (mFreeMem < mMemFullThreshold) {
+                if (!mMemFullFlag) {
+                    sendFullNotification();
+                    mMemFullFlag = true;
+                }
+            } else {
+                if (mMemFullFlag) {
+                    cancelFullNotification();
+                    mMemFullFlag = false;
+                }
+            }
+        }
+        if(localLOGV) Slog.i(TAG, "Posting Message again");
+        //keep posting messages to itself periodically
+        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
+    }
+
+    void postCheckMemoryMsg(boolean clearCache, long delay) {
+        // Remove queued messages
+        mHandler.removeMessages(DEVICE_MEMORY_WHAT);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
+                clearCache ?_TRUE : _FALSE, 0),
+                delay);
+    }
+
+    /**
+    * Constructor to run service. initializes the disk space threshold value
+    * and posts an empty message to kickstart the process.
+    */
+    @Override
+    public void onCreate(Context context) {
+        mLastReportedFreeMemTime = 0;
+        mResolver = context.getContentResolver();
+        //create StatFs object
+        mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
+        mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
+        mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath());
+        //initialize total storage on device
+        mTotalMemory = (long)mDataFileStats.getBlockCount() *
+                        mDataFileStats.getBlockSize();
+        mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
+        mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
+        mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
+        mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
+        mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+    }
+
+    @Override
+    public void onStart() {
+        // cache storage thresholds
+        final StorageManager sm = StorageManager.from(getContext());
+        mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
+        mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
+
+        mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
+        mMemCacheTrimToThreshold = mMemLowThreshold
+                + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
+        mFreeMemAfterLastCacheClear = mTotalMemory;
+        checkMemory(true);
+
+        mCacheFileDeletedObserver = new CacheFileDeletedObserver();
+        mCacheFileDeletedObserver.startWatching();
+
+        publishBinderService(SERVICE, mRemoteService);
+        publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
+    }
+
+    private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
+        @Override
+        public void checkMemory() {
+            // force an early check
+            postCheckMemoryMsg(true, 0);
+        }
+
+        @Override
+        public boolean isMemoryLow() {
+            return mLowMemFlag;
+        }
+
+        @Override
+        public long getMemoryLowThreshold() {
+            return mMemLowThreshold;
+        }
+    };
+
+    private final IBinder mRemoteService = new Binder() {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                    != PackageManager.PERMISSION_GRANTED) {
+
+                pw.println("Permission Denial: can't dump " + SERVICE + " from from pid="
+                        + Binder.getCallingPid()
+                        + ", uid=" + Binder.getCallingUid());
+                return;
+            }
+
+            dumpImpl(pw);
+        }
+    };
+
+    void dumpImpl(PrintWriter pw) {
+        final Context context = getContext();
+
+        pw.println("Current DeviceStorageMonitor state:");
+
+        pw.print("  mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
+        pw.print(" mTotalMemory=");
+        pw.println(Formatter.formatFileSize(context, mTotalMemory));
+
+        pw.print("  mFreeMemAfterLastCacheClear=");
+        pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
+
+        pw.print("  mLastReportedFreeMem=");
+        pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
+        pw.print(" mLastReportedFreeMemTime=");
+        TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
+        pw.println();
+
+        pw.print("  mLowMemFlag="); pw.print(mLowMemFlag);
+        pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
+
+        pw.print("  mClearSucceeded="); pw.print(mClearSucceeded);
+        pw.print(" mClearingCache="); pw.println(mClearingCache);
+
+        pw.print("  mMemLowThreshold=");
+        pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
+        pw.print(" mMemFullThreshold=");
+        pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
+
+        pw.print("  mMemCacheStartTrimThreshold=");
+        pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
+        pw.print(" mMemCacheTrimToThreshold=");
+        pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
+    }
+
+    /**
+    * This method sends a notification to NotificationManager to display
+    * an error dialog indicating low disk space and launch the Installer
+    * application
+    */
+    private void sendNotification() {
+        final Context context = getContext();
+        if(localLOGV) Slog.i(TAG, "Sending low memory notification");
+        //log the event to event log with the amount of free storage(in bytes) left on the device
+        EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
+        //  Pack up the values and broadcast them to everyone
+        Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
+                ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
+                : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
+        lowMemIntent.putExtra("memory", mFreeMem);
+        lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        NotificationManager mNotificationMgr =
+                (NotificationManager)context.getSystemService(
+                        Context.NOTIFICATION_SERVICE);
+        CharSequence title = context.getText(
+                com.android.internal.R.string.low_internal_storage_view_title);
+        CharSequence details = context.getText(
+                com.android.internal.R.string.low_internal_storage_view_text);
+        PendingIntent intent = PendingIntent.getActivityAsUser(context, 0,  lowMemIntent, 0,
+                null, UserHandle.CURRENT);
+        Notification notification = new Notification();
+        notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
+        notification.tickerText = title;
+        notification.flags |= Notification.FLAG_NO_CLEAR;
+        notification.setLatestEventInfo(context, title, details, intent);
+        mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
+                UserHandle.ALL);
+        context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+    }
+
+    /**
+     * Cancels low storage notification and sends OK intent.
+     */
+    private void cancelNotification() {
+        final Context context = getContext();
+        if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
+        NotificationManager mNotificationMgr =
+                (NotificationManager)context.getSystemService(
+                        Context.NOTIFICATION_SERVICE);
+        //cancel notification since memory has been freed
+        mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
+
+        context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
+        context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
+    }
+
+    /**
+     * Send a notification when storage is full.
+     */
+    private void sendFullNotification() {
+        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
+        getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+    }
+
+    /**
+     * Cancels memory full notification and sends "not full" intent.
+     */
+    private void cancelFullNotification() {
+        if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
+        getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
+        getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
+    }
+
+    private static class CacheFileDeletedObserver extends FileObserver {
+        public CacheFileDeletedObserver() {
+            super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            EventLogTags.writeCacheFileDeleted(path);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/twilight/TwilightListener.java b/services/core/java/com/android/server/twilight/TwilightListener.java
new file mode 100644
index 0000000..29ead44
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/twilight/TwilightManager.java b/services/core/java/com/android/server/twilight/TwilightManager.java
new file mode 100644
index 0000000..b3de58b
--- /dev/null
+++ b/services/core/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/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
new file mode 100644
index 0000000..8feb97b
--- /dev/null
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -0,0 +1,467 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.twilight;
+
+import com.android.server.SystemService;
+import com.android.server.TwilightCalculator;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import libcore.util.Objects;
+
+/**
+ * Figures out whether it's twilight time based on the user's location.
+ *
+ * Used by the UI mode manager and other components to adjust night mode
+ * effects based on sunrise and sunset.
+ */
+public final class TwilightService extends SystemService {
+    static final String TAG = "TwilightService";
+    static final boolean DEBUG = false;
+    static final String ACTION_UPDATE_TWILIGHT_STATE =
+            "com.android.server.action.UPDATE_TWILIGHT_STATE";
+
+    final Object mLock = new Object();
+
+    AlarmManager mAlarmManager;
+    LocationManager mLocationManager;
+    LocationHandler mLocationHandler;
+
+    final ArrayList<TwilightListenerRecord> mListeners =
+            new ArrayList<TwilightListenerRecord>();
+
+    TwilightState mTwilightState;
+
+    @Override
+    public void onStart() {
+        mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+        mLocationManager = (LocationManager) getContext().getSystemService(
+                Context.LOCATION_SERVICE);
+        mLocationHandler = new LocationHandler();
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        filter.addAction(Intent.ACTION_TIME_CHANGED);
+        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+        filter.addAction(ACTION_UPDATE_TWILIGHT_STATE);
+        getContext().registerReceiver(mUpdateLocationReceiver, filter);
+
+        publishLocalService(TwilightManager.class, mService);
+    }
+
+    private static class TwilightListenerRecord implements Runnable {
+        private final TwilightListener mListener;
+        private final Handler mHandler;
+
+        public TwilightListenerRecord(TwilightListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        public void postUpdate() {
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            mListener.onTwilightStateChanged();
+        }
+
+    }
+
+    private final TwilightManager mService = new TwilightManager() {
+        /**
+         * Gets the current twilight state.
+         *
+         * @return The current twilight state, or null if no information is available.
+         */
+        @Override
+        public TwilightState getCurrentState() {
+            synchronized (mLock) {
+                return mTwilightState;
+            }
+        }
+
+        /**
+         * Listens for twilight time.
+         *
+         * @param listener The listener.
+         */
+        @Override
+        public void registerListener(TwilightListener listener, Handler handler) {
+            synchronized (mLock) {
+                mListeners.add(new TwilightListenerRecord(listener, handler));
+
+                if (mListeners.size() == 1) {
+                    mLocationHandler.enableLocationUpdates();
+                }
+            }
+        }
+    };
+
+    private void setTwilightState(TwilightState state) {
+        synchronized (mLock) {
+            if (!Objects.equal(mTwilightState, state)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Twilight state changed: " + state);
+                }
+
+                mTwilightState = state;
+
+                final int listenerLen = mListeners.size();
+                for (int i = 0; i < listenerLen; i++) {
+                    mListeners.get(i).postUpdate();
+                }
+            }
+        }
+    }
+
+    // The user has moved if the accuracy circles of the two locations don't overlap.
+    private static boolean hasMoved(Location from, Location to) {
+        if (to == null) {
+            return false;
+        }
+
+        if (from == null) {
+            return true;
+        }
+
+        // if new location is older than the current one, the device hasn't moved.
+        if (to.getElapsedRealtimeNanos() < from.getElapsedRealtimeNanos()) {
+            return false;
+        }
+
+        // Get the distance between the two points.
+        float distance = from.distanceTo(to);
+
+        // Get the total accuracy radius for both locations.
+        float totalAccuracy = from.getAccuracy() + to.getAccuracy();
+
+        // If the distance is greater than the combined accuracy of the two
+        // points then they can't overlap and hence the user has moved.
+        return distance >= totalAccuracy;
+    }
+
+    private final class LocationHandler extends Handler {
+        private static final int MSG_ENABLE_LOCATION_UPDATES = 1;
+        private static final int MSG_GET_NEW_LOCATION_UPDATE = 2;
+        private static final int MSG_PROCESS_NEW_LOCATION = 3;
+        private static final int MSG_DO_TWILIGHT_UPDATE = 4;
+
+        private static final long LOCATION_UPDATE_MS = 24 * DateUtils.HOUR_IN_MILLIS;
+        private static final long MIN_LOCATION_UPDATE_MS = 30 * DateUtils.MINUTE_IN_MILLIS;
+        private static final float LOCATION_UPDATE_DISTANCE_METER = 1000 * 20;
+        private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MIN = 5000;
+        private static final long LOCATION_UPDATE_ENABLE_INTERVAL_MAX =
+                15 * DateUtils.MINUTE_IN_MILLIS;
+        private static final double FACTOR_GMT_OFFSET_LONGITUDE =
+                1000.0 * 360.0 / DateUtils.DAY_IN_MILLIS;
+
+        private boolean mPassiveListenerEnabled;
+        private boolean mNetworkListenerEnabled;
+        private boolean mDidFirstInit;
+        private long mLastNetworkRegisterTime = -MIN_LOCATION_UPDATE_MS;
+        private long mLastUpdateInterval;
+        private Location mLocation;
+        private final TwilightCalculator mTwilightCalculator = new TwilightCalculator();
+
+        public void processNewLocation(Location location) {
+            Message msg = obtainMessage(MSG_PROCESS_NEW_LOCATION, location);
+            sendMessage(msg);
+        }
+
+        public void enableLocationUpdates() {
+            sendEmptyMessage(MSG_ENABLE_LOCATION_UPDATES);
+        }
+
+        public void requestLocationUpdate() {
+            sendEmptyMessage(MSG_GET_NEW_LOCATION_UPDATE);
+        }
+
+        public void requestTwilightUpdate() {
+            sendEmptyMessage(MSG_DO_TWILIGHT_UPDATE);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PROCESS_NEW_LOCATION: {
+                    final Location location = (Location)msg.obj;
+                    final boolean hasMoved = hasMoved(mLocation, location);
+                    final boolean hasBetterAccuracy = mLocation == null
+                            || location.getAccuracy() < mLocation.getAccuracy();
+                    if (DEBUG) {
+                        Slog.d(TAG, "Processing new location: " + location
+                               + ", hasMoved=" + hasMoved
+                               + ", hasBetterAccuracy=" + hasBetterAccuracy);
+                    }
+                    if (hasMoved || hasBetterAccuracy) {
+                        setLocation(location);
+                    }
+                    break;
+                }
+
+                case MSG_GET_NEW_LOCATION_UPDATE:
+                    if (!mNetworkListenerEnabled) {
+                        // Don't do anything -- we are still trying to get a
+                        // location.
+                        return;
+                    }
+                    if ((mLastNetworkRegisterTime + MIN_LOCATION_UPDATE_MS) >=
+                            SystemClock.elapsedRealtime()) {
+                        // Don't do anything -- it hasn't been long enough
+                        // since we last requested an update.
+                        return;
+                    }
+
+                    // Unregister the current location monitor, so we can
+                    // register a new one for it to get an immediate update.
+                    mNetworkListenerEnabled = false;
+                    mLocationManager.removeUpdates(mEmptyLocationListener);
+
+                    // Fall through to re-register listener.
+                case MSG_ENABLE_LOCATION_UPDATES:
+                    // enable network provider to receive at least location updates for a given
+                    // distance.
+                    boolean networkLocationEnabled;
+                    try {
+                        networkLocationEnabled =
+                            mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
+                    } catch (Exception e) {
+                        // we may get IllegalArgumentException if network location provider
+                        // does not exist or is not yet installed.
+                        networkLocationEnabled = false;
+                    }
+                    if (!mNetworkListenerEnabled && networkLocationEnabled) {
+                        mNetworkListenerEnabled = true;
+                        mLastNetworkRegisterTime = SystemClock.elapsedRealtime();
+                        mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
+                                LOCATION_UPDATE_MS, 0, mEmptyLocationListener);
+
+                        if (!mDidFirstInit) {
+                            mDidFirstInit = true;
+                            if (mLocation == null) {
+                                retrieveLocation();
+                            }
+                        }
+                    }
+
+                    // enable passive provider to receive updates from location fixes (gps
+                    // and network).
+                    boolean passiveLocationEnabled;
+                    try {
+                        passiveLocationEnabled =
+                            mLocationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER);
+                    } catch (Exception e) {
+                        // we may get IllegalArgumentException if passive location provider
+                        // does not exist or is not yet installed.
+                        passiveLocationEnabled = false;
+                    }
+
+                    if (!mPassiveListenerEnabled && passiveLocationEnabled) {
+                        mPassiveListenerEnabled = true;
+                        mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
+                                0, LOCATION_UPDATE_DISTANCE_METER , mLocationListener);
+                    }
+
+                    if (!(mNetworkListenerEnabled && mPassiveListenerEnabled)) {
+                        mLastUpdateInterval *= 1.5;
+                        if (mLastUpdateInterval == 0) {
+                            mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MIN;
+                        } else if (mLastUpdateInterval > LOCATION_UPDATE_ENABLE_INTERVAL_MAX) {
+                            mLastUpdateInterval = LOCATION_UPDATE_ENABLE_INTERVAL_MAX;
+                        }
+                        sendEmptyMessageDelayed(MSG_ENABLE_LOCATION_UPDATES, mLastUpdateInterval);
+                    }
+                    break;
+
+                case MSG_DO_TWILIGHT_UPDATE:
+                    updateTwilightState();
+                    break;
+            }
+        }
+
+        private void retrieveLocation() {
+            Location location = null;
+            final Iterator<String> providers =
+                    mLocationManager.getProviders(new Criteria(), true).iterator();
+            while (providers.hasNext()) {
+                final Location lastKnownLocation =
+                        mLocationManager.getLastKnownLocation(providers.next());
+                // pick the most recent location
+                if (location == null || (lastKnownLocation != null &&
+                        location.getElapsedRealtimeNanos() <
+                        lastKnownLocation.getElapsedRealtimeNanos())) {
+                    location = lastKnownLocation;
+                }
+            }
+
+            // In the case there is no location available (e.g. GPS fix or network location
+            // is not available yet), the longitude of the location is estimated using the timezone,
+            // latitude and accuracy are set to get a good average.
+            if (location == null) {
+                Time currentTime = new Time();
+                currentTime.set(System.currentTimeMillis());
+                double lngOffset = FACTOR_GMT_OFFSET_LONGITUDE *
+                        (currentTime.gmtoff - (currentTime.isDst > 0 ? 3600 : 0));
+                location = new Location("fake");
+                location.setLongitude(lngOffset);
+                location.setLatitude(0);
+                location.setAccuracy(417000.0f);
+                location.setTime(System.currentTimeMillis());
+                location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Estimated location from timezone: " + location);
+                }
+            }
+
+            setLocation(location);
+        }
+
+        private void setLocation(Location location) {
+            mLocation = location;
+            updateTwilightState();
+        }
+
+        private void updateTwilightState() {
+            if (mLocation == null) {
+                setTwilightState(null);
+                return;
+            }
+
+            final long now = System.currentTimeMillis();
+
+            // calculate yesterday's twilight
+            mTwilightCalculator.calculateTwilight(now - DateUtils.DAY_IN_MILLIS,
+                    mLocation.getLatitude(), mLocation.getLongitude());
+            final long yesterdaySunset = mTwilightCalculator.mSunset;
+
+            // calculate today's twilight
+            mTwilightCalculator.calculateTwilight(now,
+                    mLocation.getLatitude(), mLocation.getLongitude());
+            final boolean isNight = (mTwilightCalculator.mState == TwilightCalculator.NIGHT);
+            final long todaySunrise = mTwilightCalculator.mSunrise;
+            final long todaySunset = mTwilightCalculator.mSunset;
+
+            // calculate tomorrow's twilight
+            mTwilightCalculator.calculateTwilight(now + DateUtils.DAY_IN_MILLIS,
+                    mLocation.getLatitude(), mLocation.getLongitude());
+            final long tomorrowSunrise = mTwilightCalculator.mSunrise;
+
+            // set twilight state
+            TwilightState state = new TwilightState(isNight, yesterdaySunset,
+                    todaySunrise, todaySunset, tomorrowSunrise);
+            if (DEBUG) {
+                Slog.d(TAG, "Updating twilight state: " + state);
+            }
+            setTwilightState(state);
+
+            // schedule next update
+            long nextUpdate = 0;
+            if (todaySunrise == -1 || todaySunset == -1) {
+                // In the case the day or night never ends the update is scheduled 12 hours later.
+                nextUpdate = now + 12 * DateUtils.HOUR_IN_MILLIS;
+            } else {
+                // add some extra time to be on the safe side.
+                nextUpdate += DateUtils.MINUTE_IN_MILLIS;
+
+                if (now > todaySunset) {
+                    nextUpdate += tomorrowSunrise;
+                } else if (now > todaySunrise) {
+                    nextUpdate += todaySunset;
+                } else {
+                    nextUpdate += todaySunrise;
+                }
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "Next update in " + (nextUpdate - now) + " ms");
+            }
+
+            Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE);
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                    getContext(), 0, updateIntent, 0);
+            mAlarmManager.cancel(pendingIntent);
+            mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent);
+        }
+    }
+
+    private final BroadcastReceiver mUpdateLocationReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())
+                    && !intent.getBooleanExtra("state", false)) {
+                // Airplane mode is now off!
+                mLocationHandler.requestLocationUpdate();
+                return;
+            }
+
+            // Time zone has changed or alarm expired.
+            mLocationHandler.requestTwilightUpdate();
+        }
+    };
+
+    // A LocationListener to initialize the network location provider. The location updates
+    // are handled through the passive location provider.
+    private final LocationListener mEmptyLocationListener =  new LocationListener() {
+        public void onLocationChanged(Location location) {
+        }
+
+        public void onProviderDisabled(String provider) {
+        }
+
+        public void onProviderEnabled(String provider) {
+        }
+
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+        }
+    };
+
+    private final LocationListener mLocationListener = new LocationListener() {
+        public void onLocationChanged(Location location) {
+            mLocationHandler.processNewLocation(location);
+        }
+
+        public void onProviderDisabled(String provider) {
+        }
+
+        public void onProviderEnabled(String provider) {
+        }
+
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+        }
+    };
+}
diff --git a/services/core/java/com/android/server/twilight/TwilightState.java b/services/core/java/com/android/server/twilight/TwilightState.java
new file mode 100644
index 0000000..91e24d7
--- /dev/null
+++ b/services/core/java/com/android/server/twilight/TwilightState.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.twilight;
+
+import java.text.DateFormat;
+import java.util.Date;
+
+/**
+ * Describes whether it is day or night.
+ * This object is immutable.
+ */
+public class TwilightState {
+    private final boolean mIsNight;
+    private final long mYesterdaySunset;
+    private final long mTodaySunrise;
+    private final long mTodaySunset;
+    private final long mTomorrowSunrise;
+
+    TwilightState(boolean isNight,
+            long yesterdaySunset,
+            long todaySunrise, long todaySunset,
+            long tomorrowSunrise) {
+        mIsNight = isNight;
+        mYesterdaySunset = yesterdaySunset;
+        mTodaySunrise = todaySunrise;
+        mTodaySunset = todaySunset;
+        mTomorrowSunrise = tomorrowSunrise;
+    }
+
+    /**
+     * Returns true if it is currently night time.
+     */
+    public boolean isNight() {
+        return mIsNight;
+    }
+
+    /**
+     * Returns the time of yesterday's sunset in the System.currentTimeMillis() timebase,
+     * or -1 if the sun never sets.
+     */
+    public long getYesterdaySunset() {
+        return mYesterdaySunset;
+    }
+
+    /**
+     * Returns the time of today's sunrise in the System.currentTimeMillis() timebase,
+     * or -1 if the sun never rises.
+     */
+    public long getTodaySunrise() {
+        return mTodaySunrise;
+    }
+
+    /**
+     * Returns the time of today's sunset in the System.currentTimeMillis() timebase,
+     * or -1 if the sun never sets.
+     */
+    public long getTodaySunset() {
+        return mTodaySunset;
+    }
+
+    /**
+     * Returns the time of tomorrow's sunrise in the System.currentTimeMillis() timebase,
+     * or -1 if the sun never rises.
+     */
+    public long getTomorrowSunrise() {
+        return mTomorrowSunrise;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof TwilightState && equals((TwilightState)o);
+    }
+
+    public boolean equals(TwilightState other) {
+        return other != null
+                && mIsNight == other.mIsNight
+                && mYesterdaySunset == other.mYesterdaySunset
+                && mTodaySunrise == other.mTodaySunrise
+                && mTodaySunset == other.mTodaySunset
+                && mTomorrowSunrise == other.mTomorrowSunrise;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0; // don't care
+    }
+
+    @Override
+    public String toString() {
+        DateFormat f = DateFormat.getDateTimeInstance();
+        return "{TwilightState: isNight=" + mIsNight
+                + ", mYesterdaySunset=" + f.format(new Date(mYesterdaySunset))
+                + ", mTodaySunrise=" + f.format(new Date(mTodaySunrise))
+                + ", mTodaySunset=" + f.format(new Date(mTodaySunset))
+                + ", mTomorrowSunrise=" + f.format(new Date(mTomorrowSunrise))
+                + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/updates/CarrierProvisioningUrlsInstallReceiver.java b/services/core/java/com/android/server/updates/CarrierProvisioningUrlsInstallReceiver.java
new file mode 100644
index 0000000..b53fb65
--- /dev/null
+++ b/services/core/java/com/android/server/updates/CarrierProvisioningUrlsInstallReceiver.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+public class CarrierProvisioningUrlsInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public CarrierProvisioningUrlsInstallReceiver() {
+        super("/data/misc/radio/", "provisioning_urls.xml", "metadata/", "version");
+    }
+}
diff --git a/services/core/java/com/android/server/updates/CertPinInstallReceiver.java b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
new file mode 100644
index 0000000..c03fbc3
--- /dev/null
+++ b/services/core/java/com/android/server/updates/CertPinInstallReceiver.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public CertPinInstallReceiver() {
+        super("/data/misc/keychain/", "pins", "metadata/", "version");
+    }
+}
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
new file mode 100644
index 0000000..9601e9a
--- /dev/null
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.os.FileUtils;
+import android.util.Base64;
+import android.util.EventLog;
+import android.util.Slog;
+
+import com.android.server.EventLogTags;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import libcore.io.IoUtils;
+
+public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "ConfigUpdateInstallReceiver";
+
+    private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH";
+    private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
+    private static final String EXTRA_SIGNATURE = "SIGNATURE";
+    private static final String EXTRA_VERSION_NUMBER = "VERSION";
+
+    private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
+
+    protected final File updateDir;
+    protected final File updateContent;
+    protected final File updateVersion;
+
+    public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
+                                       String updateMetadataPath, String updateVersionPath) {
+        this.updateDir = new File(updateDir);
+        this.updateContent = new File(updateDir, updateContentPath);
+        File updateMetadataDir = new File(updateDir, updateMetadataPath);
+        this.updateVersion = new File(updateMetadataDir, updateVersionPath);
+    }
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    // get the certificate from Settings.Secure
+                    X509Certificate cert = getCert(context.getContentResolver());
+                    // get the content path from the extras
+                    byte[] altContent = getAltContent(intent);
+                    // get the version from the extras
+                    int altVersion = getVersionFromIntent(intent);
+                    // get the previous value from the extras
+                    String altRequiredHash = getRequiredHashFromIntent(intent);
+                    // get the signature from the extras
+                    String altSig = getSignatureFromIntent(intent);
+                    // get the version currently being used
+                    int currentVersion = getCurrentVersion();
+                    // get the hash of the currently used value
+                    String currentHash = getCurrentHash(getCurrentContent());
+                    if (!verifyVersion(currentVersion, altVersion)) {
+                        Slog.i(TAG, "Not installing, new version is <= current version");
+                    } else if (!verifyPreviousHash(currentHash, altRequiredHash)) {
+                        EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
+                                            "Current hash did not match required value");
+                    } else if (!verifySignature(altContent, altVersion, altRequiredHash, altSig,
+                               cert)) {
+                        EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
+                                            "Signature did not verify");
+                    } else {
+                        // install the new content
+                        Slog.i(TAG, "Found new update, installing...");
+                        install(altContent, altVersion);
+                        Slog.i(TAG, "Installation successful");
+                        postInstall(context, intent);
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Could not update content!", e);
+                    // keep the error message <= 100 chars
+                    String errMsg = e.toString();
+                    if (errMsg.length() > 100) {
+                        errMsg = errMsg.substring(0, 99);
+                    }
+                    EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED, errMsg);
+                }
+            }
+        }.start();
+    }
+
+    private X509Certificate getCert(ContentResolver cr) {
+        // get the cert from settings
+        String cert = Settings.Secure.getString(cr, UPDATE_CERTIFICATE_KEY);
+        // convert it into a real certificate
+        try {
+            byte[] derCert = Base64.decode(cert.getBytes(), Base64.DEFAULT);
+            InputStream istream = new ByteArrayInputStream(derCert);
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            return (X509Certificate) cf.generateCertificate(istream);
+        } catch (CertificateException e) {
+            throw new IllegalStateException("Got malformed certificate from settings, ignoring");
+        }
+    }
+
+    private String getContentFromIntent(Intent i) {
+        String extraValue = i.getStringExtra(EXTRA_CONTENT_PATH);
+        if (extraValue == null) {
+            throw new IllegalStateException("Missing required content path, ignoring.");
+        }
+        return extraValue;
+    }
+
+    private int getVersionFromIntent(Intent i) throws NumberFormatException {
+        String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER);
+        if (extraValue == null) {
+            throw new IllegalStateException("Missing required version number, ignoring.");
+        }
+        return Integer.parseInt(extraValue.trim());
+    }
+
+    private String getRequiredHashFromIntent(Intent i) {
+        String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH);
+        if (extraValue == null) {
+            throw new IllegalStateException("Missing required previous hash, ignoring.");
+        }
+        return extraValue.trim();
+    }
+
+    private String getSignatureFromIntent(Intent i) {
+        String extraValue = i.getStringExtra(EXTRA_SIGNATURE);
+        if (extraValue == null) {
+            throw new IllegalStateException("Missing required signature, ignoring.");
+        }
+        return extraValue.trim();
+    }
+
+    private int getCurrentVersion() throws NumberFormatException {
+        try {
+            String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim();
+            return Integer.parseInt(strVersion);
+        } catch (IOException e) {
+            Slog.i(TAG, "Couldn't find current metadata, assuming first update");
+            return 0;
+        }
+    }
+
+    private byte[] getAltContent(Intent i) throws IOException {
+        return IoUtils.readFileAsByteArray(getContentFromIntent(i));
+    }
+
+    private byte[] getCurrentContent() {
+        try {
+            return IoUtils.readFileAsByteArray(updateContent.getCanonicalPath());
+        } catch (IOException e) {
+            Slog.i(TAG, "Failed to read current content, assuming first update!");
+            return null;
+        }
+    }
+
+    private static String getCurrentHash(byte[] content) {
+        if (content == null) {
+            return "0";
+        }
+        try {
+            MessageDigest dgst = MessageDigest.getInstance("SHA512");
+            byte[] fingerprint = dgst.digest(content);
+            return IntegralToString.bytesToHexString(fingerprint, false);
+        } catch (NoSuchAlgorithmException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    private boolean verifyVersion(int current, int alternative) {
+        return (current < alternative);
+    }
+
+    private boolean verifyPreviousHash(String current, String required) {
+        // this is an optional value- if the required field is NONE then we ignore it
+        if (required.equals("NONE")) {
+            return true;
+        }
+        // otherwise, verify that we match correctly
+        return current.equals(required);
+    }
+
+    private boolean verifySignature(byte[] content, int version, String requiredPrevious,
+                                   String signature, X509Certificate cert) throws Exception {
+        Signature signer = Signature.getInstance("SHA512withRSA");
+        signer.initVerify(cert);
+        signer.update(content);
+        signer.update(Long.toString(version).getBytes());
+        signer.update(requiredPrevious.getBytes());
+        return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
+    }
+
+    protected void writeUpdate(File dir, File file, byte[] content) throws IOException {
+        FileOutputStream out = null;
+        File tmp = null;
+        try {
+            // create the parents for the destination file
+            File parent = file.getParentFile();
+            parent.mkdirs();
+            // check that they were created correctly
+            if (!parent.exists()) {
+                throw new IOException("Failed to create directory " + parent.getCanonicalPath());
+            }
+            // create the temporary file
+            tmp = File.createTempFile("journal", "", dir);
+            // mark tmp -rw-r--r--
+            tmp.setReadable(true, false);
+            // write to it
+            out = new FileOutputStream(tmp);
+            out.write(content);
+            // sync to disk
+            out.getFD().sync();
+            // atomic rename
+            if (!tmp.renameTo(file)) {
+                throw new IOException("Failed to atomically rename " + file.getCanonicalPath());
+            }
+        } finally {
+            if (tmp != null) {
+                tmp.delete();
+            }
+            IoUtils.closeQuietly(out);
+        }
+    }
+
+    protected void install(byte[] content, int version) throws IOException {
+        writeUpdate(updateDir, updateContent, content);
+        writeUpdate(updateDir, updateVersion, Long.toString(version).getBytes());
+    }
+
+    protected void postInstall(Context context, Intent intent) {
+    }
+}
diff --git a/services/core/java/com/android/server/updates/IntentFirewallInstallReceiver.java b/services/core/java/com/android/server/updates/IntentFirewallInstallReceiver.java
new file mode 100644
index 0000000..0b54f92
--- /dev/null
+++ b/services/core/java/com/android/server/updates/IntentFirewallInstallReceiver.java
@@ -0,0 +1,28 @@
+/*
+ * 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.updates;
+
+import com.android.server.firewall.IntentFirewall;
+
+public class IntentFirewallInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public IntentFirewallInstallReceiver() {
+        // TODO: should we dynamically generate a filename and store the name in metadata?
+        super(IntentFirewall.getRulesDir().getAbsolutePath(), "ifw.xml", "metadata/",
+                "gservices.version");
+    }
+}
diff --git a/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.java
new file mode 100644
index 0000000..e430814
--- /dev/null
+++ b/services/core/java/com/android/server/updates/SELinuxPolicyInstallReceiver.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.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.FileUtils;
+import android.os.SELinux;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Base64;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+
+public class SELinuxPolicyInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    private static final String TAG = "SELinuxPolicyInstallReceiver";
+
+    private static final String sepolicyPath = "sepolicy";
+    private static final String fileContextsPath = "file_contexts";
+    private static final String propertyContextsPath = "property_contexts";
+    private static final String seappContextsPath = "seapp_contexts";
+
+    public SELinuxPolicyInstallReceiver() {
+        super("/data/security/bundle", "sepolicy_bundle", "metadata/", "version");
+    }
+
+    private void backupContexts(File contexts) {
+        new File(contexts, seappContextsPath).renameTo(
+                new File(contexts, seappContextsPath + "_backup"));
+
+        new File(contexts, propertyContextsPath).renameTo(
+                new File(contexts, propertyContextsPath + "_backup"));
+
+        new File(contexts, fileContextsPath).renameTo(
+                new File(contexts, fileContextsPath + "_backup"));
+
+        new File(contexts, sepolicyPath).renameTo(
+                new File(contexts, sepolicyPath + "_backup"));
+    }
+
+    private void copyUpdate(File contexts) {
+        new File(updateDir, seappContextsPath).renameTo(new File(contexts, seappContextsPath));
+        new File(updateDir, propertyContextsPath).renameTo(new File(contexts, propertyContextsPath));
+        new File(updateDir, fileContextsPath).renameTo(new File(contexts, fileContextsPath));
+        new File(updateDir, sepolicyPath).renameTo(new File(contexts, sepolicyPath));
+    }
+
+    private int readInt(BufferedInputStream reader) throws IOException {
+        int value = 0;
+        for (int i=0; i < 4; i++) {
+            value = (value << 8) | reader.read();
+        }
+        return value;
+    }
+
+    private int[] readChunkLengths(BufferedInputStream bundle) throws IOException {
+        int[] chunks = new int[4];
+        chunks[0] = readInt(bundle);
+        chunks[1] = readInt(bundle);
+        chunks[2] = readInt(bundle);
+        chunks[3] = readInt(bundle);
+        return chunks;
+    }
+
+    private void installFile(File destination, BufferedInputStream stream, int length)
+            throws IOException {
+        byte[] chunk = new byte[length];
+        stream.read(chunk, 0, length);
+        writeUpdate(updateDir, destination, Base64.decode(chunk, Base64.DEFAULT));
+    }
+
+    private void unpackBundle() throws IOException {
+        BufferedInputStream stream = new BufferedInputStream(new FileInputStream(updateContent));
+        try {
+            int[] chunkLengths = readChunkLengths(stream);
+            installFile(new File(updateDir, seappContextsPath), stream, chunkLengths[0]);
+            installFile(new File(updateDir, propertyContextsPath), stream, chunkLengths[1]);
+            installFile(new File(updateDir, fileContextsPath), stream, chunkLengths[2]);
+            installFile(new File(updateDir, sepolicyPath), stream, chunkLengths[3]);
+        } finally {
+            IoUtils.closeQuietly(stream);
+        }
+    }
+
+    private void applyUpdate() throws IOException, ErrnoException {
+        Slog.i(TAG, "Applying SELinux policy");
+        File contexts = new File(updateDir.getParentFile(), "contexts");
+        File current = new File(updateDir.getParentFile(), "current");
+        File update = new File(updateDir.getParentFile(), "update");
+        File tmp = new File(updateDir.getParentFile(), "tmp");
+        if (current.exists()) {
+            Libcore.os.symlink(updateDir.getPath(), update.getPath());
+            Libcore.os.rename(update.getPath(), current.getPath());
+        } else {
+            Libcore.os.symlink(updateDir.getPath(), current.getPath());
+        }
+        contexts.mkdirs();
+        backupContexts(contexts);
+        copyUpdate(contexts);
+        Libcore.os.symlink(contexts.getPath(), tmp.getPath());
+        Libcore.os.rename(tmp.getPath(), current.getPath());
+        SystemProperties.set("selinux.reload_policy", "1");
+    }
+
+    @Override
+    protected void postInstall(Context context, Intent intent) {
+        try {
+            unpackBundle();
+            applyUpdate();
+        } catch (IllegalArgumentException e) {
+            Slog.e(TAG, "SELinux policy update malformed: ", e);
+        } catch (IOException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Could not update selinux policy: ", e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/updates/SmsShortCodesInstallReceiver.java b/services/core/java/com/android/server/updates/SmsShortCodesInstallReceiver.java
new file mode 100644
index 0000000..0f14f57
--- /dev/null
+++ b/services/core/java/com/android/server/updates/SmsShortCodesInstallReceiver.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+public class SmsShortCodesInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public SmsShortCodesInstallReceiver() {
+        super("/data/misc/sms/", "codes", "metadata/", "version");
+    }
+}
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
new file mode 100644
index 0000000..83adbdb
--- /dev/null
+++ b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
@@ -0,0 +1,34 @@
+/*
+ * 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.updates;
+
+import android.util.Base64;
+import android.util.Slog;
+
+import java.io.IOException;
+
+public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    public TZInfoInstallReceiver() {
+        super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version");
+    }
+
+    @Override
+    protected void install(byte[] encodedContent, int version) throws IOException {
+        super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
+    }
+}
diff --git a/services/core/java/com/android/server/usb/UsbDebuggingManager.java b/services/core/java/com/android/server/usb/UsbDebuggingManager.java
new file mode 100644
index 0000000..ce953a4
--- /dev/null
+++ b/services/core/java/com/android/server/usb/UsbDebuggingManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Handler;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.Base64;
+import com.android.server.FgThread;
+
+import java.lang.Thread;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.util.Arrays;
+
+public class UsbDebuggingManager implements Runnable {
+    private static final String TAG = "UsbDebuggingManager";
+    private static final boolean DEBUG = false;
+
+    private final String ADBD_SOCKET = "adbd";
+    private final String ADB_DIRECTORY = "misc/adb";
+    private final String ADB_KEYS_FILE = "adb_keys";
+    private final int BUFFER_SIZE = 4096;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private Thread mThread;
+    private boolean mAdbEnabled = false;
+    private String mFingerprints;
+    private LocalSocket mSocket = null;
+    private OutputStream mOutputStream = null;
+
+    public UsbDebuggingManager(Context context) {
+        mHandler = new UsbDebuggingHandler(FgThread.get().getLooper());
+        mContext = context;
+    }
+
+    private void listenToSocket() throws IOException {
+        try {
+            byte[] buffer = new byte[BUFFER_SIZE];
+            LocalSocketAddress address = new LocalSocketAddress(ADBD_SOCKET,
+                                         LocalSocketAddress.Namespace.RESERVED);
+            InputStream inputStream = null;
+
+            mSocket = new LocalSocket();
+            mSocket.connect(address);
+
+            mOutputStream = mSocket.getOutputStream();
+            inputStream = mSocket.getInputStream();
+
+            while (true) {
+                int count = inputStream.read(buffer);
+                if (count < 0) {
+                    Slog.e(TAG, "got " + count + " reading");
+                    break;
+                }
+
+                if (buffer[0] == 'P' && buffer[1] == 'K') {
+                    String key = new String(Arrays.copyOfRange(buffer, 2, count));
+                    Slog.d(TAG, "Received public key: " + key);
+                    Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_CONFIRM);
+                    msg.obj = key;
+                    mHandler.sendMessage(msg);
+                }
+                else {
+                    Slog.e(TAG, "Wrong message: " + (new String(Arrays.copyOfRange(buffer, 0, 2))));
+                    break;
+                }
+            }
+        } catch (IOException ex) {
+            Slog.e(TAG, "Communication error: ", ex);
+            throw ex;
+        } finally {
+            closeSocket();
+        }
+    }
+
+    @Override
+    public void run() {
+        while (mAdbEnabled) {
+            try {
+                listenToSocket();
+            } catch (Exception e) {
+                /* Don't loop too fast if adbd dies, before init restarts it */
+                SystemClock.sleep(1000);
+            }
+        }
+    }
+
+    private void closeSocket() {
+        try {
+            mOutputStream.close();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed closing output stream: " + e);
+        }
+
+        try {
+            mSocket.close();
+        } catch (IOException ex) {
+            Slog.e(TAG, "Failed closing socket: " + ex);
+        }
+    }
+
+    private void sendResponse(String msg) {
+        if (mOutputStream != null) {
+            try {
+                mOutputStream.write(msg.getBytes());
+            }
+            catch (IOException ex) {
+                Slog.e(TAG, "Failed to write response:", ex);
+            }
+        }
+    }
+
+    class UsbDebuggingHandler extends Handler {
+        private static final int MESSAGE_ADB_ENABLED = 1;
+        private static final int MESSAGE_ADB_DISABLED = 2;
+        private static final int MESSAGE_ADB_ALLOW = 3;
+        private static final int MESSAGE_ADB_DENY = 4;
+        private static final int MESSAGE_ADB_CONFIRM = 5;
+        private static final int MESSAGE_ADB_CLEAR = 6;
+
+        public UsbDebuggingHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_ADB_ENABLED:
+                    if (mAdbEnabled)
+                        break;
+
+                    mAdbEnabled = true;
+
+                    mThread = new Thread(UsbDebuggingManager.this, TAG);
+                    mThread.start();
+
+                    break;
+
+                case MESSAGE_ADB_DISABLED:
+                    if (!mAdbEnabled)
+                        break;
+
+                    mAdbEnabled = false;
+                    closeSocket();
+
+                    try {
+                        mThread.join();
+                    } catch (Exception ex) {
+                    }
+
+                    mThread = null;
+                    mOutputStream = null;
+                    mSocket = null;
+                    break;
+
+                case MESSAGE_ADB_ALLOW: {
+                    String key = (String)msg.obj;
+                    String fingerprints = getFingerprints(key);
+
+                    if (!fingerprints.equals(mFingerprints)) {
+                        Slog.e(TAG, "Fingerprints do not match. Got "
+                                + fingerprints + ", expected " + mFingerprints);
+                        break;
+                    }
+
+                    if (msg.arg1 == 1) {
+                        writeKey(key);
+                    }
+
+                    sendResponse("OK");
+                    break;
+                }
+
+                case MESSAGE_ADB_DENY:
+                    sendResponse("NO");
+                    break;
+
+                case MESSAGE_ADB_CONFIRM: {
+                    String key = (String)msg.obj;
+                    mFingerprints = getFingerprints(key);
+                    showConfirmationDialog(key, mFingerprints);
+                    break;
+                }
+
+                case MESSAGE_ADB_CLEAR:
+                    deleteKeyFile();
+                    break;
+            }
+        }
+    }
+
+    private String getFingerprints(String key) {
+        String hex = "0123456789ABCDEF";
+        StringBuilder sb = new StringBuilder();
+        MessageDigest digester;
+
+        try {
+            digester = MessageDigest.getInstance("MD5");
+        } catch (Exception ex) {
+            Slog.e(TAG, "Error getting digester: " + ex);
+            return "";
+        }
+
+        byte[] base64_data = key.split("\\s+")[0].getBytes();
+        byte[] digest = digester.digest(Base64.decode(base64_data, Base64.DEFAULT));
+
+        for (int i = 0; i < digest.length; i++) {
+            sb.append(hex.charAt((digest[i] >> 4) & 0xf));
+            sb.append(hex.charAt(digest[i] & 0xf));
+            if (i < digest.length - 1)
+                sb.append(":");
+        }
+        return sb.toString();
+    }
+
+    private void showConfirmationDialog(String key, String fingerprints) {
+        Intent dialogIntent = new Intent();
+
+        dialogIntent.setClassName("com.android.systemui",
+                "com.android.systemui.usb.UsbDebuggingActivity");
+        dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        dialogIntent.putExtra("key", key);
+        dialogIntent.putExtra("fingerprints", fingerprints);
+        try {
+            mContext.startActivity(dialogIntent);
+        } catch (ActivityNotFoundException e) {
+            Slog.e(TAG, "unable to start UsbDebuggingActivity");
+        }
+    }
+
+    private File getUserKeyFile() {
+        File dataDir = Environment.getDataDirectory();
+        File adbDir = new File(dataDir, ADB_DIRECTORY);
+
+        if (!adbDir.exists()) {
+            Slog.e(TAG, "ADB data directory does not exist");
+            return null;
+        }
+
+        return new File(adbDir, ADB_KEYS_FILE);
+    }
+
+    private void writeKey(String key) {
+        try {
+            File keyFile = getUserKeyFile();
+
+            if (keyFile == null) {
+                return;
+            }
+
+            if (!keyFile.exists()) {
+                keyFile.createNewFile();
+                FileUtils.setPermissions(keyFile.toString(),
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR |
+                    FileUtils.S_IRGRP, -1, -1);
+            }
+
+            FileOutputStream fo = new FileOutputStream(keyFile, true);
+            fo.write(key.getBytes());
+            fo.write('\n');
+            fo.close();
+        }
+        catch (IOException ex) {
+            Slog.e(TAG, "Error writing key:" + ex);
+        }
+    }
+
+    private void deleteKeyFile() {
+        File keyFile = getUserKeyFile();
+        if (keyFile != null) {
+            keyFile.delete();
+        }
+    }
+
+    public void setAdbEnabled(boolean enabled) {
+        mHandler.sendEmptyMessage(enabled ? UsbDebuggingHandler.MESSAGE_ADB_ENABLED
+                                          : UsbDebuggingHandler.MESSAGE_ADB_DISABLED);
+    }
+
+    public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+        Message msg = mHandler.obtainMessage(UsbDebuggingHandler.MESSAGE_ADB_ALLOW);
+        msg.arg1 = alwaysAllow ? 1 : 0;
+        msg.obj = publicKey;
+        mHandler.sendMessage(msg);
+    }
+
+    public void denyUsbDebugging() {
+        mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_DENY);
+    }
+
+    public void clearUsbDebuggingKeys() {
+        mHandler.sendEmptyMessage(UsbDebuggingHandler.MESSAGE_ADB_CLEAR);
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw) {
+        pw.println("  USB Debugging State:");
+        pw.println("    Connected to adbd: " + (mOutputStream != null));
+        pw.println("    Last key received: " + mFingerprints);
+        pw.println("    User keys:");
+        try {
+            pw.println(FileUtils.readTextFile(new File("/data/misc/adb/adb_keys"), 0, null));
+        } catch (IOException e) {
+            pw.println("IOException: " + e);
+        }
+        pw.println("    System keys:");
+        try {
+            pw.println(FileUtils.readTextFile(new File("/adb_keys"), 0, null));
+        } catch (IOException e) {
+            pw.println("IOException: " + e);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/usb/UsbDeviceManager.java b/services/core/java/com/android/server/usb/UsbDeviceManager.java
new file mode 100644
index 0000000..5a60de0
--- /dev/null
+++ b/services/core/java/com/android/server/usb/UsbDeviceManager.java
@@ -0,0 +1,885 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbManager;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UEventObserver;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.Settings;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.FgThread;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Scanner;
+
+/**
+ * UsbDeviceManager manages USB state in device mode.
+ */
+public class UsbDeviceManager {
+
+    private static final String TAG = UsbDeviceManager.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final String USB_STATE_MATCH =
+            "DEVPATH=/devices/virtual/android_usb/android0";
+    private static final String ACCESSORY_START_MATCH =
+            "DEVPATH=/devices/virtual/misc/usb_accessory";
+    private static final String FUNCTIONS_PATH =
+            "/sys/class/android_usb/android0/functions";
+    private static final String STATE_PATH =
+            "/sys/class/android_usb/android0/state";
+    private static final String MASS_STORAGE_FILE_PATH =
+            "/sys/class/android_usb/android0/f_mass_storage/lun/file";
+    private static final String RNDIS_ETH_ADDR_PATH =
+            "/sys/class/android_usb/android0/f_rndis/ethaddr";
+    private static final String AUDIO_SOURCE_PCM_PATH =
+            "/sys/class/android_usb/android0/f_audio_source/pcm";
+
+    private static final int MSG_UPDATE_STATE = 0;
+    private static final int MSG_ENABLE_ADB = 1;
+    private static final int MSG_SET_CURRENT_FUNCTIONS = 2;
+    private static final int MSG_SYSTEM_READY = 3;
+    private static final int MSG_BOOT_COMPLETED = 4;
+    private static final int MSG_USER_SWITCHED = 5;
+
+    private static final int AUDIO_MODE_NONE = 0;
+    private static final int AUDIO_MODE_SOURCE = 1;
+
+    // Delay for debouncing USB disconnects.
+    // We often get rapid connect/disconnect events when enabling USB functions,
+    // which need debouncing.
+    private static final int UPDATE_DELAY = 1000;
+
+    private static final String BOOT_MODE_PROPERTY = "ro.bootmode";
+
+    private UsbHandler mHandler;
+    private boolean mBootCompleted;
+
+    private final Object mLock = new Object();
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    @GuardedBy("mLock")
+    private UsbSettingsManager mCurrentSettings;
+    private NotificationManager mNotificationManager;
+    private final boolean mHasUsbAccessory;
+    private boolean mUseUsbNotification;
+    private boolean mAdbEnabled;
+    private boolean mAudioSourceEnabled;
+    private Map<String, List<Pair<String, String>>> mOemModeMap;
+    private String[] mAccessoryStrings;
+    private UsbDebuggingManager mDebuggingManager;
+
+    private class AdbSettingsObserver extends ContentObserver {
+        public AdbSettingsObserver() {
+            super(null);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            boolean enable = (Settings.Global.getInt(mContentResolver,
+                    Settings.Global.ADB_ENABLED, 0) > 0);
+            mHandler.sendMessage(MSG_ENABLE_ADB, enable);
+        }
+    }
+
+    /*
+     * Listens for uevent messages from the kernel to monitor the USB state
+     */
+    private final UEventObserver mUEventObserver = new UEventObserver() {
+        @Override
+        public void onUEvent(UEventObserver.UEvent event) {
+            if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
+
+            String state = event.get("USB_STATE");
+            String accessory = event.get("ACCESSORY");
+            if (state != null) {
+                mHandler.updateState(state);
+            } else if ("START".equals(accessory)) {
+                if (DEBUG) Slog.d(TAG, "got accessory start");
+                startAccessoryMode();
+            }
+        }
+    };
+
+    public UsbDeviceManager(Context context) {
+        mContext = context;
+        mContentResolver = context.getContentResolver();
+        PackageManager pm = mContext.getPackageManager();
+        mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
+        initRndisAddress();
+
+        readOemUsbOverrideConfig();
+
+        mHandler = new UsbHandler(FgThread.get().getLooper());
+
+        if (nativeIsStartRequested()) {
+            if (DEBUG) Slog.d(TAG, "accessory attached at boot");
+            startAccessoryMode();
+        }
+
+        boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);
+        boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));
+        if (secureAdbEnabled && !dataEncrypted) {
+            mDebuggingManager = new UsbDebuggingManager(context);
+        }
+    }
+
+    public void setCurrentSettings(UsbSettingsManager settings) {
+        synchronized (mLock) {
+            mCurrentSettings = settings;
+        }
+    }
+
+    private UsbSettingsManager getCurrentSettings() {
+        synchronized (mLock) {
+            return mCurrentSettings;
+        }
+    }
+
+    public void systemReady() {
+        if (DEBUG) Slog.d(TAG, "systemReady");
+
+        mNotificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
+        // We do not show the USB notification if the primary volume supports mass storage.
+        // The legacy mass storage UI will be used instead.
+        boolean massStorageSupported = false;
+        final StorageManager storageManager = StorageManager.from(mContext);
+        final StorageVolume primary = storageManager.getPrimaryVolume();
+        massStorageSupported = primary != null && primary.allowMassStorage();
+        mUseUsbNotification = !massStorageSupported;
+
+        // make sure the ADB_ENABLED setting value matches the current state
+        Settings.Global.putInt(mContentResolver, Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+
+        mHandler.sendEmptyMessage(MSG_SYSTEM_READY);
+    }
+
+    private void startAccessoryMode() {
+        mAccessoryStrings = nativeGetAccessoryStrings();
+        boolean enableAudio = (nativeGetAudioMode() == AUDIO_MODE_SOURCE);
+        // don't start accessory mode if our mandatory strings have not been set
+        boolean enableAccessory = (mAccessoryStrings != null &&
+                        mAccessoryStrings[UsbAccessory.MANUFACTURER_STRING] != null &&
+                        mAccessoryStrings[UsbAccessory.MODEL_STRING] != null);
+        String functions = null;
+
+        if (enableAccessory && enableAudio) {
+            functions = UsbManager.USB_FUNCTION_ACCESSORY + ","
+                    + UsbManager.USB_FUNCTION_AUDIO_SOURCE;
+        } else if (enableAccessory) {
+            functions = UsbManager.USB_FUNCTION_ACCESSORY;
+        } else if (enableAudio) {
+            functions = UsbManager.USB_FUNCTION_AUDIO_SOURCE;
+        }
+
+        if (functions != null) {
+            setCurrentFunctions(functions, false);
+        }
+    }
+
+    private static void initRndisAddress() {
+        // configure RNDIS ethernet address based on our serial number using the same algorithm
+        // we had been previously using in kernel board files
+        final int ETH_ALEN = 6;
+        int address[] = new int[ETH_ALEN];
+        // first byte is 0x02 to signify a locally administered address
+        address[0] = 0x02;
+
+        String serial = SystemProperties.get("ro.serialno", "1234567890ABCDEF");
+        int serialLength = serial.length();
+        // XOR the USB serial across the remaining 5 bytes
+        for (int i = 0; i < serialLength; i++) {
+            address[i % (ETH_ALEN - 1) + 1] ^= (int)serial.charAt(i);
+        }
+        String addrString = String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
+            address[0], address[1], address[2], address[3], address[4], address[5]);
+        try {
+            FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString);
+        } catch (IOException e) {
+           Slog.e(TAG, "failed to write to " + RNDIS_ETH_ADDR_PATH);
+        }
+    }
+
+     private static String addFunction(String functions, String function) {
+         if ("none".equals(functions)) {
+             return function;
+         }
+        if (!containsFunction(functions, function)) {
+            if (functions.length() > 0) {
+                functions += ",";
+            }
+            functions += function;
+        }
+        return functions;
+    }
+
+    private static String removeFunction(String functions, String function) {
+        String[] split = functions.split(",");
+        for (int i = 0; i < split.length; i++) {
+            if (function.equals(split[i])) {
+                split[i] = null;
+            }
+        }
+        if (split.length == 1 && split[0] == null) {
+            return "none";
+        }
+        StringBuilder builder = new StringBuilder();
+         for (int i = 0; i < split.length; i++) {
+            String s = split[i];
+            if (s != null) {
+                if (builder.length() > 0) {
+                    builder.append(",");
+                }
+                builder.append(s);
+            }
+        }
+        return builder.toString();
+    }
+
+    private static boolean containsFunction(String functions, String function) {
+        int index = functions.indexOf(function);
+        if (index < 0) return false;
+        if (index > 0 && functions.charAt(index - 1) != ',') return false;
+        int charAfter = index + function.length();
+        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
+        return true;
+    }
+
+    private final class UsbHandler extends Handler {
+
+        // current USB state
+        private boolean mConnected;
+        private boolean mConfigured;
+        private String mCurrentFunctions;
+        private String mDefaultFunctions;
+        private UsbAccessory mCurrentAccessory;
+        private int mUsbNotificationId;
+        private boolean mAdbNotificationShown;
+        private int mCurrentUser = UserHandle.USER_NULL;
+
+        private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (DEBUG) Slog.d(TAG, "boot completed");
+                mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
+            }
+        };
+
+        private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                mHandler.obtainMessage(MSG_USER_SWITCHED, userId, 0).sendToTarget();
+            }
+        };
+
+        public UsbHandler(Looper looper) {
+            super(looper);
+            try {
+                // persist.sys.usb.config should never be unset.  But if it is, set it to "adb"
+                // so we have a chance of debugging what happened.
+                mDefaultFunctions = SystemProperties.get("persist.sys.usb.config", "adb");
+
+                // Check if USB mode needs to be overridden depending on OEM specific bootmode.
+                mDefaultFunctions = processOemUsbOverride(mDefaultFunctions);
+
+                // sanity check the sys.usb.config system property
+                // this may be necessary if we crashed while switching USB configurations
+                String config = SystemProperties.get("sys.usb.config", "none");
+                if (!config.equals(mDefaultFunctions)) {
+                    Slog.w(TAG, "resetting config to persistent property: " + mDefaultFunctions);
+                    SystemProperties.set("sys.usb.config", mDefaultFunctions);
+                }
+
+                mCurrentFunctions = mDefaultFunctions;
+                String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim();
+                updateState(state);
+                mAdbEnabled = containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ADB);
+
+                // Upgrade step for previous versions that used persist.service.adb.enable
+                String value = SystemProperties.get("persist.service.adb.enable", "");
+                if (value.length() > 0) {
+                    char enable = value.charAt(0);
+                    if (enable == '1') {
+                        setAdbEnabled(true);
+                    } else if (enable == '0') {
+                        setAdbEnabled(false);
+                    }
+                    SystemProperties.set("persist.service.adb.enable", "");
+                }
+
+                // register observer to listen for settings changes
+                mContentResolver.registerContentObserver(
+                        Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+                                false, new AdbSettingsObserver());
+
+                // Watch for USB configuration changes
+                mUEventObserver.startObserving(USB_STATE_MATCH);
+                mUEventObserver.startObserving(ACCESSORY_START_MATCH);
+
+                mContext.registerReceiver(
+                        mBootCompletedReceiver, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+                mContext.registerReceiver(
+                        mUserSwitchedReceiver, new IntentFilter(Intent.ACTION_USER_SWITCHED));
+            } catch (Exception e) {
+                Slog.e(TAG, "Error initializing UsbHandler", e);
+            }
+        }
+
+        public void sendMessage(int what, boolean arg) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.arg1 = (arg ? 1 : 0);
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, Object arg) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg;
+            sendMessage(m);
+        }
+
+        public void sendMessage(int what, Object arg0, boolean arg1) {
+            removeMessages(what);
+            Message m = Message.obtain(this, what);
+            m.obj = arg0;
+            m.arg1 = (arg1 ? 1 : 0);
+            sendMessage(m);
+        }
+
+        public void updateState(String state) {
+            int connected, configured;
+
+            if ("DISCONNECTED".equals(state)) {
+                connected = 0;
+                configured = 0;
+            } else if ("CONNECTED".equals(state)) {
+                connected = 1;
+                configured = 0;
+            } else if ("CONFIGURED".equals(state)) {
+                connected = 1;
+                configured = 1;
+            } else {
+                Slog.e(TAG, "unknown state " + state);
+                return;
+            }
+            removeMessages(MSG_UPDATE_STATE);
+            Message msg = Message.obtain(this, MSG_UPDATE_STATE);
+            msg.arg1 = connected;
+            msg.arg2 = configured;
+            // debounce disconnects to avoid problems bringing up USB tethering
+            sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
+        }
+
+        private boolean waitForState(String state) {
+            // wait for the transition to complete.
+            // give up after 1 second.
+            for (int i = 0; i < 20; i++) {
+                // State transition is done when sys.usb.state is set to the new configuration
+                if (state.equals(SystemProperties.get("sys.usb.state"))) return true;
+                SystemClock.sleep(50);
+            }
+            Slog.e(TAG, "waitForState(" + state + ") FAILED");
+            return false;
+        }
+
+        private boolean setUsbConfig(String config) {
+            if (DEBUG) Slog.d(TAG, "setUsbConfig(" + config + ")");
+            // set the new configuration
+            SystemProperties.set("sys.usb.config", config);
+            return waitForState(config);
+        }
+
+        private void setAdbEnabled(boolean enable) {
+            if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable);
+            if (enable != mAdbEnabled) {
+                mAdbEnabled = enable;
+                // Due to the persist.sys.usb.config property trigger, changing adb state requires
+                // switching to default function
+                setEnabledFunctions(mDefaultFunctions, true);
+                updateAdbNotification();
+            }
+            if (mDebuggingManager != null) {
+                mDebuggingManager.setAdbEnabled(mAdbEnabled);
+            }
+        }
+
+        private void setEnabledFunctions(String functions, boolean makeDefault) {
+
+            // Do not update persystent.sys.usb.config if the device is booted up
+            // with OEM specific mode.
+            if (functions != null && makeDefault && !needsOemUsbOverride()) {
+
+                if (mAdbEnabled) {
+                    functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
+                } else {
+                    functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
+                }
+                if (!mDefaultFunctions.equals(functions)) {
+                    if (!setUsbConfig("none")) {
+                        Slog.e(TAG, "Failed to disable USB");
+                        // revert to previous configuration if we fail
+                        setUsbConfig(mCurrentFunctions);
+                        return;
+                    }
+                    // setting this property will also change the current USB state
+                    // via a property trigger
+                    SystemProperties.set("persist.sys.usb.config", functions);
+                    if (waitForState(functions)) {
+                        mCurrentFunctions = functions;
+                        mDefaultFunctions = functions;
+                    } else {
+                        Slog.e(TAG, "Failed to switch persistent USB config to " + functions);
+                        // revert to previous configuration if we fail
+                        SystemProperties.set("persist.sys.usb.config", mDefaultFunctions);
+                    }
+                }
+            } else {
+                if (functions == null) {
+                    functions = mDefaultFunctions;
+                }
+
+                // Override with bootmode specific usb mode if needed
+                functions = processOemUsbOverride(functions);
+
+                if (mAdbEnabled) {
+                    functions = addFunction(functions, UsbManager.USB_FUNCTION_ADB);
+                } else {
+                    functions = removeFunction(functions, UsbManager.USB_FUNCTION_ADB);
+                }
+                if (!mCurrentFunctions.equals(functions)) {
+                    if (!setUsbConfig("none")) {
+                        Slog.e(TAG, "Failed to disable USB");
+                        // revert to previous configuration if we fail
+                        setUsbConfig(mCurrentFunctions);
+                        return;
+                    }
+                    if (setUsbConfig(functions)) {
+                        mCurrentFunctions = functions;
+                    } else {
+                        Slog.e(TAG, "Failed to switch USB config to " + functions);
+                        // revert to previous configuration if we fail
+                        setUsbConfig(mCurrentFunctions);
+                    }
+                }
+            }
+        }
+
+        private void updateCurrentAccessory() {
+            if (!mHasUsbAccessory) return;
+
+            if (mConfigured) {
+                if (mAccessoryStrings != null) {
+                    mCurrentAccessory = new UsbAccessory(mAccessoryStrings);
+                    Slog.d(TAG, "entering USB accessory mode: " + mCurrentAccessory);
+                    // defer accessoryAttached if system is not ready
+                    if (mBootCompleted) {
+                        getCurrentSettings().accessoryAttached(mCurrentAccessory);
+                    } // else handle in mBootCompletedReceiver
+                } else {
+                    Slog.e(TAG, "nativeGetAccessoryStrings failed");
+                }
+            } else if (!mConnected) {
+                // make sure accessory mode is off
+                // and restore default functions
+                Slog.d(TAG, "exited USB accessory mode");
+                setEnabledFunctions(mDefaultFunctions, false);
+
+                if (mCurrentAccessory != null) {
+                    if (mBootCompleted) {
+                        getCurrentSettings().accessoryDetached(mCurrentAccessory);
+                    }
+                    mCurrentAccessory = null;
+                    mAccessoryStrings = null;
+                }
+            }
+        }
+
+        private void updateUsbState() {
+            // send a sticky broadcast containing current USB state
+            Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
+            intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+            intent.putExtra(UsbManager.USB_CONNECTED, mConnected);
+            intent.putExtra(UsbManager.USB_CONFIGURED, mConfigured);
+
+            if (mCurrentFunctions != null) {
+                String[] functions = mCurrentFunctions.split(",");
+                for (int i = 0; i < functions.length; i++) {
+                    intent.putExtra(functions[i], true);
+                }
+            }
+
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void updateAudioSourceFunction() {
+            boolean enabled = containsFunction(mCurrentFunctions,
+                    UsbManager.USB_FUNCTION_AUDIO_SOURCE);
+            if (enabled != mAudioSourceEnabled) {
+                // send a sticky broadcast containing current USB state
+                Intent intent = new Intent(Intent.ACTION_USB_AUDIO_ACCESSORY_PLUG);
+                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra("state", (enabled ? 1 : 0));
+                if (enabled) {
+                    try {
+                        Scanner scanner = new Scanner(new File(AUDIO_SOURCE_PCM_PATH));
+                        int card = scanner.nextInt();
+                        int device = scanner.nextInt();
+                        intent.putExtra("card", card);
+                        intent.putExtra("device", device);
+                    } catch (FileNotFoundException e) {
+                        Slog.e(TAG, "could not open audio source PCM file", e);
+                    }
+                }
+                mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+                mAudioSourceEnabled = enabled;
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_STATE:
+                    mConnected = (msg.arg1 == 1);
+                    mConfigured = (msg.arg2 == 1);
+                    updateUsbNotification();
+                    updateAdbNotification();
+                    if (containsFunction(mCurrentFunctions,
+                            UsbManager.USB_FUNCTION_ACCESSORY)) {
+                        updateCurrentAccessory();
+                    }
+
+                    if (!mConnected) {
+                        // restore defaults when USB is disconnected
+                        setEnabledFunctions(mDefaultFunctions, false);
+                    }
+                    if (mBootCompleted) {
+                        updateUsbState();
+                        updateAudioSourceFunction();
+                    }
+                    break;
+                case MSG_ENABLE_ADB:
+                    setAdbEnabled(msg.arg1 == 1);
+                    break;
+                case MSG_SET_CURRENT_FUNCTIONS:
+                    String functions = (String)msg.obj;
+                    boolean makeDefault = (msg.arg1 == 1);
+                    setEnabledFunctions(functions, makeDefault);
+                    break;
+                case MSG_SYSTEM_READY:
+                    updateUsbNotification();
+                    updateAdbNotification();
+                    updateUsbState();
+                    updateAudioSourceFunction();
+                    break;
+                case MSG_BOOT_COMPLETED:
+                    mBootCompleted = true;
+                    if (mCurrentAccessory != null) {
+                        getCurrentSettings().accessoryAttached(mCurrentAccessory);
+                    }
+                    if (mDebuggingManager != null) {
+                        mDebuggingManager.setAdbEnabled(mAdbEnabled);
+                    }
+                    break;
+                case MSG_USER_SWITCHED: {
+                    final boolean mtpActive =
+                            containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)
+                            || containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP);
+                    if (mtpActive && mCurrentUser != UserHandle.USER_NULL) {
+                        Slog.v(TAG, "Current user switched; resetting USB host stack for MTP");
+                        setUsbConfig("none");
+                        setUsbConfig(mCurrentFunctions);
+                    }
+                    mCurrentUser = msg.arg1;
+                    break;
+                }
+            }
+        }
+
+        public UsbAccessory getCurrentAccessory() {
+            return mCurrentAccessory;
+        }
+
+        private void updateUsbNotification() {
+            if (mNotificationManager == null || !mUseUsbNotification) return;
+            int id = 0;
+            Resources r = mContext.getResources();
+            if (mConnected) {
+                if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) {
+                    id = com.android.internal.R.string.usb_mtp_notification_title;
+                } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) {
+                    id = com.android.internal.R.string.usb_ptp_notification_title;
+                } else if (containsFunction(mCurrentFunctions,
+                        UsbManager.USB_FUNCTION_MASS_STORAGE)) {
+                    id = com.android.internal.R.string.usb_cd_installer_notification_title;
+                } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) {
+                    id = com.android.internal.R.string.usb_accessory_notification_title;
+                } else {
+                    // There is a different notification for USB tethering so we don't need one here
+                    //if (!containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_RNDIS)) {
+                    //    Slog.e(TAG, "No known USB function in updateUsbNotification");
+                    //}
+                }
+            }
+            if (id != mUsbNotificationId) {
+                // clear notification if title needs changing
+                if (mUsbNotificationId != 0) {
+                    mNotificationManager.cancelAsUser(null, mUsbNotificationId,
+                            UserHandle.ALL);
+                    mUsbNotificationId = 0;
+                }
+                if (id != 0) {
+                    CharSequence message = r.getText(
+                            com.android.internal.R.string.usb_notification_message);
+                    CharSequence title = r.getText(id);
+
+                    Notification notification = new Notification();
+                    notification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
+                    notification.when = 0;
+                    notification.flags = Notification.FLAG_ONGOING_EVENT;
+                    notification.tickerText = title;
+                    notification.defaults = 0; // please be quiet
+                    notification.sound = null;
+                    notification.vibrate = null;
+                    notification.priority = Notification.PRIORITY_MIN;
+
+                    Intent intent = Intent.makeRestartActivityTask(
+                            new ComponentName("com.android.settings",
+                                    "com.android.settings.UsbSettings"));
+                    PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+                            intent, 0, null, UserHandle.CURRENT);
+                    notification.setLatestEventInfo(mContext, title, message, pi);
+                    mNotificationManager.notifyAsUser(null, id, notification,
+                            UserHandle.ALL);
+                    mUsbNotificationId = id;
+                }
+            }
+        }
+
+        private void updateAdbNotification() {
+            if (mNotificationManager == null) return;
+            final int id = com.android.internal.R.string.adb_active_notification_title;
+            if (mAdbEnabled && mConnected) {
+                if ("0".equals(SystemProperties.get("persist.adb.notify"))) return;
+
+                if (!mAdbNotificationShown) {
+                    Resources r = mContext.getResources();
+                    CharSequence title = r.getText(id);
+                    CharSequence message = r.getText(
+                            com.android.internal.R.string.adb_active_notification_message);
+
+                    Notification notification = new Notification();
+                    notification.icon = com.android.internal.R.drawable.stat_sys_adb;
+                    notification.when = 0;
+                    notification.flags = Notification.FLAG_ONGOING_EVENT;
+                    notification.tickerText = title;
+                    notification.defaults = 0; // please be quiet
+                    notification.sound = null;
+                    notification.vibrate = null;
+                    notification.priority = Notification.PRIORITY_LOW;
+
+                    Intent intent = Intent.makeRestartActivityTask(
+                            new ComponentName("com.android.settings",
+                                    "com.android.settings.DevelopmentSettings"));
+                    PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
+                            intent, 0, null, UserHandle.CURRENT);
+                    notification.setLatestEventInfo(mContext, title, message, pi);
+                    mAdbNotificationShown = true;
+                    mNotificationManager.notifyAsUser(null, id, notification,
+                            UserHandle.ALL);
+                }
+            } else if (mAdbNotificationShown) {
+                mAdbNotificationShown = false;
+                mNotificationManager.cancelAsUser(null, id, UserHandle.ALL);
+            }
+        }
+
+        public void dump(FileDescriptor fd, PrintWriter pw) {
+            pw.println("  USB Device State:");
+            pw.println("    Current Functions: " + mCurrentFunctions);
+            pw.println("    Default Functions: " + mDefaultFunctions);
+            pw.println("    mConnected: " + mConnected);
+            pw.println("    mConfigured: " + mConfigured);
+            pw.println("    mCurrentAccessory: " + mCurrentAccessory);
+            try {
+                pw.println("    Kernel state: "
+                        + FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim());
+                pw.println("    Kernel function list: "
+                        + FileUtils.readTextFile(new File(FUNCTIONS_PATH), 0, null).trim());
+                pw.println("    Mass storage backing file: "
+                        + FileUtils.readTextFile(new File(MASS_STORAGE_FILE_PATH), 0, null).trim());
+            } catch (IOException e) {
+                pw.println("IOException: " + e);
+            }
+        }
+    }
+
+    /* returns the currently attached USB accessory */
+    public UsbAccessory getCurrentAccessory() {
+        return mHandler.getCurrentAccessory();
+    }
+
+    /* opens the currently attached USB accessory */
+    public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+        UsbAccessory currentAccessory = mHandler.getCurrentAccessory();
+        if (currentAccessory == null) {
+            throw new IllegalArgumentException("no accessory attached");
+        }
+        if (!currentAccessory.equals(accessory)) {
+            String error = accessory.toString()
+                    + " does not match current accessory "
+                    + currentAccessory;
+            throw new IllegalArgumentException(error);
+        }
+        getCurrentSettings().checkPermission(accessory);
+        return nativeOpenAccessory();
+    }
+
+    public void setCurrentFunctions(String functions, boolean makeDefault) {
+        if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ") default: " + makeDefault);
+        mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, makeDefault);
+    }
+
+    public void setMassStorageBackingFile(String path) {
+        if (path == null) path = "";
+        try {
+            FileUtils.stringToFile(MASS_STORAGE_FILE_PATH, path);
+        } catch (IOException e) {
+           Slog.e(TAG, "failed to write to " + MASS_STORAGE_FILE_PATH);
+        }
+    }
+
+    private void readOemUsbOverrideConfig() {
+        String[] configList = mContext.getResources().getStringArray(
+            com.android.internal.R.array.config_oemUsbModeOverride);
+
+        if (configList != null) {
+            for (String config: configList) {
+                String[] items = config.split(":");
+                if (items.length == 3) {
+                    if (mOemModeMap == null) {
+                        mOemModeMap = new HashMap<String, List<Pair<String, String>>>();
+                    }
+                    List overrideList = mOemModeMap.get(items[0]);
+                    if (overrideList == null) {
+                        overrideList = new LinkedList<Pair<String, String>>();
+                        mOemModeMap.put(items[0], overrideList);
+                    }
+                    overrideList.add(new Pair<String, String>(items[1], items[2]));
+                }
+            }
+        }
+    }
+
+    private boolean needsOemUsbOverride() {
+        if (mOemModeMap == null) return false;
+
+        String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+        return (mOemModeMap.get(bootMode) != null) ? true : false;
+    }
+
+    private String processOemUsbOverride(String usbFunctions) {
+        if ((usbFunctions == null) || (mOemModeMap == null)) return usbFunctions;
+
+        String bootMode = SystemProperties.get(BOOT_MODE_PROPERTY, "unknown");
+
+        List<Pair<String, String>> overrides = mOemModeMap.get(bootMode);
+        if (overrides != null) {
+            for (Pair<String, String> pair: overrides) {
+                if (pair.first.equals(usbFunctions)) {
+                    Slog.d(TAG, "OEM USB override: " + pair.first + " ==> " + pair.second);
+                    return pair.second;
+                }
+            }
+        }
+        // return passed in functions as is.
+        return usbFunctions;
+    }
+
+    public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+        if (mDebuggingManager != null) {
+            mDebuggingManager.allowUsbDebugging(alwaysAllow, publicKey);
+        }
+    }
+
+    public void denyUsbDebugging() {
+        if (mDebuggingManager != null) {
+            mDebuggingManager.denyUsbDebugging();
+        }
+    }
+
+    public void clearUsbDebuggingKeys() {
+        if (mDebuggingManager != null) {
+            mDebuggingManager.clearUsbDebuggingKeys();
+        } else {
+            throw new RuntimeException("Cannot clear Usb Debugging keys, "
+                        + "UsbDebuggingManager not enabled");
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw) {
+        if (mHandler != null) {
+            mHandler.dump(fd, pw);
+        }
+        if (mDebuggingManager != null) {
+            mDebuggingManager.dump(fd, pw);
+        }
+    }
+
+    private native String[] nativeGetAccessoryStrings();
+    private native ParcelFileDescriptor nativeOpenAccessory();
+    private native boolean nativeIsStartRequested();
+    private native int nativeGetAudioMode();
+}
diff --git a/services/core/java/com/android/server/usb/UsbHostManager.java b/services/core/java/com/android/server/usb/UsbHostManager.java
new file mode 100644
index 0000000..10272f2
--- /dev/null
+++ b/services/core/java/com/android/server/usb/UsbHostManager.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.content.Context;
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.HashMap;
+
+/**
+ * UsbHostManager manages USB state in host mode.
+ */
+public class UsbHostManager {
+    private static final String TAG = UsbHostManager.class.getSimpleName();
+    private static final boolean LOG = false;
+
+    // contains all connected USB devices
+    private final HashMap<String, UsbDevice> mDevices = new HashMap<String, UsbDevice>();
+
+    // USB busses to exclude from USB host support
+    private final String[] mHostBlacklist;
+
+    private final Context mContext;
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private UsbSettingsManager mCurrentSettings;
+
+    public UsbHostManager(Context context) {
+        mContext = context;
+        mHostBlacklist = context.getResources().getStringArray(
+                com.android.internal.R.array.config_usbHostBlacklist);
+    }
+
+    public void setCurrentSettings(UsbSettingsManager settings) {
+        synchronized (mLock) {
+            mCurrentSettings = settings;
+        }
+    }
+
+    private UsbSettingsManager getCurrentSettings() {
+        synchronized (mLock) {
+            return mCurrentSettings;
+        }
+    }
+
+    private boolean isBlackListed(String deviceName) {
+        int count = mHostBlacklist.length;
+        for (int i = 0; i < count; i++) {
+            if (deviceName.startsWith(mHostBlacklist[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /* returns true if the USB device should not be accessible by applications */
+    private boolean isBlackListed(int clazz, int subClass, int protocol) {
+        // blacklist hubs
+        if (clazz == UsbConstants.USB_CLASS_HUB) return true;
+
+        // blacklist HID boot devices (mouse and keyboard)
+        if (clazz == UsbConstants.USB_CLASS_HID &&
+                subClass == UsbConstants.USB_INTERFACE_SUBCLASS_BOOT) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /* Called from JNI in monitorUsbHostBus() to report new USB devices */
+    private void usbDeviceAdded(String deviceName, int vendorID, int productID,
+            int deviceClass, int deviceSubclass, int deviceProtocol,
+            /* array of quintuples containing id, class, subclass, protocol
+               and number of endpoints for each interface */
+            int[] interfaceValues,
+           /* array of quadruples containing address, attributes, max packet size
+              and interval for each endpoint */
+            int[] endpointValues) {
+
+        if (isBlackListed(deviceName) ||
+                isBlackListed(deviceClass, deviceSubclass, deviceProtocol)) {
+            return;
+        }
+
+        synchronized (mLock) {
+            if (mDevices.get(deviceName) != null) {
+                Slog.w(TAG, "device already on mDevices list: " + deviceName);
+                return;
+            }
+
+            int numInterfaces = interfaceValues.length / 5;
+            Parcelable[] interfaces = new UsbInterface[numInterfaces];
+            try {
+                // repackage interfaceValues as an array of UsbInterface
+                int intf, endp, ival = 0, eval = 0;
+                for (intf = 0; intf < numInterfaces; intf++) {
+                    int interfaceId = interfaceValues[ival++];
+                    int interfaceClass = interfaceValues[ival++];
+                    int interfaceSubclass = interfaceValues[ival++];
+                    int interfaceProtocol = interfaceValues[ival++];
+                    int numEndpoints = interfaceValues[ival++];
+
+                    Parcelable[] endpoints = new UsbEndpoint[numEndpoints];
+                    for (endp = 0; endp < numEndpoints; endp++) {
+                        int address = endpointValues[eval++];
+                        int attributes = endpointValues[eval++];
+                        int maxPacketSize = endpointValues[eval++];
+                        int interval = endpointValues[eval++];
+                        endpoints[endp] = new UsbEndpoint(address, attributes,
+                                maxPacketSize, interval);
+                    }
+
+                    // don't allow if any interfaces are blacklisted
+                    if (isBlackListed(interfaceClass, interfaceSubclass, interfaceProtocol)) {
+                        return;
+                    }
+                    interfaces[intf] = new UsbInterface(interfaceId, interfaceClass,
+                            interfaceSubclass, interfaceProtocol, endpoints);
+                }
+            } catch (Exception e) {
+                // beware of index out of bound exceptions, which might happen if
+                // a device does not set bNumEndpoints correctly
+                Slog.e(TAG, "error parsing USB descriptors", e);
+                return;
+            }
+
+            UsbDevice device = new UsbDevice(deviceName, vendorID, productID,
+                    deviceClass, deviceSubclass, deviceProtocol, interfaces);
+            mDevices.put(deviceName, device);
+            getCurrentSettings().deviceAttached(device);
+        }
+    }
+
+    /* Called from JNI in monitorUsbHostBus to report USB device removal */
+    private void usbDeviceRemoved(String deviceName) {
+        synchronized (mLock) {
+            UsbDevice device = mDevices.remove(deviceName);
+            if (device != null) {
+                getCurrentSettings().deviceDetached(device);
+            }
+        }
+    }
+
+    public void systemReady() {
+        synchronized (mLock) {
+            // Create a thread to call into native code to wait for USB host events.
+            // This thread will call us back on usbDeviceAdded and usbDeviceRemoved.
+            Runnable runnable = new Runnable() {
+                public void run() {
+                    monitorUsbHostBus();
+                }
+            };
+            new Thread(null, runnable, "UsbService host thread").start();
+        }
+    }
+
+    /* Returns a list of all currently attached USB devices */
+    public void getDeviceList(Bundle devices) {
+        synchronized (mLock) {
+            for (String name : mDevices.keySet()) {
+                devices.putParcelable(name, mDevices.get(name));
+            }
+        }
+    }
+
+    /* Opens the specified USB device */
+    public ParcelFileDescriptor openDevice(String deviceName) {
+        synchronized (mLock) {
+            if (isBlackListed(deviceName)) {
+                throw new SecurityException("USB device is on a restricted bus");
+            }
+            UsbDevice device = mDevices.get(deviceName);
+            if (device == null) {
+                // if it is not in mDevices, it either does not exist or is blacklisted
+                throw new IllegalArgumentException(
+                        "device " + deviceName + " does not exist or is restricted");
+            }
+            getCurrentSettings().checkPermission(device);
+            return nativeOpenDevice(deviceName);
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("  USB Host State:");
+            for (String name : mDevices.keySet()) {
+                pw.println("    " + name + ": " + mDevices.get(name));
+            }
+        }
+    }
+
+    private native void monitorUsbHostBus();
+    private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+}
diff --git a/services/core/java/com/android/server/usb/UsbService.java b/services/core/java/com/android/server/usb/UsbService.java
new file mode 100644
index 0000000..36669b1
--- /dev/null
+++ b/services/core/java/com/android/server/usb/UsbService.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2010 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 an
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.usb.IUsbManager;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * UsbService manages all USB related state, including both host and device support.
+ * Host related events and calls are delegated to UsbHostManager, and device related
+ * support is delegated to UsbDeviceManager.
+ */
+public class UsbService extends IUsbManager.Stub {
+    private static final String TAG = "UsbService";
+
+    private final Context mContext;
+
+    private UsbDeviceManager mDeviceManager;
+    private UsbHostManager mHostManager;
+
+    private final Object mLock = new Object();
+
+    /** Map from {@link UserHandle} to {@link UsbSettingsManager} */
+    @GuardedBy("mLock")
+    private final SparseArray<UsbSettingsManager>
+            mSettingsByUser = new SparseArray<UsbSettingsManager>();
+
+    private UsbSettingsManager getSettingsForUser(int userId) {
+        synchronized (mLock) {
+            UsbSettingsManager settings = mSettingsByUser.get(userId);
+            if (settings == null) {
+                settings = new UsbSettingsManager(mContext, new UserHandle(userId));
+                mSettingsByUser.put(userId, settings);
+            }
+            return settings;
+        }
+    }
+
+    public UsbService(Context context) {
+        mContext = context;
+
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {
+            mHostManager = new UsbHostManager(context);
+        }
+        if (new File("/sys/class/android_usb").exists()) {
+            mDeviceManager = new UsbDeviceManager(context);
+        }
+
+        setCurrentUser(UserHandle.USER_OWNER);
+
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        userFilter.addAction(Intent.ACTION_USER_STOPPED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, null);
+    }
+
+    private BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+                setCurrentUser(userId);
+            } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
+                synchronized (mLock) {
+                    mSettingsByUser.remove(userId);
+                }
+            }
+        }
+    };
+
+    private void setCurrentUser(int userId) {
+        final UsbSettingsManager userSettings = getSettingsForUser(userId);
+        if (mHostManager != null) {
+            mHostManager.setCurrentSettings(userSettings);
+        }
+        if (mDeviceManager != null) {
+            mDeviceManager.setCurrentSettings(userSettings);
+        }
+    }
+
+    public void systemReady() {
+        if (mDeviceManager != null) {
+            mDeviceManager.systemReady();
+        }
+        if (mHostManager != null) {
+            mHostManager.systemReady();
+        }
+    }
+
+    /* Returns a list of all currently attached USB devices (host mdoe) */
+    @Override
+    public void getDeviceList(Bundle devices) {
+        if (mHostManager != null) {
+            mHostManager.getDeviceList(devices);
+        }
+    }
+
+    /* Opens the specified USB device (host mode) */
+    @Override
+    public ParcelFileDescriptor openDevice(String deviceName) {
+        if (mHostManager != null) {
+            return mHostManager.openDevice(deviceName);
+        } else {
+            return null;
+        }
+    }
+
+    /* returns the currently attached USB accessory (device mode) */
+    @Override
+    public UsbAccessory getCurrentAccessory() {
+        if (mDeviceManager != null) {
+            return mDeviceManager.getCurrentAccessory();
+        } else {
+            return null;
+        }
+    }
+
+    /* opens the currently attached USB accessory (device mode) */
+    @Override
+    public ParcelFileDescriptor openAccessory(UsbAccessory accessory) {
+        if (mDeviceManager != null) {
+            return mDeviceManager.openAccessory(accessory);
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void setDevicePackage(UsbDevice device, String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getSettingsForUser(userId).setDevicePackage(device, packageName);
+    }
+
+    @Override
+    public void setAccessoryPackage(UsbAccessory accessory, String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getSettingsForUser(userId).setAccessoryPackage(accessory, packageName);
+    }
+
+    @Override
+    public boolean hasDevicePermission(UsbDevice device) {
+        final int userId = UserHandle.getCallingUserId();
+        return getSettingsForUser(userId).hasPermission(device);
+    }
+
+    @Override
+    public boolean hasAccessoryPermission(UsbAccessory accessory) {
+        final int userId = UserHandle.getCallingUserId();
+        return getSettingsForUser(userId).hasPermission(accessory);
+    }
+
+    @Override
+    public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
+        final int userId = UserHandle.getCallingUserId();
+        getSettingsForUser(userId).requestPermission(device, packageName, pi);
+    }
+
+    @Override
+    public void requestAccessoryPermission(
+            UsbAccessory accessory, String packageName, PendingIntent pi) {
+        final int userId = UserHandle.getCallingUserId();
+        getSettingsForUser(userId).requestPermission(accessory, packageName, pi);
+    }
+
+    @Override
+    public void grantDevicePermission(UsbDevice device, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        final int userId = UserHandle.getUserId(uid);
+        getSettingsForUser(userId).grantDevicePermission(device, uid);
+    }
+
+    @Override
+    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        final int userId = UserHandle.getUserId(uid);
+        getSettingsForUser(userId).grantAccessoryPermission(accessory, uid);
+    }
+
+    @Override
+    public boolean hasDefaults(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        return getSettingsForUser(userId).hasDefaults(packageName);
+    }
+
+    @Override
+    public void clearDefaults(String packageName, int userId) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        getSettingsForUser(userId).clearDefaults(packageName);
+    }
+
+    @Override
+    public void setCurrentFunction(String function, boolean makeDefault) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        if (mDeviceManager != null) {
+            mDeviceManager.setCurrentFunctions(function, makeDefault);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
+    @Override
+    public void setMassStorageBackingFile(String path) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        if (mDeviceManager != null) {
+            mDeviceManager.setMassStorageBackingFile(path);
+        } else {
+            throw new IllegalStateException("USB device mode not supported");
+        }
+    }
+
+    @Override
+    public void allowUsbDebugging(boolean alwaysAllow, String publicKey) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey);
+    }
+
+    @Override
+    public void denyUsbDebugging() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        mDeviceManager.denyUsbDebugging();
+    }
+
+    @Override
+    public void clearUsbDebuggingKeys() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null);
+        mDeviceManager.clearUsbDebuggingKeys();
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
+        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
+
+        pw.println("USB Manager State:");
+        if (mDeviceManager != null) {
+            mDeviceManager.dump(fd, pw);
+        }
+        if (mHostManager != null) {
+            mHostManager.dump(fd, pw);
+        }
+
+        synchronized (mLock) {
+            for (int i = 0; i < mSettingsByUser.size(); i++) {
+                final int userId = mSettingsByUser.keyAt(i);
+                final UsbSettingsManager settings = mSettingsByUser.valueAt(i);
+                pw.increaseIndent();
+                pw.println("Settings for user " + userId + ":");
+                settings.dump(fd, pw);
+                pw.decreaseIndent();
+            }
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/usb/UsbSettingsManager.java b/services/core/java/com/android/server/usb/UsbSettingsManager.java
new file mode 100644
index 0000000..9b5b312
--- /dev/null
+++ b/services/core/java/com/android/server/usb/UsbSettingsManager.java
@@ -0,0 +1,1100 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.XmlResourceParser;
+import android.hardware.usb.UsbAccessory;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.Xml;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+class UsbSettingsManager {
+    private static final String TAG = "UsbSettingsManager";
+    private static final boolean DEBUG = false;
+
+    /** Legacy settings file, before multi-user */
+    private static final File sSingleUserSettingsFile = new File(
+            "/data/system/usb_device_manager.xml");
+
+    private final UserHandle mUser;
+    private final AtomicFile mSettingsFile;
+
+    private final Context mContext;
+    private final Context mUserContext;
+    private final PackageManager mPackageManager;
+
+    // Temporary mapping USB device name to list of UIDs with permissions for the device
+    private final HashMap<String, SparseBooleanArray> mDevicePermissionMap =
+            new HashMap<String, SparseBooleanArray>();
+    // Temporary mapping UsbAccessory to list of UIDs with permissions for the accessory
+    private final HashMap<UsbAccessory, SparseBooleanArray> mAccessoryPermissionMap =
+            new HashMap<UsbAccessory, SparseBooleanArray>();
+    // Maps DeviceFilter to user preferred application package
+    private final HashMap<DeviceFilter, String> mDevicePreferenceMap =
+            new HashMap<DeviceFilter, String>();
+    // Maps AccessoryFilter to user preferred application package
+    private final HashMap<AccessoryFilter, String> mAccessoryPreferenceMap =
+            new HashMap<AccessoryFilter, String>();
+
+    private final Object mLock = new Object();
+
+    // This class is used to describe a USB device.
+    // When used in HashMaps all values must be specified,
+    // but wildcards can be used for any of the fields in
+    // the package meta-data.
+    private static class DeviceFilter {
+        // USB Vendor ID (or -1 for unspecified)
+        public final int mVendorId;
+        // USB Product ID (or -1 for unspecified)
+        public final int mProductId;
+        // USB device or interface class (or -1 for unspecified)
+        public final int mClass;
+        // USB device subclass (or -1 for unspecified)
+        public final int mSubclass;
+        // USB device protocol (or -1 for unspecified)
+        public final int mProtocol;
+
+        public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol) {
+            mVendorId = vid;
+            mProductId = pid;
+            mClass = clasz;
+            mSubclass = subclass;
+            mProtocol = protocol;
+        }
+
+        public DeviceFilter(UsbDevice device) {
+            mVendorId = device.getVendorId();
+            mProductId = device.getProductId();
+            mClass = device.getDeviceClass();
+            mSubclass = device.getDeviceSubclass();
+            mProtocol = device.getDeviceProtocol();
+        }
+
+        public static DeviceFilter read(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            int vendorId = -1;
+            int productId = -1;
+            int deviceClass = -1;
+            int deviceSubclass = -1;
+            int deviceProtocol = -1;
+
+            int count = parser.getAttributeCount();
+            for (int i = 0; i < count; i++) {
+                String name = parser.getAttributeName(i);
+                // All attribute values are ints
+                int value = Integer.parseInt(parser.getAttributeValue(i));
+
+                if ("vendor-id".equals(name)) {
+                    vendorId = value;
+                } else if ("product-id".equals(name)) {
+                    productId = value;
+                } else if ("class".equals(name)) {
+                    deviceClass = value;
+                } else if ("subclass".equals(name)) {
+                    deviceSubclass = value;
+                } else if ("protocol".equals(name)) {
+                    deviceProtocol = value;
+                }
+            }
+            return new DeviceFilter(vendorId, productId,
+                    deviceClass, deviceSubclass, deviceProtocol);
+        }
+
+        public void write(XmlSerializer serializer) throws IOException {
+            serializer.startTag(null, "usb-device");
+            if (mVendorId != -1) {
+                serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+            }
+            if (mProductId != -1) {
+                serializer.attribute(null, "product-id", Integer.toString(mProductId));
+            }
+            if (mClass != -1) {
+                serializer.attribute(null, "class", Integer.toString(mClass));
+            }
+            if (mSubclass != -1) {
+                serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+            }
+            if (mProtocol != -1) {
+                serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+            }
+            serializer.endTag(null, "usb-device");
+        }
+
+        private boolean matches(int clasz, int subclass, int protocol) {
+            return ((mClass == -1 || clasz == mClass) &&
+                    (mSubclass == -1 || subclass == mSubclass) &&
+                    (mProtocol == -1 || protocol == mProtocol));
+        }
+
+        public boolean matches(UsbDevice device) {
+            if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+            if (mProductId != -1 && device.getProductId() != mProductId) return false;
+
+            // check device class/subclass/protocol
+            if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+                    device.getDeviceProtocol())) return true;
+
+            // if device doesn't match, check the interfaces
+            int count = device.getInterfaceCount();
+            for (int i = 0; i < count; i++) {
+                UsbInterface intf = device.getInterface(i);
+                 if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+                        intf.getInterfaceProtocol())) return true;
+            }
+
+            return false;
+        }
+
+        public boolean matches(DeviceFilter f) {
+            if (mVendorId != -1 && f.mVendorId != mVendorId) return false;
+            if (mProductId != -1 && f.mProductId != mProductId) return false;
+
+            // check device class/subclass/protocol
+            return matches(f.mClass, f.mSubclass, f.mProtocol);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            // can't compare if we have wildcard strings
+            if (mVendorId == -1 || mProductId == -1 ||
+                    mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+                return false;
+            }
+            if (obj instanceof DeviceFilter) {
+                DeviceFilter filter = (DeviceFilter)obj;
+                return (filter.mVendorId == mVendorId &&
+                        filter.mProductId == mProductId &&
+                        filter.mClass == mClass &&
+                        filter.mSubclass == mSubclass &&
+                        filter.mProtocol == mProtocol);
+            }
+            if (obj instanceof UsbDevice) {
+                UsbDevice device = (UsbDevice)obj;
+                return (device.getVendorId() == mVendorId &&
+                        device.getProductId() == mProductId &&
+                        device.getDeviceClass() == mClass &&
+                        device.getDeviceSubclass() == mSubclass &&
+                        device.getDeviceProtocol() == mProtocol);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return (((mVendorId << 16) | mProductId) ^
+                    ((mClass << 16) | (mSubclass << 8) | mProtocol));
+        }
+
+        @Override
+        public String toString() {
+            return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+                    ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+                    ",mProtocol=" + mProtocol + "]";
+        }
+    }
+
+    // This class is used to describe a USB accessory.
+    // When used in HashMaps all values must be specified,
+    // but wildcards can be used for any of the fields in
+    // the package meta-data.
+    private static class AccessoryFilter {
+        // USB accessory manufacturer (or null for unspecified)
+        public final String mManufacturer;
+        // USB accessory model (or null for unspecified)
+        public final String mModel;
+        // USB accessory version (or null for unspecified)
+        public final String mVersion;
+
+        public AccessoryFilter(String manufacturer, String model, String version) {
+            mManufacturer = manufacturer;
+            mModel = model;
+            mVersion = version;
+        }
+
+        public AccessoryFilter(UsbAccessory accessory) {
+            mManufacturer = accessory.getManufacturer();
+            mModel = accessory.getModel();
+            mVersion = accessory.getVersion();
+        }
+
+        public static AccessoryFilter read(XmlPullParser parser)
+                throws XmlPullParserException, IOException {
+            String manufacturer = null;
+            String model = null;
+            String version = null;
+
+            int count = parser.getAttributeCount();
+            for (int i = 0; i < count; i++) {
+                String name = parser.getAttributeName(i);
+                String value = parser.getAttributeValue(i);
+
+                if ("manufacturer".equals(name)) {
+                    manufacturer = value;
+                } else if ("model".equals(name)) {
+                    model = value;
+                } else if ("version".equals(name)) {
+                    version = value;
+                }
+             }
+             return new AccessoryFilter(manufacturer, model, version);
+        }
+
+        public void write(XmlSerializer serializer)throws IOException {
+            serializer.startTag(null, "usb-accessory");
+            if (mManufacturer != null) {
+                serializer.attribute(null, "manufacturer", mManufacturer);
+            }
+            if (mModel != null) {
+                serializer.attribute(null, "model", mModel);
+            }
+            if (mVersion != null) {
+                serializer.attribute(null, "version", mVersion);
+            }
+            serializer.endTag(null, "usb-accessory");
+        }
+
+        public boolean matches(UsbAccessory acc) {
+            if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+            if (mModel != null && !acc.getModel().equals(mModel)) return false;
+            if (mVersion != null && !acc.getVersion().equals(mVersion)) return false;
+            return true;
+        }
+
+        public boolean matches(AccessoryFilter f) {
+            if (mManufacturer != null && !f.mManufacturer.equals(mManufacturer)) return false;
+            if (mModel != null && !f.mModel.equals(mModel)) return false;
+            if (mVersion != null && !f.mVersion.equals(mVersion)) return false;
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            // can't compare if we have wildcard strings
+            if (mManufacturer == null || mModel == null || mVersion == null) {
+                return false;
+            }
+            if (obj instanceof AccessoryFilter) {
+                AccessoryFilter filter = (AccessoryFilter)obj;
+                return (mManufacturer.equals(filter.mManufacturer) &&
+                        mModel.equals(filter.mModel) &&
+                        mVersion.equals(filter.mVersion));
+            }
+            if (obj instanceof UsbAccessory) {
+                UsbAccessory accessory = (UsbAccessory)obj;
+                return (mManufacturer.equals(accessory.getManufacturer()) &&
+                        mModel.equals(accessory.getModel()) &&
+                        mVersion.equals(accessory.getVersion()));
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+                    (mModel == null ? 0 : mModel.hashCode()) ^
+                    (mVersion == null ? 0 : mVersion.hashCode()));
+        }
+
+        @Override
+        public String toString() {
+            return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+                                "\", mModel=\"" + mModel +
+                                "\", mVersion=\"" + mVersion + "\"]";
+        }
+    }
+
+    private class MyPackageMonitor extends PackageMonitor {
+        @Override
+        public void onPackageAdded(String packageName, int uid) {
+            handlePackageUpdate(packageName);
+        }
+
+        @Override
+        public boolean onPackageChanged(String packageName, int uid, String[] components) {
+            handlePackageUpdate(packageName);
+            return false;
+        }
+
+        @Override
+        public void onPackageRemoved(String packageName, int uid) {
+            clearDefaults(packageName);
+        }
+    }
+
+    MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
+
+    public UsbSettingsManager(Context context, UserHandle user) {
+        if (DEBUG) Slog.v(TAG, "Creating settings for " + user);
+
+        try {
+            mUserContext = context.createPackageContextAsUser("android", 0, user);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException("Missing android package");
+        }
+
+        mContext = context;
+        mPackageManager = mUserContext.getPackageManager();
+
+        mUser = user;
+        mSettingsFile = new AtomicFile(new File(
+                Environment.getUserSystemDirectory(user.getIdentifier()),
+                "usb_device_manager.xml"));
+
+        synchronized (mLock) {
+            if (UserHandle.OWNER.equals(user)) {
+                upgradeSingleUserLocked();
+            }
+            readSettingsLocked();
+        }
+
+        mPackageMonitor.register(mUserContext, null, true);
+    }
+
+    private void readPreference(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        String packageName = null;
+        int count = parser.getAttributeCount();
+        for (int i = 0; i < count; i++) {
+            if ("package".equals(parser.getAttributeName(i))) {
+                packageName = parser.getAttributeValue(i);
+                break;
+            }
+        }
+        XmlUtils.nextElement(parser);
+        if ("usb-device".equals(parser.getName())) {
+            DeviceFilter filter = DeviceFilter.read(parser);
+            mDevicePreferenceMap.put(filter, packageName);
+        } else if ("usb-accessory".equals(parser.getName())) {
+            AccessoryFilter filter = AccessoryFilter.read(parser);
+            mAccessoryPreferenceMap.put(filter, packageName);
+        }
+        XmlUtils.nextElement(parser);
+    }
+
+    /**
+     * Upgrade any single-user settings from {@link #sSingleUserSettingsFile}.
+     * Should only by called by owner.
+     */
+    private void upgradeSingleUserLocked() {
+        if (sSingleUserSettingsFile.exists()) {
+            mDevicePreferenceMap.clear();
+            mAccessoryPreferenceMap.clear();
+
+            FileInputStream fis = null;
+            try {
+                fis = new FileInputStream(sSingleUserSettingsFile);
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(fis, null);
+
+                XmlUtils.nextElement(parser);
+                while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                    final String tagName = parser.getName();
+                    if ("preference".equals(tagName)) {
+                        readPreference(parser);
+                    } else {
+                        XmlUtils.nextElement(parser);
+                    }
+                }
+            } catch (IOException e) {
+                Log.wtf(TAG, "Failed to read single-user settings", e);
+            } catch (XmlPullParserException e) {
+                Log.wtf(TAG, "Failed to read single-user settings", e);
+            } finally {
+                IoUtils.closeQuietly(fis);
+            }
+
+            writeSettingsLocked();
+
+            // Success or failure, we delete single-user file
+            sSingleUserSettingsFile.delete();
+        }
+    }
+
+    private void readSettingsLocked() {
+        if (DEBUG) Slog.v(TAG, "readSettingsLocked()");
+
+        mDevicePreferenceMap.clear();
+        mAccessoryPreferenceMap.clear();
+
+        FileInputStream stream = null;
+        try {
+            stream = mSettingsFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, null);
+
+            XmlUtils.nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if ("preference".equals(tagName)) {
+                    readPreference(parser);
+                } else {
+                    XmlUtils.nextElement(parser);
+                }
+            }
+        } catch (FileNotFoundException e) {
+            if (DEBUG) Slog.d(TAG, "settings file not found");
+        } catch (Exception e) {
+            Slog.e(TAG, "error reading settings file, deleting to start fresh", e);
+            mSettingsFile.delete();
+        } finally {
+            IoUtils.closeQuietly(stream);
+        }
+    }
+
+    private void writeSettingsLocked() {
+        if (DEBUG) Slog.v(TAG, "writeSettingsLocked()");
+
+        FileOutputStream fos = null;
+        try {
+            fos = mSettingsFile.startWrite();
+
+            FastXmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(fos, "utf-8");
+            serializer.startDocument(null, true);
+            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            serializer.startTag(null, "settings");
+
+            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
+                serializer.startTag(null, "preference");
+                serializer.attribute(null, "package", mDevicePreferenceMap.get(filter));
+                filter.write(serializer);
+                serializer.endTag(null, "preference");
+            }
+
+            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
+                serializer.startTag(null, "preference");
+                serializer.attribute(null, "package", mAccessoryPreferenceMap.get(filter));
+                filter.write(serializer);
+                serializer.endTag(null, "preference");
+            }
+
+            serializer.endTag(null, "settings");
+            serializer.endDocument();
+
+            mSettingsFile.finishWrite(fos);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to write settings", e);
+            if (fos != null) {
+                mSettingsFile.failWrite(fos);
+            }
+        }
+    }
+
+    // Checks to see if a package matches a device or accessory.
+    // Only one of device and accessory should be non-null.
+    private boolean packageMatchesLocked(ResolveInfo info, String metaDataName,
+            UsbDevice device, UsbAccessory accessory) {
+        ActivityInfo ai = info.activityInfo;
+
+        XmlResourceParser parser = null;
+        try {
+            parser = ai.loadXmlMetaData(mPackageManager, metaDataName);
+            if (parser == null) {
+                Slog.w(TAG, "no meta-data for " + info);
+                return false;
+            }
+
+            XmlUtils.nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if (device != null && "usb-device".equals(tagName)) {
+                    DeviceFilter filter = DeviceFilter.read(parser);
+                    if (filter.matches(device)) {
+                        return true;
+                    }
+                }
+                else if (accessory != null && "usb-accessory".equals(tagName)) {
+                    AccessoryFilter filter = AccessoryFilter.read(parser);
+                    if (filter.matches(accessory)) {
+                        return true;
+                    }
+                }
+                XmlUtils.nextElement(parser);
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Unable to load component info " + info.toString(), e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        return false;
+    }
+
+    private final ArrayList<ResolveInfo> getDeviceMatchesLocked(UsbDevice device, Intent intent) {
+        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
+                PackageManager.GET_META_DATA);
+        int count = resolveInfos.size();
+        for (int i = 0; i < count; i++) {
+            ResolveInfo resolveInfo = resolveInfos.get(i);
+            if (packageMatchesLocked(resolveInfo, intent.getAction(), device, null)) {
+                matches.add(resolveInfo);
+            }
+        }
+        return matches;
+    }
+
+    private final ArrayList<ResolveInfo> getAccessoryMatchesLocked(
+            UsbAccessory accessory, Intent intent) {
+        ArrayList<ResolveInfo> matches = new ArrayList<ResolveInfo>();
+        List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(intent,
+                PackageManager.GET_META_DATA);
+        int count = resolveInfos.size();
+        for (int i = 0; i < count; i++) {
+            ResolveInfo resolveInfo = resolveInfos.get(i);
+            if (packageMatchesLocked(resolveInfo, intent.getAction(), null, accessory)) {
+                matches.add(resolveInfo);
+            }
+        }
+        return matches;
+    }
+
+    public void deviceAttached(UsbDevice device) {
+        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ArrayList<ResolveInfo> matches;
+        String defaultPackage;
+        synchronized (mLock) {
+            matches = getDeviceMatchesLocked(device, intent);
+            // Launch our default activity directly, if we have one.
+            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
+            defaultPackage = mDevicePreferenceMap.get(new DeviceFilter(device));
+        }
+
+        // Send broadcast to running activity with registered intent
+        mUserContext.sendBroadcast(intent);
+
+        // Start activity with registered intent
+        resolveActivity(intent, matches, defaultPackage, device, null);
+    }
+
+    public void deviceDetached(UsbDevice device) {
+        // clear temporary permissions for the device
+        mDevicePermissionMap.remove(device.getDeviceName());
+
+        Intent intent = new Intent(UsbManager.ACTION_USB_DEVICE_DETACHED);
+        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+        if (DEBUG) Slog.d(TAG, "usbDeviceRemoved, sending " + intent);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    public void accessoryAttached(UsbAccessory accessory) {
+        Intent intent = new Intent(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
+        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ArrayList<ResolveInfo> matches;
+        String defaultPackage;
+        synchronized (mLock) {
+            matches = getAccessoryMatchesLocked(accessory, intent);
+            // Launch our default activity directly, if we have one.
+            // Otherwise we will start the UsbResolverActivity to allow the user to choose.
+            defaultPackage = mAccessoryPreferenceMap.get(new AccessoryFilter(accessory));
+        }
+
+        resolveActivity(intent, matches, defaultPackage, null, accessory);
+    }
+
+    public void accessoryDetached(UsbAccessory accessory) {
+        // clear temporary permissions for the accessory
+        mAccessoryPermissionMap.remove(accessory);
+
+        Intent intent = new Intent(
+                UsbManager.ACTION_USB_ACCESSORY_DETACHED);
+        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void resolveActivity(Intent intent, ArrayList<ResolveInfo> matches,
+            String defaultPackage, UsbDevice device, UsbAccessory accessory) {
+        int count = matches.size();
+
+        // don't show the resolver activity if there are no choices available
+        if (count == 0) {
+            if (accessory != null) {
+                String uri = accessory.getUri();
+                if (uri != null && uri.length() > 0) {
+                    // display URI to user
+                    // start UsbResolverActivity so user can choose an activity
+                    Intent dialogIntent = new Intent();
+                    dialogIntent.setClassName("com.android.systemui",
+                            "com.android.systemui.usb.UsbAccessoryUriActivity");
+                    dialogIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    dialogIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+                    dialogIntent.putExtra("uri", uri);
+                    try {
+                        mUserContext.startActivityAsUser(dialogIntent, mUser);
+                    } catch (ActivityNotFoundException e) {
+                        Slog.e(TAG, "unable to start UsbAccessoryUriActivity");
+                    }
+                }
+            }
+
+            // do nothing
+            return;
+        }
+
+        ResolveInfo defaultRI = null;
+        if (count == 1 && defaultPackage == null) {
+            // Check to see if our single choice is on the system partition.
+            // If so, treat it as our default without calling UsbResolverActivity
+            ResolveInfo rInfo = matches.get(0);
+            if (rInfo.activityInfo != null &&
+                    rInfo.activityInfo.applicationInfo != null &&
+                    (rInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                defaultRI = rInfo;
+            }
+        }
+
+        if (defaultRI == null && defaultPackage != null) {
+            // look for default activity
+            for (int i = 0; i < count; i++) {
+                ResolveInfo rInfo = matches.get(i);
+                if (rInfo.activityInfo != null &&
+                        defaultPackage.equals(rInfo.activityInfo.packageName)) {
+                    defaultRI = rInfo;
+                    break;
+                }
+            }
+        }
+
+        if (defaultRI != null) {
+            // grant permission for default activity
+            if (device != null) {
+                grantDevicePermission(device, defaultRI.activityInfo.applicationInfo.uid);
+            } else if (accessory != null) {
+                grantAccessoryPermission(accessory, defaultRI.activityInfo.applicationInfo.uid);
+            }
+
+            // start default activity directly
+            try {
+                intent.setComponent(
+                        new ComponentName(defaultRI.activityInfo.packageName,
+                                defaultRI.activityInfo.name));
+                mUserContext.startActivityAsUser(intent, mUser);
+            } catch (ActivityNotFoundException e) {
+                Slog.e(TAG, "startActivity failed", e);
+            }
+        } else {
+            Intent resolverIntent = new Intent();
+            resolverIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            if (count == 1) {
+                // start UsbConfirmActivity if there is only one choice
+                resolverIntent.setClassName("com.android.systemui",
+                        "com.android.systemui.usb.UsbConfirmActivity");
+                resolverIntent.putExtra("rinfo", matches.get(0));
+
+                if (device != null) {
+                    resolverIntent.putExtra(UsbManager.EXTRA_DEVICE, device);
+                } else {
+                    resolverIntent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+                }
+            } else {
+                // start UsbResolverActivity so user can choose an activity
+                resolverIntent.setClassName("com.android.systemui",
+                        "com.android.systemui.usb.UsbResolverActivity");
+                resolverIntent.putParcelableArrayListExtra("rlist", matches);
+                resolverIntent.putExtra(Intent.EXTRA_INTENT, intent);
+            }
+            try {
+                mUserContext.startActivityAsUser(resolverIntent, mUser);
+            } catch (ActivityNotFoundException e) {
+                Slog.e(TAG, "unable to start activity " + resolverIntent);
+            }
+        }
+    }
+
+    private boolean clearCompatibleMatchesLocked(String packageName, DeviceFilter filter) {
+        boolean changed = false;
+        for (DeviceFilter test : mDevicePreferenceMap.keySet()) {
+            if (filter.matches(test)) {
+                mDevicePreferenceMap.remove(test);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    private boolean clearCompatibleMatchesLocked(String packageName, AccessoryFilter filter) {
+        boolean changed = false;
+        for (AccessoryFilter test : mAccessoryPreferenceMap.keySet()) {
+            if (filter.matches(test)) {
+                mAccessoryPreferenceMap.remove(test);
+                changed = true;
+            }
+        }
+        return changed;
+    }
+
+    private boolean handlePackageUpdateLocked(String packageName, ActivityInfo aInfo,
+            String metaDataName) {
+        XmlResourceParser parser = null;
+        boolean changed = false;
+
+        try {
+            parser = aInfo.loadXmlMetaData(mPackageManager, metaDataName);
+            if (parser == null) return false;
+
+            XmlUtils.nextElement(parser);
+            while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+                String tagName = parser.getName();
+                if ("usb-device".equals(tagName)) {
+                    DeviceFilter filter = DeviceFilter.read(parser);
+                    if (clearCompatibleMatchesLocked(packageName, filter)) {
+                        changed = true;
+                    }
+                }
+                else if ("usb-accessory".equals(tagName)) {
+                    AccessoryFilter filter = AccessoryFilter.read(parser);
+                    if (clearCompatibleMatchesLocked(packageName, filter)) {
+                        changed = true;
+                    }
+                }
+                XmlUtils.nextElement(parser);
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Unable to load component info " + aInfo.toString(), e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+        return changed;
+    }
+
+    // Check to see if the package supports any USB devices or accessories.
+    // If so, clear any non-matching preferences for matching devices/accessories.
+    private void handlePackageUpdate(String packageName) {
+        synchronized (mLock) {
+            PackageInfo info;
+            boolean changed = false;
+
+            try {
+                info = mPackageManager.getPackageInfo(packageName,
+                        PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+            } catch (NameNotFoundException e) {
+                Slog.e(TAG, "handlePackageUpdate could not find package " + packageName, e);
+                return;
+            }
+
+            ActivityInfo[] activities = info.activities;
+            if (activities == null) return;
+            for (int i = 0; i < activities.length; i++) {
+                // check for meta-data, both for devices and accessories
+                if (handlePackageUpdateLocked(packageName, activities[i],
+                        UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+                    changed = true;
+                }
+                if (handlePackageUpdateLocked(packageName, activities[i],
+                        UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+                    changed = true;
+                }
+            }
+
+            if (changed) {
+                writeSettingsLocked();
+            }
+        }
+    }
+
+    public boolean hasPermission(UsbDevice device) {
+        synchronized (mLock) {
+            int uid = Binder.getCallingUid();
+            if (uid == Process.SYSTEM_UID) {
+                return true;
+            }
+            SparseBooleanArray uidList = mDevicePermissionMap.get(device.getDeviceName());
+            if (uidList == null) {
+                return false;
+            }
+            return uidList.get(uid);
+        }
+    }
+
+    public boolean hasPermission(UsbAccessory accessory) {
+        synchronized (mLock) {
+            int uid = Binder.getCallingUid();
+            if (uid == Process.SYSTEM_UID) {
+                return true;
+            }
+            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+            if (uidList == null) {
+                return false;
+            }
+            return uidList.get(uid);
+        }
+    }
+
+    public void checkPermission(UsbDevice device) {
+        if (!hasPermission(device)) {
+            throw new SecurityException("User has not given permission to device " + device);
+        }
+    }
+
+    public void checkPermission(UsbAccessory accessory) {
+        if (!hasPermission(accessory)) {
+            throw new SecurityException("User has not given permission to accessory " + accessory);
+        }
+    }
+
+    private void requestPermissionDialog(Intent intent, String packageName, PendingIntent pi) {
+        final int uid = Binder.getCallingUid();
+
+        // compare uid with packageName to foil apps pretending to be someone else
+        try {
+            ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+            if (aInfo.uid != uid) {
+                throw new IllegalArgumentException("package " + packageName +
+                        " does not match caller's uid " + uid);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("package " + packageName + " not found");
+        }
+
+        long identity = Binder.clearCallingIdentity();
+        intent.setClassName("com.android.systemui",
+                "com.android.systemui.usb.UsbPermissionActivity");
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(Intent.EXTRA_INTENT, pi);
+        intent.putExtra("package", packageName);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        try {
+            mUserContext.startActivityAsUser(intent, mUser);
+        } catch (ActivityNotFoundException e) {
+            Slog.e(TAG, "unable to start UsbPermissionActivity");
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+      Intent intent = new Intent();
+
+        // respond immediately if permission has already been granted
+      if (hasPermission(device)) {
+            intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
+            try {
+                pi.send(mUserContext, 0, intent);
+            } catch (PendingIntent.CanceledException e) {
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+            }
+            return;
+        }
+
+        // start UsbPermissionActivity so user can choose an activity
+        intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+        requestPermissionDialog(intent, packageName, pi);
+    }
+
+    public void requestPermission(UsbAccessory accessory, String packageName, PendingIntent pi) {
+        Intent intent = new Intent();
+
+        // respond immediately if permission has already been granted
+        if (hasPermission(accessory)) {
+            intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+            intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
+            try {
+                pi.send(mUserContext, 0, intent);
+            } catch (PendingIntent.CanceledException e) {
+                if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+            }
+            return;
+        }
+
+        intent.putExtra(UsbManager.EXTRA_ACCESSORY, accessory);
+        requestPermissionDialog(intent, packageName, pi);
+    }
+
+    public void setDevicePackage(UsbDevice device, String packageName) {
+        DeviceFilter filter = new DeviceFilter(device);
+        boolean changed = false;
+        synchronized (mLock) {
+            if (packageName == null) {
+                changed = (mDevicePreferenceMap.remove(filter) != null);
+            } else {
+                changed = !packageName.equals(mDevicePreferenceMap.get(filter));
+                if (changed) {
+                    mDevicePreferenceMap.put(filter, packageName);
+                }
+            }
+            if (changed) {
+                writeSettingsLocked();
+            }
+        }
+    }
+
+    public void setAccessoryPackage(UsbAccessory accessory, String packageName) {
+        AccessoryFilter filter = new AccessoryFilter(accessory);
+        boolean changed = false;
+        synchronized (mLock) {
+            if (packageName == null) {
+                changed = (mAccessoryPreferenceMap.remove(filter) != null);
+            } else {
+                changed = !packageName.equals(mAccessoryPreferenceMap.get(filter));
+                if (changed) {
+                    mAccessoryPreferenceMap.put(filter, packageName);
+                }
+            }
+            if (changed) {
+                writeSettingsLocked();
+            }
+        }
+    }
+
+    public void grantDevicePermission(UsbDevice device, int uid) {
+        synchronized (mLock) {
+            String deviceName = device.getDeviceName();
+            SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+            if (uidList == null) {
+                uidList = new SparseBooleanArray(1);
+                mDevicePermissionMap.put(deviceName, uidList);
+            }
+            uidList.put(uid, true);
+        }
+    }
+
+    public void grantAccessoryPermission(UsbAccessory accessory, int uid) {
+        synchronized (mLock) {
+            SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+            if (uidList == null) {
+                uidList = new SparseBooleanArray(1);
+                mAccessoryPermissionMap.put(accessory, uidList);
+            }
+            uidList.put(uid, true);
+        }
+    }
+
+    public boolean hasDefaults(String packageName) {
+        synchronized (mLock) {
+            if (mDevicePreferenceMap.values().contains(packageName)) return true;
+            if (mAccessoryPreferenceMap.values().contains(packageName)) return true;
+            return false;
+        }
+    }
+
+    public void clearDefaults(String packageName) {
+        synchronized (mLock) {
+            if (clearPackageDefaultsLocked(packageName)) {
+                writeSettingsLocked();
+            }
+        }
+    }
+
+    private boolean clearPackageDefaultsLocked(String packageName) {
+        boolean cleared = false;
+        synchronized (mLock) {
+            if (mDevicePreferenceMap.containsValue(packageName)) {
+                // make a copy of the key set to avoid ConcurrentModificationException
+                Object[] keys = mDevicePreferenceMap.keySet().toArray();
+                for (int i = 0; i < keys.length; i++) {
+                    Object key = keys[i];
+                    if (packageName.equals(mDevicePreferenceMap.get(key))) {
+                        mDevicePreferenceMap.remove(key);
+                        cleared = true;
+                    }
+                }
+            }
+            if (mAccessoryPreferenceMap.containsValue(packageName)) {
+                // make a copy of the key set to avoid ConcurrentModificationException
+                Object[] keys = mAccessoryPreferenceMap.keySet().toArray();
+                for (int i = 0; i < keys.length; i++) {
+                    Object key = keys[i];
+                    if (packageName.equals(mAccessoryPreferenceMap.get(key))) {
+                        mAccessoryPreferenceMap.remove(key);
+                        cleared = true;
+                    }
+                }
+            }
+            return cleared;
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("  Device permissions:");
+            for (String deviceName : mDevicePermissionMap.keySet()) {
+                pw.print("    " + deviceName + ": ");
+                SparseBooleanArray uidList = mDevicePermissionMap.get(deviceName);
+                int count = uidList.size();
+                for (int i = 0; i < count; i++) {
+                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
+                }
+                pw.println("");
+            }
+            pw.println("  Accessory permissions:");
+            for (UsbAccessory accessory : mAccessoryPermissionMap.keySet()) {
+                pw.print("    " + accessory + ": ");
+                SparseBooleanArray uidList = mAccessoryPermissionMap.get(accessory);
+                int count = uidList.size();
+                for (int i = 0; i < count; i++) {
+                    pw.print(Integer.toString(uidList.keyAt(i)) + " ");
+                }
+                pw.println("");
+            }
+            pw.println("  Device preferences:");
+            for (DeviceFilter filter : mDevicePreferenceMap.keySet()) {
+                pw.println("    " + filter + ": " + mDevicePreferenceMap.get(filter));
+            }
+            pw.println("  Accessory preferences:");
+            for (AccessoryFilter filter : mAccessoryPreferenceMap.keySet()) {
+                pw.println("    " + filter + ": " + mAccessoryPreferenceMap.get(filter));
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
new file mode 100644
index 0000000..97ea52c
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -0,0 +1,1353 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wallpaper;
+
+import static android.os.ParcelFileDescriptor.*;
+
+import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
+import android.app.IUserSwitchObserver;
+import android.app.IWallpaperManager;
+import android.app.IWallpaperManagerCallback;
+import android.app.PendingIntent;
+import android.app.WallpaperInfo;
+import android.app.backup.BackupManager;
+import android.app.backup.WallpaperBackupHelper;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.FileObserver;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteCallbackList;
+import android.os.SELinux;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.wallpaper.IWallpaperConnection;
+import android.service.wallpaper.IWallpaperEngine;
+import android.service.wallpaper.IWallpaperService;
+import android.service.wallpaper.WallpaperService;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+import android.view.Display;
+import android.view.IWindowManager;
+import android.view.WindowManager;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.PrintWriter;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.JournaledFile;
+
+public class WallpaperManagerService extends IWallpaperManager.Stub {
+    static final String TAG = "WallpaperManagerService";
+    static final boolean DEBUG = false;
+
+    final Object mLock = new Object[0];
+
+    /**
+     * Minimum time between crashes of a wallpaper service for us to consider
+     * restarting it vs. just reverting to the static wallpaper.
+     */
+    static final long MIN_WALLPAPER_CRASH_TIME = 10000;
+    static final String WALLPAPER = "wallpaper";
+    static final String WALLPAPER_INFO = "wallpaper_info.xml";
+    /**
+     * Name of the component used to display bitmap wallpapers from either the gallery or
+     * built-in wallpapers.
+     */
+    static final ComponentName IMAGE_WALLPAPER = new ComponentName("com.android.systemui",
+            "com.android.systemui.ImageWallpaper");
+
+    /**
+     * Observes the wallpaper for changes and notifies all IWallpaperServiceCallbacks
+     * that the wallpaper has changed. The CREATE is triggered when there is no
+     * wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
+     * everytime the wallpaper is changed.
+     */
+    private class WallpaperObserver extends FileObserver {
+
+        final WallpaperData mWallpaper;
+        final File mWallpaperDir;
+        final File mWallpaperFile;
+
+        public WallpaperObserver(WallpaperData wallpaper) {
+            super(getWallpaperDir(wallpaper.userId).getAbsolutePath(),
+                    CLOSE_WRITE | DELETE | DELETE_SELF);
+            mWallpaperDir = getWallpaperDir(wallpaper.userId);
+            mWallpaper = wallpaper;
+            mWallpaperFile = new File(mWallpaperDir, WALLPAPER);
+        }
+
+        @Override
+        public void onEvent(int event, String path) {
+            if (path == null) {
+                return;
+            }
+            synchronized (mLock) {
+                // changing the wallpaper means we'll need to back up the new one
+                long origId = Binder.clearCallingIdentity();
+                BackupManager bm = new BackupManager(mContext);
+                bm.dataChanged();
+                Binder.restoreCallingIdentity(origId);
+
+                File changedFile = new File(mWallpaperDir, path);
+                if (mWallpaperFile.equals(changedFile)) {
+                    notifyCallbacksLocked(mWallpaper);
+                    if (mWallpaper.wallpaperComponent == null || event != CLOSE_WRITE
+                            || mWallpaper.imageWallpaperPending) {
+                        if (event == CLOSE_WRITE) {
+                            mWallpaper.imageWallpaperPending = false;
+                        }
+                        bindWallpaperComponentLocked(IMAGE_WALLPAPER, true,
+                                false, mWallpaper, null);
+                        saveSettingsLocked(mWallpaper);
+                    }
+                }
+            }
+        }
+    }
+
+    final Context mContext;
+    final IWindowManager mIWindowManager;
+    final IPackageManager mIPackageManager;
+    final MyPackageMonitor mMonitor;
+    WallpaperData mLastWallpaper;
+
+    SparseArray<WallpaperData> mWallpaperMap = new SparseArray<WallpaperData>();
+
+    int mCurrentUserId;
+
+    static class WallpaperData {
+
+        int userId;
+
+        File wallpaperFile;
+
+        /**
+         * Client is currently writing a new image wallpaper.
+         */
+        boolean imageWallpaperPending;
+
+        /**
+         * Resource name if using a picture from the wallpaper gallery
+         */
+        String name = "";
+
+        /**
+         * The component name of the currently set live wallpaper.
+         */
+        ComponentName wallpaperComponent;
+
+        /**
+         * The component name of the wallpaper that should be set next.
+         */
+        ComponentName nextWallpaperComponent;
+
+        WallpaperConnection connection;
+        long lastDiedTime;
+        boolean wallpaperUpdating;
+        WallpaperObserver wallpaperObserver;
+
+        /**
+         * List of callbacks registered they should each be notified when the wallpaper is changed.
+         */
+        private RemoteCallbackList<IWallpaperManagerCallback> callbacks
+                = new RemoteCallbackList<IWallpaperManagerCallback>();
+
+        int width = -1;
+        int height = -1;
+
+        WallpaperData(int userId) {
+            this.userId = userId;
+            wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
+        }
+    }
+
+    class WallpaperConnection extends IWallpaperConnection.Stub
+            implements ServiceConnection {
+        final WallpaperInfo mInfo;
+        final Binder mToken = new Binder();
+        IWallpaperService mService;
+        IWallpaperEngine mEngine;
+        WallpaperData mWallpaper;
+        IRemoteCallback mReply;
+
+        boolean mDimensionsChanged = false;
+
+        public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) {
+            mInfo = info;
+            mWallpaper = wallpaper;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            synchronized (mLock) {
+                if (mWallpaper.connection == this) {
+                    mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
+                    mService = IWallpaperService.Stub.asInterface(service);
+                    attachServiceLocked(this, mWallpaper);
+                    // XXX should probably do saveSettingsLocked() later
+                    // when we have an engine, but I'm not sure about
+                    // locking there and anyway we always need to be able to
+                    // recover if there is something wrong.
+                    saveSettingsLocked(mWallpaper);
+                }
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            synchronized (mLock) {
+                mService = null;
+                mEngine = null;
+                if (mWallpaper.connection == this) {
+                    Slog.w(TAG, "Wallpaper service gone: " + mWallpaper.wallpaperComponent);
+                    if (!mWallpaper.wallpaperUpdating
+                            && (mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME)
+                                > SystemClock.uptimeMillis()
+                            && mWallpaper.userId == mCurrentUserId) {
+                        Slog.w(TAG, "Reverting to built-in wallpaper!");
+                        clearWallpaperLocked(true, mWallpaper.userId, null);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void attachEngine(IWallpaperEngine engine) {
+            synchronized (mLock) {
+                mEngine = engine;
+                if (mDimensionsChanged) {
+                    try {
+                        mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to set wallpaper dimensions", e);
+                    }
+                    mDimensionsChanged = false;
+                }
+            }
+        }
+
+        @Override
+        public void engineShown(IWallpaperEngine engine) {
+            synchronized (mLock) {
+                if (mReply != null) {
+                    long ident = Binder.clearCallingIdentity();
+                    try {
+                        mReply.sendResult(null);
+                    } catch (RemoteException e) {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                    mReply = null;
+                }
+            }
+        }
+
+        @Override
+        public ParcelFileDescriptor setWallpaper(String name) {
+            synchronized (mLock) {
+                if (mWallpaper.connection == this) {
+                    return updateWallpaperBitmapLocked(name, mWallpaper);
+                }
+                return null;
+            }
+        }
+    }
+
+    class MyPackageMonitor extends PackageMonitor {
+        @Override
+        public void onPackageUpdateFinished(String packageName, int uid) {
+            synchronized (mLock) {
+                if (mCurrentUserId != getChangingUserId()) {
+                    return;
+                }
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                if (wallpaper != null) {
+                    if (wallpaper.wallpaperComponent != null
+                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+                        wallpaper.wallpaperUpdating = false;
+                        ComponentName comp = wallpaper.wallpaperComponent;
+                        clearWallpaperComponentLocked(wallpaper);
+                        if (!bindWallpaperComponentLocked(comp, false, false,
+                                wallpaper, null)) {
+                            Slog.w(TAG, "Wallpaper no longer available; reverting to default");
+                            clearWallpaperLocked(false, wallpaper.userId, null);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onPackageModified(String packageName) {
+            synchronized (mLock) {
+                if (mCurrentUserId != getChangingUserId()) {
+                    return;
+                }
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                if (wallpaper != null) {
+                    if (wallpaper.wallpaperComponent == null
+                            || !wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+                        return;
+                    }
+                    doPackagesChangedLocked(true, wallpaper);
+                }
+            }
+        }
+
+        @Override
+        public void onPackageUpdateStarted(String packageName, int uid) {
+            synchronized (mLock) {
+                if (mCurrentUserId != getChangingUserId()) {
+                    return;
+                }
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                if (wallpaper != null) {
+                    if (wallpaper.wallpaperComponent != null
+                            && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) {
+                        wallpaper.wallpaperUpdating = true;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+            synchronized (mLock) {
+                boolean changed = false;
+                if (mCurrentUserId != getChangingUserId()) {
+                    return false;
+                }
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                if (wallpaper != null) {
+                    boolean res = doPackagesChangedLocked(doit, wallpaper);
+                    changed |= res;
+                }
+                return changed;
+            }
+        }
+
+        @Override
+        public void onSomePackagesChanged() {
+            synchronized (mLock) {
+                if (mCurrentUserId != getChangingUserId()) {
+                    return;
+                }
+                WallpaperData wallpaper = mWallpaperMap.get(mCurrentUserId);
+                if (wallpaper != null) {
+                    doPackagesChangedLocked(true, wallpaper);
+                }
+            }
+        }
+
+        boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) {
+            boolean changed = false;
+            if (wallpaper.wallpaperComponent != null) {
+                int change = isPackageDisappearing(wallpaper.wallpaperComponent
+                        .getPackageName());
+                if (change == PACKAGE_PERMANENT_CHANGE
+                        || change == PACKAGE_TEMPORARY_CHANGE) {
+                    changed = true;
+                    if (doit) {
+                        Slog.w(TAG, "Wallpaper uninstalled, removing: "
+                                + wallpaper.wallpaperComponent);
+                        clearWallpaperLocked(false, wallpaper.userId, null);
+                    }
+                }
+            }
+            if (wallpaper.nextWallpaperComponent != null) {
+                int change = isPackageDisappearing(wallpaper.nextWallpaperComponent
+                        .getPackageName());
+                if (change == PACKAGE_PERMANENT_CHANGE
+                        || change == PACKAGE_TEMPORARY_CHANGE) {
+                    wallpaper.nextWallpaperComponent = null;
+                }
+            }
+            if (wallpaper.wallpaperComponent != null
+                    && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) {
+                try {
+                    mContext.getPackageManager().getServiceInfo(
+                            wallpaper.wallpaperComponent, 0);
+                } catch (NameNotFoundException e) {
+                    Slog.w(TAG, "Wallpaper component gone, removing: "
+                            + wallpaper.wallpaperComponent);
+                    clearWallpaperLocked(false, wallpaper.userId, null);
+                }
+            }
+            if (wallpaper.nextWallpaperComponent != null
+                    && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) {
+                try {
+                    mContext.getPackageManager().getServiceInfo(
+                            wallpaper.nextWallpaperComponent, 0);
+                } catch (NameNotFoundException e) {
+                    wallpaper.nextWallpaperComponent = null;
+                }
+            }
+            return changed;
+        }
+    }
+    
+    public WallpaperManagerService(Context context) {
+        if (DEBUG) Slog.v(TAG, "WallpaperService startup");
+        mContext = context;
+        mIWindowManager = IWindowManager.Stub.asInterface(
+                ServiceManager.getService(Context.WINDOW_SERVICE));
+        mIPackageManager = AppGlobals.getPackageManager();
+        mMonitor = new MyPackageMonitor();
+        mMonitor.register(context, null, UserHandle.ALL, true);
+        getWallpaperDir(UserHandle.USER_OWNER).mkdirs();
+        loadSettingsLocked(UserHandle.USER_OWNER);
+    }
+    
+    private static File getWallpaperDir(int userId) {
+        return Environment.getUserSystemDirectory(userId);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        for (int i = 0; i < mWallpaperMap.size(); i++) {
+            WallpaperData wallpaper = mWallpaperMap.valueAt(i);
+            wallpaper.wallpaperObserver.stopWatching();
+        }
+    }
+
+    public void systemRunning() {
+        if (DEBUG) Slog.v(TAG, "systemReady");
+        WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER);
+        switchWallpaper(wallpaper, null);
+        wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
+        wallpaper.wallpaperObserver.startWatching();
+
+        IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        userFilter.addAction(Intent.ACTION_USER_STOPPING);
+        mContext.registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                    onRemoveUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                            UserHandle.USER_NULL));
+                }
+                // TODO: Race condition causing problems when cleaning up on stopping a user.
+                // Comment this out for now.
+                // else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+                //     onStoppingUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                //             UserHandle.USER_NULL));
+                // }
+            }
+        }, userFilter);
+
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            switchUser(newUserId, reply);
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+        } catch (RemoteException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /** Called by SystemBackupAgent */
+    public String getName() {
+        // Verify caller is the system
+        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+            throw new RuntimeException("getName() can only be called from the system process");
+        }
+        synchronized (mLock) {
+            return mWallpaperMap.get(0).name;
+        }
+    }
+
+    void onStoppingUser(int userId) {
+        if (userId < 1) return;
+        synchronized (mLock) {
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper != null) {
+                if (wallpaper.wallpaperObserver != null) {
+                    wallpaper.wallpaperObserver.stopWatching();
+                    wallpaper.wallpaperObserver = null;
+                }
+                mWallpaperMap.remove(userId);
+            }
+        }
+    }
+
+    void onRemoveUser(int userId) {
+        if (userId < 1) return;
+        synchronized (mLock) {
+            onStoppingUser(userId);
+            File wallpaperFile = new File(getWallpaperDir(userId), WALLPAPER);
+            wallpaperFile.delete();
+            File wallpaperInfoFile = new File(getWallpaperDir(userId), WALLPAPER_INFO);
+            wallpaperInfoFile.delete();
+        }
+    }
+
+    void switchUser(int userId, IRemoteCallback reply) {
+        synchronized (mLock) {
+            mCurrentUserId = userId;
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                wallpaper = new WallpaperData(userId);
+                mWallpaperMap.put(userId, wallpaper);
+                loadSettingsLocked(userId);
+            }
+            // Not started watching yet, in case wallpaper data was loaded for other reasons.
+            if (wallpaper.wallpaperObserver == null) {
+                wallpaper.wallpaperObserver = new WallpaperObserver(wallpaper);
+                wallpaper.wallpaperObserver.startWatching();
+            }
+            switchWallpaper(wallpaper, reply);
+        }
+    }
+
+    void switchWallpaper(WallpaperData wallpaper, IRemoteCallback reply) {
+        synchronized (mLock) {
+            RuntimeException e = null;
+            try {
+                ComponentName cname = wallpaper.wallpaperComponent != null ?
+                        wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent;
+                if (bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) {
+                    return;
+                }
+            } catch (RuntimeException e1) {
+                e = e1;
+            }
+            Slog.w(TAG, "Failure starting previous wallpaper", e);
+            clearWallpaperLocked(false, wallpaper.userId, reply);
+        }
+    }
+
+    public void clearWallpaper() {
+        if (DEBUG) Slog.v(TAG, "clearWallpaper");
+        synchronized (mLock) {
+            clearWallpaperLocked(false, UserHandle.getCallingUserId(), null);
+        }
+    }
+
+    void clearWallpaperLocked(boolean defaultFailed, int userId, IRemoteCallback reply) {
+        WallpaperData wallpaper = mWallpaperMap.get(userId);
+        File f = new File(getWallpaperDir(userId), WALLPAPER);
+        if (f.exists()) {
+            f.delete();
+        }
+        final long ident = Binder.clearCallingIdentity();
+        RuntimeException e = null;
+        try {
+            wallpaper.imageWallpaperPending = false;
+            if (userId != mCurrentUserId) return;
+            if (bindWallpaperComponentLocked(defaultFailed
+                    ? IMAGE_WALLPAPER
+                    : null, true, false, wallpaper, reply)) {
+                return;
+            }
+        } catch (IllegalArgumentException e1) {
+            e = e1;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        
+        // This can happen if the default wallpaper component doesn't
+        // exist.  This should be a system configuration problem, but
+        // let's not let it crash the system and just live with no
+        // wallpaper.
+        Slog.e(TAG, "Default wallpaper component not found!", e);
+        clearWallpaperComponentLocked(wallpaper);
+        if (reply != null) {
+            try {
+                reply.sendResult(null);
+            } catch (RemoteException e1) {
+            }
+        }
+    }
+
+    public boolean hasNamedWallpaper(String name) {
+        synchronized (mLock) {
+            List<UserInfo> users;
+            long ident = Binder.clearCallingIdentity();
+            try {
+                users = ((UserManager) mContext.getSystemService(Context.USER_SERVICE)).getUsers();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            for (UserInfo user: users) {
+                WallpaperData wd = mWallpaperMap.get(user.id);
+                if (wd == null) {
+                    // User hasn't started yet, so load her settings to peek at the wallpaper
+                    loadSettingsLocked(user.id);
+                    wd = mWallpaperMap.get(user.id);
+                }
+                if (wd != null && name.equals(wd.name)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private Point getDefaultDisplaySize() {
+        Point p = new Point();
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        Display d = wm.getDefaultDisplay();
+        d.getRealSize(p);
+        return p;
+    }
+
+    public void setDimensionHints(int width, int height) throws RemoteException {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER_HINTS);
+        synchronized (mLock) {
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
+            if (width <= 0 || height <= 0) {
+                throw new IllegalArgumentException("width and height must be > 0");
+            }
+            // Make sure it is at least as large as the display.
+            Point displaySize = getDefaultDisplaySize();
+            width = Math.max(width, displaySize.x);
+            height = Math.max(height, displaySize.y);
+
+            if (width != wallpaper.width || height != wallpaper.height) {
+                wallpaper.width = width;
+                wallpaper.height = height;
+                saveSettingsLocked(wallpaper);
+                if (mCurrentUserId != userId) return; // Don't change the properties now
+                if (wallpaper.connection != null) {
+                    if (wallpaper.connection.mEngine != null) {
+                        try {
+                            wallpaper.connection.mEngine.setDesiredSize(
+                                    width, height);
+                        } catch (RemoteException e) {
+                        }
+                        notifyCallbacksLocked(wallpaper);
+                    } else if (wallpaper.connection.mService != null) {
+                        // We've attached to the service but the engine hasn't attached back to us
+                        // yet. This means it will be created with the previous dimensions, so we
+                        // need to update it to the new dimensions once it attaches.
+                        wallpaper.connection.mDimensionsChanged = true;
+                    }
+                }
+            }
+        }
+    }
+
+    public int getWidthHint() throws RemoteException {
+        synchronized (mLock) {
+            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
+            return wallpaper.width;
+        }
+    }
+
+    public int getHeightHint() throws RemoteException {
+        synchronized (mLock) {
+            WallpaperData wallpaper = mWallpaperMap.get(UserHandle.getCallingUserId());
+            return wallpaper.height;
+        }
+    }
+
+    public ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb,
+            Bundle outParams) {
+        synchronized (mLock) {
+            // This returns the current user's wallpaper, if called by a system service. Else it
+            // returns the wallpaper for the calling user.
+            int callingUid = Binder.getCallingUid();
+            int wallpaperUserId = 0;
+            if (callingUid == android.os.Process.SYSTEM_UID) {
+                wallpaperUserId = mCurrentUserId;
+            } else {
+                wallpaperUserId = UserHandle.getUserId(callingUid);
+            }
+            WallpaperData wallpaper = mWallpaperMap.get(wallpaperUserId);
+            try {
+                if (outParams != null) {
+                    outParams.putInt("width", wallpaper.width);
+                    outParams.putInt("height", wallpaper.height);
+                }
+                wallpaper.callbacks.register(cb);
+                File f = new File(getWallpaperDir(wallpaperUserId), WALLPAPER);
+                if (!f.exists()) {
+                    return null;
+                }
+                return ParcelFileDescriptor.open(f, MODE_READ_ONLY);
+            } catch (FileNotFoundException e) {
+                /* Shouldn't happen as we check to see if the file exists */
+                Slog.w(TAG, "Error getting wallpaper", e);
+            }
+            return null;
+        }
+    }
+
+    public WallpaperInfo getWallpaperInfo() {
+        int userId = UserHandle.getCallingUserId();
+        synchronized (mLock) {
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper.connection != null) {
+                return wallpaper.connection.mInfo;
+            }
+            return null;
+        }
+    }
+
+    public ParcelFileDescriptor setWallpaper(String name) {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER);
+        synchronized (mLock) {
+            if (DEBUG) Slog.v(TAG, "setWallpaper");
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                ParcelFileDescriptor pfd = updateWallpaperBitmapLocked(name, wallpaper);
+                if (pfd != null) {
+                    wallpaper.imageWallpaperPending = true;
+                }
+                return pfd;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    ParcelFileDescriptor updateWallpaperBitmapLocked(String name, WallpaperData wallpaper) {
+        if (name == null) name = "";
+        try {
+            File dir = getWallpaperDir(wallpaper.userId);
+            if (!dir.exists()) {
+                dir.mkdir();
+                FileUtils.setPermissions(
+                        dir.getPath(),
+                        FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
+                        -1, -1);
+            }
+            File file = new File(dir, WALLPAPER);
+            ParcelFileDescriptor fd = ParcelFileDescriptor.open(file,
+                    MODE_CREATE|MODE_READ_WRITE);
+            if (!SELinux.restorecon(file)) {
+                return null;
+            }
+            wallpaper.name = name;
+            return fd;
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, "Error setting wallpaper", e);
+        }
+        return null;
+    }
+
+    public void setWallpaperComponent(ComponentName name) {
+        checkPermission(android.Manifest.permission.SET_WALLPAPER_COMPONENT);
+        synchronized (mLock) {
+            if (DEBUG) Slog.v(TAG, "setWallpaperComponent name=" + name);
+            int userId = UserHandle.getCallingUserId();
+            WallpaperData wallpaper = mWallpaperMap.get(userId);
+            if (wallpaper == null) {
+                throw new IllegalStateException("Wallpaper not yet initialized for user " + userId);
+            }
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                wallpaper.imageWallpaperPending = false;
+                bindWallpaperComponentLocked(name, false, true, wallpaper, null);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+    
+    boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
+            boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
+        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
+        // Has the component changed?
+        if (!force) {
+            if (wallpaper.connection != null) {
+                if (wallpaper.wallpaperComponent == null) {
+                    if (componentName == null) {
+                        if (DEBUG) Slog.v(TAG, "bindWallpaperComponentLocked: still using default");
+                        // Still using default wallpaper.
+                        return true;
+                    }
+                } else if (wallpaper.wallpaperComponent.equals(componentName)) {
+                    // Changing to same wallpaper.
+                    if (DEBUG) Slog.v(TAG, "same wallpaper");
+                    return true;
+                }
+            }
+        }
+        
+        try {
+            if (componentName == null) {
+                String defaultComponent = 
+                    mContext.getString(com.android.internal.R.string.default_wallpaper_component);
+                if (defaultComponent != null) {
+                    // See if there is a default wallpaper component specified
+                    componentName = ComponentName.unflattenFromString(defaultComponent);
+                    if (DEBUG) Slog.v(TAG, "Use default component wallpaper:" + componentName);
+                }
+                if (componentName == null) {
+                    // Fall back to static image wallpaper
+                    componentName = IMAGE_WALLPAPER;
+                    //clearWallpaperComponentLocked();
+                    //return;
+                    if (DEBUG) Slog.v(TAG, "Using image wallpaper");
+                }
+            }
+            int serviceUserId = wallpaper.userId;
+            ServiceInfo si = mIPackageManager.getServiceInfo(componentName,
+                    PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId);
+            if (si == null) {
+                // The wallpaper component we're trying to use doesn't exist
+                Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable");
+                return false;
+            }
+            if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) {
+                String msg = "Selected service does not require "
+                        + android.Manifest.permission.BIND_WALLPAPER
+                        + ": " + componentName;
+                if (fromUser) {
+                    throw new SecurityException(msg);
+                }
+                Slog.w(TAG, msg);
+                return false;
+            }
+            
+            WallpaperInfo wi = null;
+            
+            Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+            if (componentName != null && !componentName.equals(IMAGE_WALLPAPER)) {
+                // Make sure the selected service is actually a wallpaper service.
+                List<ResolveInfo> ris =
+                        mIPackageManager.queryIntentServices(intent,
+                                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                                PackageManager.GET_META_DATA, serviceUserId);
+                for (int i=0; i<ris.size(); i++) {
+                    ServiceInfo rsi = ris.get(i).serviceInfo;
+                    if (rsi.name.equals(si.name) &&
+                            rsi.packageName.equals(si.packageName)) {
+                        try {
+                            wi = new WallpaperInfo(mContext, ris.get(i));
+                        } catch (XmlPullParserException e) {
+                            if (fromUser) {
+                                throw new IllegalArgumentException(e);
+                            }
+                            Slog.w(TAG, e);
+                            return false;
+                        } catch (IOException e) {
+                            if (fromUser) {
+                                throw new IllegalArgumentException(e);
+                            }
+                            Slog.w(TAG, e);
+                            return false;
+                        }
+                        break;
+                    }
+                }
+                if (wi == null) {
+                    String msg = "Selected service is not a wallpaper: "
+                            + componentName;
+                    if (fromUser) {
+                        throw new SecurityException(msg);
+                    }
+                    Slog.w(TAG, msg);
+                    return false;
+                }
+            }
+            
+            // Bind the service!
+            if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
+            WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);
+            intent.setComponent(componentName);
+            intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
+                    com.android.internal.R.string.wallpaper_binding_label);
+            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
+                    mContext, 0,
+                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
+                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
+                    0, null, new UserHandle(serviceUserId)));
+            if (!mContext.bindServiceAsUser(intent, newConn,
+                    Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI,
+                    new UserHandle(serviceUserId))) {
+                String msg = "Unable to bind service: "
+                        + componentName;
+                if (fromUser) {
+                    throw new IllegalArgumentException(msg);
+                }
+                Slog.w(TAG, msg);
+                return false;
+            }
+            if (wallpaper.userId == mCurrentUserId && mLastWallpaper != null) {
+                detachWallpaperLocked(mLastWallpaper);
+            }
+            wallpaper.wallpaperComponent = componentName;
+            wallpaper.connection = newConn;
+            wallpaper.lastDiedTime = SystemClock.uptimeMillis();
+            newConn.mReply = reply;
+            try {
+                if (wallpaper.userId == mCurrentUserId) {
+                    if (DEBUG)
+                        Slog.v(TAG, "Adding window token: " + newConn.mToken);
+                    mIWindowManager.addWindowToken(newConn.mToken,
+                            WindowManager.LayoutParams.TYPE_WALLPAPER);
+                    mLastWallpaper = wallpaper;
+                }
+            } catch (RemoteException e) {
+            }
+        } catch (RemoteException e) {
+            String msg = "Remote exception for " + componentName + "\n" + e;
+            if (fromUser) {
+                throw new IllegalArgumentException(msg);
+            }
+            Slog.w(TAG, msg);
+            return false;
+        }
+        return true;
+    }
+
+    void detachWallpaperLocked(WallpaperData wallpaper) {
+        if (wallpaper.connection != null) {
+            if (wallpaper.connection.mReply != null) {
+                try {
+                    wallpaper.connection.mReply.sendResult(null);
+                } catch (RemoteException e) {
+                }
+                wallpaper.connection.mReply = null;
+            }
+            if (wallpaper.connection.mEngine != null) {
+                try {
+                    wallpaper.connection.mEngine.destroy();
+                } catch (RemoteException e) {
+                }
+            }
+            mContext.unbindService(wallpaper.connection);
+            try {
+                if (DEBUG)
+                    Slog.v(TAG, "Removing window token: " + wallpaper.connection.mToken);
+                mIWindowManager.removeWindowToken(wallpaper.connection.mToken);
+            } catch (RemoteException e) {
+            }
+            wallpaper.connection.mService = null;
+            wallpaper.connection.mEngine = null;
+            wallpaper.connection = null;
+        }
+    }
+
+    void clearWallpaperComponentLocked(WallpaperData wallpaper) {
+        wallpaper.wallpaperComponent = null;
+        detachWallpaperLocked(wallpaper);
+    }
+
+    void attachServiceLocked(WallpaperConnection conn, WallpaperData wallpaper) {
+        try {
+            conn.mService.attach(conn, conn.mToken,
+                    WindowManager.LayoutParams.TYPE_WALLPAPER, false,
+                    wallpaper.width, wallpaper.height);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed attaching wallpaper; clearing", e);
+            if (!wallpaper.wallpaperUpdating) {
+                bindWallpaperComponentLocked(null, false, false, wallpaper, null);
+            }
+        }
+    }
+
+    private void notifyCallbacksLocked(WallpaperData wallpaper) {
+        final int n = wallpaper.callbacks.beginBroadcast();
+        for (int i = 0; i < n; i++) {
+            try {
+                wallpaper.callbacks.getBroadcastItem(i).onWallpaperChanged();
+            } catch (RemoteException e) {
+
+                // The RemoteCallbackList will take care of removing
+                // the dead object for us.
+            }
+        }
+        wallpaper.callbacks.finishBroadcast();
+        final Intent intent = new Intent(Intent.ACTION_WALLPAPER_CHANGED);
+        mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+    }
+
+    private void checkPermission(String permission) {
+        if (PackageManager.PERMISSION_GRANTED!= mContext.checkCallingOrSelfPermission(permission)) {
+            throw new SecurityException("Access denied to process: " + Binder.getCallingPid()
+                    + ", must have permission " + permission);
+        }
+    }
+
+    private static JournaledFile makeJournaledFile(int userId) {
+        final String base = new File(getWallpaperDir(userId), WALLPAPER_INFO).getAbsolutePath();
+        return new JournaledFile(new File(base), new File(base + ".tmp"));
+    }
+
+    private void saveSettingsLocked(WallpaperData wallpaper) {
+        JournaledFile journal = makeJournaledFile(wallpaper.userId);
+        FileOutputStream stream = null;
+        try {
+            stream = new FileOutputStream(journal.chooseForWrite(), false);
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, "utf-8");
+            out.startDocument(null, true);
+
+            out.startTag(null, "wp");
+            out.attribute(null, "width", Integer.toString(wallpaper.width));
+            out.attribute(null, "height", Integer.toString(wallpaper.height));
+            out.attribute(null, "name", wallpaper.name);
+            if (wallpaper.wallpaperComponent != null
+                    && !wallpaper.wallpaperComponent.equals(IMAGE_WALLPAPER)) {
+                out.attribute(null, "component",
+                        wallpaper.wallpaperComponent.flattenToShortString());
+            }
+            out.endTag(null, "wp");
+
+            out.endDocument();
+            stream.close();
+            journal.commit();
+        } catch (IOException e) {
+            try {
+                if (stream != null) {
+                    stream.close();
+                }
+            } catch (IOException ex) {
+                // Ignore
+            }
+            journal.rollback();
+        }
+    }
+
+    private void migrateFromOld() {
+        File oldWallpaper = new File(WallpaperBackupHelper.WALLPAPER_IMAGE_KEY);
+        File oldInfo = new File(WallpaperBackupHelper.WALLPAPER_INFO_KEY);
+        if (oldWallpaper.exists()) {
+            File newWallpaper = new File(getWallpaperDir(0), WALLPAPER);
+            oldWallpaper.renameTo(newWallpaper);
+        }
+        if (oldInfo.exists()) {
+            File newInfo = new File(getWallpaperDir(0), WALLPAPER_INFO);
+            oldInfo.renameTo(newInfo);
+        }
+    }
+
+    private void loadSettingsLocked(int userId) {
+        if (DEBUG) Slog.v(TAG, "loadSettingsLocked");
+        
+        JournaledFile journal = makeJournaledFile(userId);
+        FileInputStream stream = null;
+        File file = journal.chooseForRead();
+        if (!file.exists()) {
+            // This should only happen one time, when upgrading from a legacy system
+            migrateFromOld();
+        }
+        WallpaperData wallpaper = mWallpaperMap.get(userId);
+        if (wallpaper == null) {
+            wallpaper = new WallpaperData(userId);
+            mWallpaperMap.put(userId, wallpaper);
+        }
+        boolean success = false;
+        try {
+            stream = new FileInputStream(file);
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, null);
+
+            int type;
+            do {
+                type = parser.next();
+                if (type == XmlPullParser.START_TAG) {
+                    String tag = parser.getName();
+                    if ("wp".equals(tag)) {
+                        wallpaper.width = Integer.parseInt(parser.getAttributeValue(null, "width"));
+                        wallpaper.height = Integer.parseInt(parser
+                                .getAttributeValue(null, "height"));
+                        wallpaper.name = parser.getAttributeValue(null, "name");
+                        String comp = parser.getAttributeValue(null, "component");
+                        wallpaper.nextWallpaperComponent = comp != null
+                                ? ComponentName.unflattenFromString(comp)
+                                : null;
+                        if (wallpaper.nextWallpaperComponent == null
+                                || "android".equals(wallpaper.nextWallpaperComponent
+                                        .getPackageName())) {
+                            wallpaper.nextWallpaperComponent = IMAGE_WALLPAPER;
+                        }
+                          
+                        if (DEBUG) {
+                            Slog.v(TAG, "mWidth:" + wallpaper.width);
+                            Slog.v(TAG, "mHeight:" + wallpaper.height);
+                            Slog.v(TAG, "mName:" + wallpaper.name);
+                            Slog.v(TAG, "mNextWallpaperComponent:"
+                                    + wallpaper.nextWallpaperComponent);
+                        }
+                    }
+                }
+            } while (type != XmlPullParser.END_DOCUMENT);
+            success = true;
+        } catch (FileNotFoundException e) {
+            Slog.w(TAG, "no current wallpaper -- first boot?");
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "failed parsing " + file + " " + e);
+        } catch (NumberFormatException e) {
+            Slog.w(TAG, "failed parsing " + file + " " + e);
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "failed parsing " + file + " " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "failed parsing " + file + " " + e);
+        } catch (IndexOutOfBoundsException e) {
+            Slog.w(TAG, "failed parsing " + file + " " + e);
+        }
+        try {
+            if (stream != null) {
+                stream.close();
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+
+        if (!success) {
+            wallpaper.width = -1;
+            wallpaper.height = -1;
+            wallpaper.name = "";
+        }
+
+        // We always want to have some reasonable width hint.
+        int baseSize = getMaximumSizeDimension();
+        if (wallpaper.width < baseSize) {
+            wallpaper.width = baseSize;
+        }
+        if (wallpaper.height < baseSize) {
+            wallpaper.height = baseSize;
+        }
+    }
+
+    private int getMaximumSizeDimension() {
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        Display d = wm.getDefaultDisplay();
+        return d.getMaximumSizeDimension();
+    }
+
+    // Called by SystemBackupAgent after files are restored to disk.
+    public void settingsRestored() {
+        // Verify caller is the system
+        if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+            throw new RuntimeException("settingsRestored() can only be called from the system process");
+        }
+        // TODO: If necessary, make it work for secondary users as well. This currently assumes
+        // restores only to the primary user
+        if (DEBUG) Slog.v(TAG, "settingsRestored");
+        WallpaperData wallpaper = null;
+        boolean success = false;
+        synchronized (mLock) {
+            loadSettingsLocked(0);
+            wallpaper = mWallpaperMap.get(0);
+            if (wallpaper.nextWallpaperComponent != null
+                    && !wallpaper.nextWallpaperComponent.equals(IMAGE_WALLPAPER)) {
+                if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
+                        wallpaper, null)) {
+                    // No such live wallpaper or other failure; fall back to the default
+                    // live wallpaper (since the profile being restored indicated that the
+                    // user had selected a live rather than static one).
+                    bindWallpaperComponentLocked(null, false, false, wallpaper, null);
+                }
+                success = true;
+            } else {
+                // If there's a wallpaper name, we use that.  If that can't be loaded, then we
+                // use the default.
+                if ("".equals(wallpaper.name)) {
+                    if (DEBUG) Slog.v(TAG, "settingsRestored: name is empty");
+                    success = true;
+                } else {
+                    if (DEBUG) Slog.v(TAG, "settingsRestored: attempting to restore named resource");
+                    success = restoreNamedResourceLocked(wallpaper);
+                }
+                if (DEBUG) Slog.v(TAG, "settingsRestored: success=" + success);
+                if (success) {
+                    bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false,
+                            wallpaper, null);
+                }
+            }
+        }
+
+        if (!success) {
+            Slog.e(TAG, "Failed to restore wallpaper: '" + wallpaper.name + "'");
+            wallpaper.name = "";
+            getWallpaperDir(0).delete();
+        }
+
+        synchronized (mLock) {
+            saveSettingsLocked(wallpaper);
+        }
+    }
+
+    boolean restoreNamedResourceLocked(WallpaperData wallpaper) {
+        if (wallpaper.name.length() > 4 && "res:".equals(wallpaper.name.substring(0, 4))) {
+            String resName = wallpaper.name.substring(4);
+
+            String pkg = null;
+            int colon = resName.indexOf(':');
+            if (colon > 0) {
+                pkg = resName.substring(0, colon);
+            }
+
+            String ident = null;
+            int slash = resName.lastIndexOf('/');
+            if (slash > 0) {
+                ident = resName.substring(slash+1);
+            }
+
+            String type = null;
+            if (colon > 0 && slash > 0 && (slash-colon) > 1) {
+                type = resName.substring(colon+1, slash);
+            }
+
+            if (pkg != null && ident != null && type != null) {
+                int resId = -1;
+                InputStream res = null;
+                FileOutputStream fos = null;
+                try {
+                    Context c = mContext.createPackageContext(pkg, Context.CONTEXT_RESTRICTED);
+                    Resources r = c.getResources();
+                    resId = r.getIdentifier(resName, null, null);
+                    if (resId == 0) {
+                        Slog.e(TAG, "couldn't resolve identifier pkg=" + pkg + " type=" + type
+                                + " ident=" + ident);
+                        return false;
+                    }
+
+                    res = r.openRawResource(resId);
+                    if (wallpaper.wallpaperFile.exists()) {
+                        wallpaper.wallpaperFile.delete();
+                    }
+                    fos = new FileOutputStream(wallpaper.wallpaperFile);
+
+                    byte[] buffer = new byte[32768];
+                    int amt;
+                    while ((amt=res.read(buffer)) > 0) {
+                        fos.write(buffer, 0, amt);
+                    }
+                    // mWallpaperObserver will notice the close and send the change broadcast
+
+                    Slog.v(TAG, "Restored wallpaper: " + resName);
+                    return true;
+                } catch (NameNotFoundException e) {
+                    Slog.e(TAG, "Package name " + pkg + " not found");
+                } catch (Resources.NotFoundException e) {
+                    Slog.e(TAG, "Resource not found: " + resId);
+                } catch (IOException e) {
+                    Slog.e(TAG, "IOException while restoring wallpaper ", e);
+                } finally {
+                    if (res != null) {
+                        try {
+                            res.close();
+                        } catch (IOException ex) {}
+                    }
+                    if (fos != null) {
+                        FileUtils.sync(fos);
+                        try {
+                            fos.close();
+                        } catch (IOException ex) {}
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    @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 wallpaper service from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        synchronized (mLock) {
+            pw.println("Current Wallpaper Service state:");
+            for (int i = 0; i < mWallpaperMap.size(); i++) {
+                WallpaperData wallpaper = mWallpaperMap.valueAt(i);
+                pw.println(" User " + wallpaper.userId + ":");
+                pw.print("  mWidth=");
+                pw.print(wallpaper.width);
+                pw.print(" mHeight=");
+                pw.println(wallpaper.height);
+                pw.print("  mName=");
+                pw.println(wallpaper.name);
+                pw.print("  mWallpaperComponent=");
+                pw.println(wallpaper.wallpaperComponent);
+                if (wallpaper.connection != null) {
+                    WallpaperConnection conn = wallpaper.connection;
+                    pw.print("  Wallpaper connection ");
+                    pw.print(conn);
+                    pw.println(":");
+                    if (conn.mInfo != null) {
+                        pw.print("    mInfo.component=");
+                        pw.println(conn.mInfo.getComponent());
+                    }
+                    pw.print("    mToken=");
+                    pw.println(conn.mToken);
+                    pw.print("    mService=");
+                    pw.println(conn.mService);
+                    pw.print("    mEngine=");
+                    pw.println(conn.mEngine);
+                    pw.print("    mLastDiedTime=");
+                    pw.println(wallpaper.lastDiedTime - SystemClock.uptimeMillis());
+                }
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wifi/README.txt b/services/core/java/com/android/server/wifi/README.txt
new file mode 100644
index 0000000..39e14751
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/README.txt
@@ -0,0 +1,19 @@
+WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down.
+
+WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService.
+
+WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off.
+
+WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it.
+
+WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results.
+
+WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down.
+
+
+Feature description:
+
+Scan-only mode with Wi-Fi turned off:
+ - Setup wizard opts user into allowing scanning for improved location. We show no further dialogs in setup wizard since the user has just opted into the feature. This is the reason WifiService listens to DEVICE_PROVISIONED setting.
+ - Once the user has his device provisioned, turning off Wi-Fi from settings or from a third party app will show up a dialog reminding the user that scan mode will be on even though Wi-Fi is being turned off. The user has the choice to turn this notification off.
+ - In the scan mode, the device continues to allow scanning from any app with Wi-Fi turned off. This is done by disabling all networks and allowing only scans to be passed.
diff --git a/services/core/java/com/android/server/wifi/WifiController.java b/services/core/java/com/android/server/wifi/WifiController.java
new file mode 100644
index 0000000..a3d514e
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/WifiController.java
@@ -0,0 +1,751 @@
+/*
+ * 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.wifi;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
+import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.wifi.WifiService.LockList;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+class WifiController extends StateMachine {
+    private static final String TAG = "WifiController";
+    private static final boolean DBG = false;
+    private Context mContext;
+    private boolean mScreenOff;
+    private boolean mDeviceIdle;
+    private int mPluggedType;
+    private int mStayAwakeConditions;
+    private long mIdleMillis;
+    private int mSleepPolicy;
+    private boolean mFirstUserSignOnSeen = false;
+
+    private AlarmManager mAlarmManager;
+    private PendingIntent mIdleIntent;
+    private static final int IDLE_REQUEST = 0;
+
+    /**
+     * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a
+     * Settings.Global value is not present. This timeout value is chosen as
+     * the approximate point at which the battery drain caused by Wi-Fi
+     * being enabled but not active exceeds the battery drain caused by
+     * re-establishing a connection to the mobile data network.
+     */
+    private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */
+
+    /**
+     * See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}.  This is the default value if a
+     * Settings.Global value is not present.  This is the minimum time after wifi is disabled
+     * we'll act on an enable.  Enable requests received before this delay will be deferred.
+     */
+    private static final long DEFAULT_REENABLE_DELAY_MS = 500;
+
+    // finding that delayed messages can sometimes be delivered earlier than expected
+    // probably rounding errors..  add a margin to prevent problems
+    private static final long DEFER_MARGIN_MS = 5;
+
+    NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", "");
+
+    private static final String ACTION_DEVICE_IDLE =
+            "com.android.server.WifiManager.action.DEVICE_IDLE";
+
+    /* References to values tracked in WifiService */
+    final WifiStateMachine mWifiStateMachine;
+    final WifiSettingsStore mSettingsStore;
+    final LockList mLocks;
+
+    /**
+     * Temporary for computing UIDS that are responsible for starting WIFI.
+     * Protected by mWifiStateTracker lock.
+     */
+    private final WorkSource mTmpWorkSource = new WorkSource();
+
+    private long mReEnableDelayMillis;
+
+    private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
+
+    static final int CMD_EMERGENCY_MODE_CHANGED     = BASE + 1;
+    static final int CMD_SCREEN_ON                  = BASE + 2;
+    static final int CMD_SCREEN_OFF                 = BASE + 3;
+    static final int CMD_BATTERY_CHANGED            = BASE + 4;
+    static final int CMD_DEVICE_IDLE                = BASE + 5;
+    static final int CMD_LOCKS_CHANGED              = BASE + 6;
+    static final int CMD_SCAN_ALWAYS_MODE_CHANGED   = BASE + 7;
+    static final int CMD_WIFI_TOGGLED               = BASE + 8;
+    static final int CMD_AIRPLANE_TOGGLED           = BASE + 9;
+    static final int CMD_SET_AP                     = BASE + 10;
+    static final int CMD_DEFERRED_TOGGLE            = BASE + 11;
+    static final int CMD_USER_PRESENT               = BASE + 12;
+
+    private DefaultState mDefaultState = new DefaultState();
+    private StaEnabledState mStaEnabledState = new StaEnabledState();
+    private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState();
+    private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState();
+    private ApEnabledState mApEnabledState = new ApEnabledState();
+    private DeviceActiveState mDeviceActiveState = new DeviceActiveState();
+    private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState();
+    private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState();
+    private FullLockHeldState mFullLockHeldState = new FullLockHeldState();
+    private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState();
+    private NoLockHeldState mNoLockHeldState = new NoLockHeldState();
+    private EcmState mEcmState = new EcmState();
+
+    WifiController(Context context, WifiService service, Looper looper) {
+        super(TAG, looper);
+        mContext = context;
+        mWifiStateMachine = service.mWifiStateMachine;
+        mSettingsStore = service.mSettingsStore;
+        mLocks = service.mLocks;
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null);
+        mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0);
+
+        addState(mDefaultState);
+            addState(mApStaDisabledState, mDefaultState);
+            addState(mStaEnabledState, mDefaultState);
+                addState(mDeviceActiveState, mStaEnabledState);
+                addState(mDeviceInactiveState, mStaEnabledState);
+                    addState(mScanOnlyLockHeldState, mDeviceInactiveState);
+                    addState(mFullLockHeldState, mDeviceInactiveState);
+                    addState(mFullHighPerfLockHeldState, mDeviceInactiveState);
+                    addState(mNoLockHeldState, mDeviceInactiveState);
+            addState(mStaDisabledWithScanState, mDefaultState);
+            addState(mApEnabledState, mDefaultState);
+            addState(mEcmState, mDefaultState);
+        if (mSettingsStore.isScanAlwaysAvailable()) {
+            setInitialState(mStaDisabledWithScanState);
+        } else {
+            setInitialState(mApStaDisabledState);
+        }
+        setLogRecSize(100);
+        setLogOnlyTransitions(false);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_DEVICE_IDLE);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ACTION_DEVICE_IDLE)) {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                        }
+                    }
+                },
+                new IntentFilter(filter));
+
+        initializeAndRegisterForSettingsChange(looper);
+    }
+
+    private void initializeAndRegisterForSettingsChange(Looper looper) {
+        Handler handler = new Handler(looper);
+        readStayAwakeConditions();
+        registerForStayAwakeModeChange(handler);
+        readWifiIdleTime();
+        registerForWifiIdleTimeChange(handler);
+        readWifiSleepPolicy();
+        registerForWifiSleepPolicyChange(handler);
+        readWifiReEnableDelay();
+    }
+
+    private void readStayAwakeConditions() {
+        mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
+    }
+
+    private void readWifiIdleTime() {
+        mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS);
+    }
+
+    private void readWifiSleepPolicy() {
+        mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SLEEP_POLICY,
+                Settings.Global.WIFI_SLEEP_POLICY_NEVER);
+    }
+
+    private void readWifiReEnableDelay() {
+        mReEnableDelayMillis = Settings.Global.getLong(mContext.getContentResolver(),
+                Settings.Global.WIFI_REENABLE_DELAY_MS, DEFAULT_REENABLE_DELAY_MS);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForStayAwakeModeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readStayAwakeConditions();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForWifiIdleTimeChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiIdleTime();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS),
+                false, contentObserver);
+    }
+
+    /**
+     * Observes changes to wifi sleep policy
+     */
+    private void registerForWifiSleepPolicyChange(Handler handler) {
+        ContentObserver contentObserver = new ContentObserver(handler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                readWifiSleepPolicy();
+            }
+        };
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY),
+                false, contentObserver);
+    }
+
+    /**
+     * Determines whether the Wi-Fi chipset should stay awake or be put to
+     * sleep. Looks at the setting for the sleep policy and the current
+     * conditions.
+     *
+     * @see #shouldDeviceStayAwake(int)
+     */
+    private boolean shouldWifiStayAwake(int pluggedType) {
+        if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) {
+            // Never sleep
+            return true;
+        } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) &&
+                (pluggedType != 0)) {
+            // Never sleep while plugged, and we're plugged
+            return true;
+        } else {
+            // Default
+            return shouldDeviceStayAwake(pluggedType);
+        }
+    }
+
+    /**
+     * Determine whether the bit value corresponding to {@code pluggedType} is set in
+     * the bit string mStayAwakeConditions. This determines whether the device should
+     * stay awake based on the current plugged type.
+     *
+     * @param pluggedType the type of plug (USB, AC, or none) for which the check is
+     * being made
+     * @return {@code true} if {@code pluggedType} indicates that the device is
+     * supposed to stay awake, {@code false} otherwise.
+     */
+    private boolean shouldDeviceStayAwake(int pluggedType) {
+        return (mStayAwakeConditions & pluggedType) != 0;
+    }
+
+    private void updateBatteryWorkSource() {
+        mTmpWorkSource.clear();
+        if (mDeviceIdle) {
+            mLocks.updateWorkSource(mTmpWorkSource);
+        }
+        mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource);
+    }
+
+    class DefaultState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_SCREEN_ON:
+                    mAlarmManager.cancel(mIdleIntent);
+                    mScreenOff = false;
+                    mDeviceIdle = false;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_SCREEN_OFF:
+                    mScreenOff = true;
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND the "stay on while plugged in" setting doesn't match the
+                    * current power conditions (i.e, not plugged in, plugged in to USB,
+                    * or plugged in to AC).
+                    */
+                    if (!shouldWifiStayAwake(mPluggedType)) {
+                        //Delayed shutdown if wifi is connected
+                        if (mNetworkInfo.getDetailedState() ==
+                                NetworkInfo.DetailedState.CONNECTED) {
+                            if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms");
+                            mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                                    System.currentTimeMillis() + mIdleMillis, mIdleIntent);
+                        } else {
+                            sendMessage(CMD_DEVICE_IDLE);
+                        }
+                    }
+                    break;
+                case CMD_DEVICE_IDLE:
+                    mDeviceIdle = true;
+                    updateBatteryWorkSource();
+                    break;
+                case CMD_BATTERY_CHANGED:
+                    /*
+                    * Set a timer to put Wi-Fi to sleep, but only if the screen is off
+                    * AND we are transitioning from a state in which the device was supposed
+                    * to stay awake to a state in which it is not supposed to stay awake.
+                    * If "stay awake" state is not changing, we do nothing, to avoid resetting
+                    * the already-set timer.
+                    */
+                    int pluggedType = msg.arg1;
+                    if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType);
+                    if (mScreenOff && shouldWifiStayAwake(mPluggedType) &&
+                            !shouldWifiStayAwake(pluggedType)) {
+                        long triggerTime = System.currentTimeMillis() + mIdleMillis;
+                        if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms");
+                        mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent);
+                    }
+
+                    mPluggedType = pluggedType;
+                    break;
+                case CMD_SET_AP:
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                case CMD_LOCKS_CHANGED:
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    break;
+                case CMD_USER_PRESENT:
+                    mFirstUserSignOnSeen = true;
+                    break;
+                case CMD_DEFERRED_TOGGLE:
+                    log("DEFERRED_TOGGLE ignored due to state change");
+                    break;
+                default:
+                    throw new RuntimeException("WifiController.handleMessage " + msg.what);
+            }
+            return HANDLED;
+        }
+
+    }
+
+    class ApStaDisabledState extends State {
+        private int mDeferredEnableSerialNumber = 0;
+        private boolean mHaveDeferredEnable = false;
+        private long mDisabledTimestamp;
+
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+            // Supplicant can't restart right away, so not the time we switched off
+            mDisabledTimestamp = SystemClock.elapsedRealtime();
+            mDeferredEnableSerialNumber++;
+            mHaveDeferredEnable = false;
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (doDeferEnable(msg)) {
+                            if (mHaveDeferredEnable) {
+                                //  have 2 toggles now, inc serial number an ignore both
+                                mDeferredEnableSerialNumber++;
+                            }
+                            mHaveDeferredEnable = !mHaveDeferredEnable;
+                            break;
+                        }
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mStaDisabledWithScanState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 1) {
+                        mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj,
+                                true);
+                        transitionTo(mApEnabledState);
+                    }
+                    break;
+                case CMD_DEFERRED_TOGGLE:
+                    if (msg.arg1 != mDeferredEnableSerialNumber) {
+                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
+                        break;
+                    }
+                    log("DEFERRED_TOGGLE handled");
+                    sendMessage((Message)(msg.obj));
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        private boolean doDeferEnable(Message msg) {
+            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+            if (delaySoFar >= mReEnableDelayMillis) {
+                return false;
+            }
+
+            log("WifiController msg " + msg + " deferred for " +
+                    (mReEnableDelayMillis - delaySoFar) + "ms");
+
+            // need to defer this action.
+            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+            deferredMsg.obj = Message.obtain(msg);
+            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+            return true;
+        }
+
+    }
+
+    class StaEnabledState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+        }
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        if (mSettingsStore.isScanAlwaysAvailable()) {
+                            transitionTo(mStaDisabledWithScanState);
+                        } else {
+                            transitionTo(mApStaDisabledState);
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    /* When wi-fi is turned off due to airplane,
+                    * disable entirely (including scan)
+                    */
+                    if (! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_EMERGENCY_MODE_CHANGED:
+                    if (msg.arg1 == 1) {
+                        transitionTo(mEcmState);
+                        break;
+                    }
+                default:
+                    return NOT_HANDLED;
+
+            }
+            return HANDLED;
+        }
+    }
+
+    class StaDisabledWithScanState extends State {
+        private int mDeferredEnableSerialNumber = 0;
+        private boolean mHaveDeferredEnable = false;
+        private long mDisabledTimestamp;
+
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(true);
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            // Supplicant can't restart right away, so not the time we switched off
+            mDisabledTimestamp = SystemClock.elapsedRealtime();
+            mDeferredEnableSerialNumber++;
+            mHaveDeferredEnable = false;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_WIFI_TOGGLED:
+                    if (mSettingsStore.isWifiToggleEnabled()) {
+                        if (doDeferEnable(msg)) {
+                            if (mHaveDeferredEnable) {
+                                // have 2 toggles now, inc serial number and ignore both
+                                mDeferredEnableSerialNumber++;
+                            }
+                            mHaveDeferredEnable = !mHaveDeferredEnable;
+                            break;
+                        }
+                        if (mDeviceIdle == false) {
+                            transitionTo(mDeviceActiveState);
+                        } else {
+                            checkLocksAndTransitionWhenDeviceIdle();
+                        }
+                    }
+                    break;
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn() &&
+                            ! mSettingsStore.isWifiToggleEnabled()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                case CMD_SCAN_ALWAYS_MODE_CHANGED:
+                    if (! mSettingsStore.isScanAlwaysAvailable()) {
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    // Before starting tethering, turn off supplicant for scan mode
+                    if (msg.arg1 == 1) {
+                        deferMessage(msg);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_DEFERRED_TOGGLE:
+                    if (msg.arg1 != mDeferredEnableSerialNumber) {
+                        log("DEFERRED_TOGGLE ignored due to serial mismatch");
+                        break;
+                    }
+                    logd("DEFERRED_TOGGLE handled");
+                    sendMessage((Message)(msg.obj));
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+
+        private boolean doDeferEnable(Message msg) {
+            long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;
+            if (delaySoFar >= mReEnableDelayMillis) {
+                return false;
+            }
+
+            log("WifiController msg " + msg + " deferred for " +
+                    (mReEnableDelayMillis - delaySoFar) + "ms");
+
+            // need to defer this action.
+            Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);
+            deferredMsg.obj = Message.obtain(msg);
+            deferredMsg.arg1 = ++mDeferredEnableSerialNumber;
+            sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);
+            return true;
+        }
+
+    }
+
+    class ApEnabledState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_AIRPLANE_TOGGLED:
+                    if (mSettingsStore.isAirplaneModeOn()) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                case CMD_SET_AP:
+                    if (msg.arg1 == 0) {
+                        mWifiStateMachine.setHostApRunning(null, false);
+                        transitionTo(mApStaDisabledState);
+                    }
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class EcmState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setSupplicantRunning(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) {
+                if (mSettingsStore.isWifiToggleEnabled()) {
+                    if (mDeviceIdle == false) {
+                        transitionTo(mDeviceActiveState);
+                    } else {
+                        checkLocksAndTransitionWhenDeviceIdle();
+                    }
+                } else if (mSettingsStore.isScanAlwaysAvailable()) {
+                    transitionTo(mStaDisabledWithScanState);
+                } else {
+                    transitionTo(mApStaDisabledState);
+                }
+                return HANDLED;
+            } else {
+                return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceActiveState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (msg.what == CMD_DEVICE_IDLE) {
+                checkLocksAndTransitionWhenDeviceIdle();
+                // We let default state handle the rest of work
+            } else if (msg.what == CMD_USER_PRESENT) {
+                // TLS networks can't connect until user unlocks keystore. KeyStore
+                // unlocks when the user punches PIN after the reboot. So use this
+                // trigger to get those networks connected.
+                if (mFirstUserSignOnSeen == false) {
+                    mWifiStateMachine.reloadTlsNetworksAndReconnect();
+                }
+                mFirstUserSignOnSeen = true;
+                return HANDLED;
+            }
+            return NOT_HANDLED;
+        }
+    }
+
+    /* Parent: StaEnabledState */
+    class DeviceInactiveState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case CMD_LOCKS_CHANGED:
+                    checkLocksAndTransitionWhenDeviceIdle();
+                    updateBatteryWorkSource();
+                    return HANDLED;
+                case CMD_SCREEN_ON:
+                    transitionTo(mDeviceActiveState);
+                    // More work in default state
+                    return NOT_HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */
+    class ScanOnlyLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE);
+            mWifiStateMachine.setDriverStart(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */
+    class FullLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(false);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */
+    class FullHighPerfLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);
+            mWifiStateMachine.setDriverStart(true);
+            mWifiStateMachine.setHighPerfModeEnabled(true);
+        }
+    }
+
+    /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */
+    class NoLockHeldState extends State {
+        @Override
+        public void enter() {
+            mWifiStateMachine.setDriverStart(false);
+        }
+    }
+
+    private void checkLocksAndTransitionWhenDeviceIdle() {
+        if (mLocks.hasLocks()) {
+            switch (mLocks.getStrongestLockMode()) {
+                case WIFI_MODE_FULL:
+                    transitionTo(mFullLockHeldState);
+                    break;
+                case WIFI_MODE_FULL_HIGH_PERF:
+                    transitionTo(mFullHighPerfLockHeldState);
+                    break;
+                case WIFI_MODE_SCAN_ONLY:
+                    transitionTo(mScanOnlyLockHeldState);
+                    break;
+                default:
+                    loge("Illegal lock " + mLocks.getStrongestLockMode());
+            }
+        } else {
+            if (mSettingsStore.isScanAlwaysAvailable()) {
+                transitionTo(mScanOnlyLockHeldState);
+            } else {
+                transitionTo(mNoLockHeldState);
+            }
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        super.dump(fd, pw, args);
+
+        pw.println("mScreenOff " + mScreenOff);
+        pw.println("mDeviceIdle " + mDeviceIdle);
+        pw.println("mPluggedType " + mPluggedType);
+        pw.println("mIdleMillis " + mIdleMillis);
+        pw.println("mSleepPolicy " + mSleepPolicy);
+    }
+}
diff --git a/services/core/java/com/android/server/wifi/WifiNotificationController.java b/services/core/java/com/android/server/wifi/WifiNotificationController.java
new file mode 100644
index 0000000..a9206e0
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/WifiNotificationController.java
@@ -0,0 +1,297 @@
+/*
+ * 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.wifi;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.TaskStackBuilder;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.NetworkInfo;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiStateMachine;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+
+/* Takes care of handling the "open wi-fi network available" notification @hide */
+final class WifiNotificationController {
+    /**
+     * The icon to show in the 'available networks' notification. This will also
+     * be the ID of the Notification given to the NotificationManager.
+     */
+    private static final int ICON_NETWORKS_AVAILABLE =
+            com.android.internal.R.drawable.stat_notify_wifi_in_range;
+    /**
+     * When a notification is shown, we wait this amount before possibly showing it again.
+     */
+    private final long NOTIFICATION_REPEAT_DELAY_MS;
+    /**
+     * Whether the user has set the setting to show the 'available networks' notification.
+     */
+    private boolean mNotificationEnabled;
+    /**
+     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
+     */
+    private NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+    /**
+     * The {@link System#currentTimeMillis()} must be at least this value for us
+     * to show the notification again.
+     */
+    private long mNotificationRepeatTime;
+    /**
+     * The Notification object given to the NotificationManager.
+     */
+    private Notification mNotification;
+    /**
+     * Whether the notification is being shown, as set by us. That is, if the
+     * user cancels the notification, we will not receive the callback so this
+     * will still be true. We only guarantee if this is false, then the
+     * notification is not showing.
+     */
+    private boolean mNotificationShown;
+    /**
+     * The number of continuous scans that must occur before consider the
+     * supplicant in a scanning state. This allows supplicant to associate with
+     * remembered networks that are in the scan results.
+     */
+    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+    /**
+     * The number of scans since the last network state change. When this
+     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
+     * supplicant to actually be scanning. When the network state changes to
+     * something other than scanning, we reset this to 0.
+     */
+    private int mNumScansSinceNetworkStateChange;
+
+    private final Context mContext;
+    private final WifiStateMachine mWifiStateMachine;
+    private NetworkInfo mNetworkInfo;
+    private volatile int mWifiState;
+
+    WifiNotificationController(Context context, WifiStateMachine wsm) {
+        mContext = context;
+        mWifiStateMachine = wsm;
+        mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                            mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                    WifiManager.WIFI_STATE_UNKNOWN);
+                            resetNotification();
+                        } else if (intent.getAction().equals(
+                                WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                            // reset & clear notification on a network connect & disconnect
+                            switch(mNetworkInfo.getDetailedState()) {
+                                case CONNECTED:
+                                case DISCONNECTED:
+                                case CAPTIVE_PORTAL_CHECK:
+                                    resetNotification();
+                                    break;
+                            }
+                        } else if (intent.getAction().equals(
+                                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
+                            checkAndSetNotification(mNetworkInfo,
+                                    mWifiStateMachine.syncGetScanResultsList());
+                        }
+                    }
+                }, filter);
+
+        // Setting is in seconds
+        NOTIFICATION_REPEAT_DELAY_MS = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000l;
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(new Handler());
+        mNotificationEnabledSettingObserver.register();
+    }
+
+    private synchronized void checkAndSetNotification(NetworkInfo networkInfo,
+            List<ScanResult> scanResults) {
+        // TODO: unregister broadcast so we do not have to check here
+        // If we shouldn't place a notification on available networks, then
+        // don't bother doing any of the following
+        if (!mNotificationEnabled) return;
+        if (networkInfo == null) return;
+        if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
+
+        NetworkInfo.State state = networkInfo.getState();
+        if ((state == NetworkInfo.State.DISCONNECTED)
+                || (state == NetworkInfo.State.UNKNOWN)) {
+            if (scanResults != null) {
+                int numOpenNetworks = 0;
+                for (int i = scanResults.size() - 1; i >= 0; i--) {
+                    ScanResult scanResult = scanResults.get(i);
+
+                    //A capability of [ESS] represents an open access point
+                    //that is available for an STA to connect
+                    if (scanResult.capabilities != null &&
+                            scanResult.capabilities.equals("[ESS]")) {
+                        numOpenNetworks++;
+                    }
+                }
+
+                if (numOpenNetworks > 0) {
+                    if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+                        /*
+                         * We've scanned continuously at least
+                         * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+                         * probably does not have a remembered network in range,
+                         * since otherwise supplicant would have tried to
+                         * associate and thus resetting this counter.
+                         */
+                        setNotificationVisible(true, numOpenNetworks, false, 0);
+                    }
+                    return;
+                }
+            }
+        }
+
+        // No open networks in range, remove the notification
+        setNotificationVisible(false, 0, false, 0);
+    }
+
+    /**
+     * Clears variables related to tracking whether a notification has been
+     * shown recently and clears the current notification.
+     */
+    private synchronized void resetNotification() {
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+        setNotificationVisible(false, 0, false, 0);
+    }
+
+    /**
+     * Display or don't display a notification that there are open Wi-Fi networks.
+     * @param visible {@code true} if notification should be visible, {@code false} otherwise
+     * @param numNetworks the number networks seen
+     * @param force {@code true} to force notification to be shown/not-shown,
+     * even if it is already shown/not-shown.
+     * @param delay time in milliseconds after which the notification should be made
+     * visible or invisible.
+     */
+    private void setNotificationVisible(boolean visible, int numNetworks, boolean force,
+            int delay) {
+
+        // Since we use auto cancel on the notification, when the
+        // mNetworksAvailableNotificationShown is true, the notification may
+        // have actually been canceled.  However, when it is false we know
+        // for sure that it is not being shown (it will not be shown any other
+        // place than here)
+
+        // If it should be hidden and it is already hidden, then noop
+        if (!visible && !mNotificationShown && !force) {
+            return;
+        }
+
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        Message message;
+        if (visible) {
+
+            // Not enough time has passed to show the notification again
+            if (System.currentTimeMillis() < mNotificationRepeatTime) {
+                return;
+            }
+
+            if (mNotification == null) {
+                // Cache the Notification object.
+                mNotification = new Notification();
+                mNotification.when = 0;
+                mNotification.icon = ICON_NETWORKS_AVAILABLE;
+                mNotification.flags = Notification.FLAG_AUTO_CANCEL;
+                mNotification.contentIntent = TaskStackBuilder.create(mContext)
+                        .addNextIntentWithParentStack(
+                                new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK))
+                        .getPendingIntent(0, 0, null, UserHandle.CURRENT);
+            }
+
+            CharSequence title = mContext.getResources().getQuantityText(
+                    com.android.internal.R.plurals.wifi_available, numNetworks);
+            CharSequence details = mContext.getResources().getQuantityText(
+                    com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+            mNotification.tickerText = title;
+            mNotification.setLatestEventInfo(mContext, title, details, mNotification.contentIntent);
+
+            mNotificationRepeatTime = System.currentTimeMillis() + NOTIFICATION_REPEAT_DELAY_MS;
+
+            notificationManager.notifyAsUser(null, ICON_NETWORKS_AVAILABLE, mNotification,
+                    UserHandle.ALL);
+        } else {
+            notificationManager.cancelAsUser(null, ICON_NETWORKS_AVAILABLE, UserHandle.ALL);
+        }
+
+        mNotificationShown = visible;
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mNotificationEnabled " + mNotificationEnabled);
+        pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
+        pw.println("mNotificationShown " + mNotificationShown);
+        pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
+    }
+
+    private class NotificationEnabledSettingObserver extends ContentObserver {
+        public NotificationEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void register() {
+            ContentResolver cr = mContext.getContentResolver();
+            cr.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
+            synchronized (WifiNotificationController.this) {
+                mNotificationEnabled = getValue();
+            }
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+
+            synchronized (WifiNotificationController.this) {
+                mNotificationEnabled = getValue();
+                resetNotification();
+            }
+        }
+
+        private boolean getValue() {
+            return Settings.Global.getInt(mContext.getContentResolver(),
+                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/wifi/WifiService.java b/services/core/java/com/android/server/wifi/WifiService.java
new file mode 100644
index 0000000..f2efde1
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/WifiService.java
@@ -0,0 +1,1599 @@
+/*
+ * Copyright (C) 2010 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.wifi;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.DhcpInfo;
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.net.wifi.IWifiManager;
+import android.net.wifi.ScanResult;
+import android.net.wifi.BatchedScanResult;
+import android.net.wifi.BatchedScanSettings;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConfiguration.ProxySettings;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiStateMachine;
+import android.net.wifi.WifiWatchdogStateMachine;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.Messenger;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.os.AsyncTask;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.FileNotFoundException;
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.android.internal.R;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.AsyncChannel;
+import com.android.server.am.BatteryStatsService;
+import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED;
+import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF;
+import static com.android.server.wifi.WifiController.CMD_SCREEN_ON;
+import static com.android.server.wifi.WifiController.CMD_SET_AP;
+import static com.android.server.wifi.WifiController.CMD_USER_PRESENT;
+import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
+/**
+ * WifiService handles remote WiFi operation requests by implementing
+ * the IWifiManager interface.
+ *
+ * @hide
+ */
+public final class WifiService extends IWifiManager.Stub {
+    private static final String TAG = "WifiService";
+    private static final boolean DBG = false;
+
+    final WifiStateMachine mWifiStateMachine;
+
+    private final Context mContext;
+
+    final LockList mLocks = new LockList();
+    // some wifi lock statistics
+    private int mFullHighPerfLocksAcquired;
+    private int mFullHighPerfLocksReleased;
+    private int mFullLocksAcquired;
+    private int mFullLocksReleased;
+    private int mScanLocksAcquired;
+    private int mScanLocksReleased;
+
+    private final List<Multicaster> mMulticasters =
+            new ArrayList<Multicaster>();
+    private int mMulticastEnabled;
+    private int mMulticastDisabled;
+
+    private final IBatteryStats mBatteryStats;
+    private final AppOpsManager mAppOps;
+
+    private String mInterfaceName;
+
+    /* Tracks the open wi-fi network notification */
+    private WifiNotificationController mNotificationController;
+    /* Polls traffic stats and notifies clients */
+    private WifiTrafficPoller mTrafficPoller;
+    /* Tracks the persisted states for wi-fi & airplane mode */
+    final WifiSettingsStore mSettingsStore;
+
+    final boolean mBatchedScanSupported;
+
+    /**
+     * Asynchronous channel to WifiStateMachine
+     */
+    private AsyncChannel mWifiStateMachineChannel;
+
+    /**
+     * Handles client connections
+     */
+    private class ClientHandler extends Handler {
+
+        ClientHandler(android.os.Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
+                        // We track the clients by the Messenger
+                        // since it is expected to be always available
+                        mTrafficPoller.addClient(msg.replyTo);
+                    } else {
+                        Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
+                    }
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
+                        if (DBG) Slog.d(TAG, "Send failed, client connection lost");
+                    } else {
+                        if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
+                    }
+                    mTrafficPoller.removeClient(msg.replyTo);
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+                    AsyncChannel ac = new AsyncChannel();
+                    ac.connect(mContext, this, msg.replyTo);
+                    break;
+                }
+                /* Client commands are forwarded to state machine */
+                case WifiManager.CONNECT_NETWORK:
+                case WifiManager.SAVE_NETWORK: {
+                    WifiConfiguration config = (WifiConfiguration) msg.obj;
+                    int networkId = msg.arg1;
+                    if (config != null && config.isValid()) {
+                        // This is restricted because there is no UI for the user to
+                        // monitor/control PAC.
+                        if (config.proxySettings != ProxySettings.PAC) {
+                            if (DBG) Slog.d(TAG, "Connect with config" + config);
+                            mWifiStateMachine.sendMessage(Message.obtain(msg));
+                        } else {
+                            Slog.e(TAG,  "ClientHandler.handleMessage cannot process msg with PAC");
+                            if (msg.what == WifiManager.CONNECT_NETWORK) {
+                                replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
+                            } else {
+                                replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
+                            }
+                        }
+                    } else if (config == null
+                            && networkId != WifiConfiguration.INVALID_NETWORK_ID) {
+                        if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
+                        mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    } else {
+                        Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
+                        if (msg.what == WifiManager.CONNECT_NETWORK) {
+                            replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED);
+                        } else {
+                            replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED);
+                        }
+                    }
+                    break;
+                }
+                case WifiManager.FORGET_NETWORK:
+                case WifiManager.START_WPS:
+                case WifiManager.CANCEL_WPS:
+                case WifiManager.DISABLE_NETWORK:
+                case WifiManager.RSSI_PKTCNT_FETCH: {
+                    mWifiStateMachine.sendMessage(Message.obtain(msg));
+                    break;
+                }
+                default: {
+                    Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
+                    break;
+                }
+            }
+        }
+
+        private void replyFailed(Message msg, int what) {
+            Message reply = msg.obtain();
+            reply.what = what;
+            reply.arg1 = WifiManager.INVALID_ARGS;
+            try {
+                msg.replyTo.send(reply);
+            } catch (RemoteException e) {
+                // There's not much we can do if reply can't be sent!
+            }
+        }
+    }
+    private ClientHandler mClientHandler;
+
+    /**
+     * Handles interaction with WifiStateMachine
+     */
+    private class WifiStateMachineHandler extends Handler {
+        private AsyncChannel mWsmChannel;
+
+        WifiStateMachineHandler(android.os.Looper looper) {
+            super(looper);
+            mWsmChannel = new AsyncChannel();
+            mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: {
+                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                        mWifiStateMachineChannel = mWsmChannel;
+                    } else {
+                        Slog.e(TAG, "WifiStateMachine connection failure, error=" + msg.arg1);
+                        mWifiStateMachineChannel = null;
+                    }
+                    break;
+                }
+                case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+                    Slog.e(TAG, "WifiStateMachine channel lost, msg.arg1 =" + msg.arg1);
+                    mWifiStateMachineChannel = null;
+                    //Re-establish connection to state machine
+                    mWsmChannel.connect(mContext, this, mWifiStateMachine.getHandler());
+                    break;
+                }
+                default: {
+                    Slog.d(TAG, "WifiStateMachineHandler.handleMessage ignoring msg=" + msg);
+                    break;
+                }
+            }
+        }
+    }
+    WifiStateMachineHandler mWifiStateMachineHandler;
+
+    private WifiWatchdogStateMachine mWifiWatchdogStateMachine;
+
+    public WifiService(Context context) {
+        mContext = context;
+
+        mInterfaceName =  SystemProperties.get("wifi.interface", "wlan0");
+
+        mWifiStateMachine = new WifiStateMachine(mContext, mInterfaceName);
+        mWifiStateMachine.enableRssiPolling(true);
+        mBatteryStats = BatteryStatsService.getService();
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+
+        mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine);
+        mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName);
+        mSettingsStore = new WifiSettingsStore(mContext);
+
+        HandlerThread wifiThread = new HandlerThread("WifiService");
+        wifiThread.start();
+        mClientHandler = new ClientHandler(wifiThread.getLooper());
+        mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
+        mWifiController = new WifiController(mContext, this, wifiThread.getLooper());
+        mWifiController.start();
+
+        mBatchedScanSupported = mContext.getResources().getBoolean(
+                R.bool.config_wifi_batched_scan_supported);
+
+        registerForScanModeChange();
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (mSettingsStore.handleAirplaneModeToggled()) {
+                            mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED);
+                        }
+                    }
+                },
+                new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+
+        // Adding optimizations of only receiving broadcasts when wifi is enabled
+        // can result in race conditions when apps toggle wifi in the background
+        // without active user involvement. Always receive broadcasts.
+        registerForBroadcasts();
+    }
+
+    private WifiController mWifiController;
+
+    /**
+     * Check if Wi-Fi needs to be enabled and start
+     * if needed
+     *
+     * This function is used only at boot time
+     */
+    public void checkAndStartWifi() {
+        /* Check if wi-fi needs to be enabled */
+        boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
+        Slog.i(TAG, "WifiService starting up with Wi-Fi " +
+                (wifiEnabled ? "enabled" : "disabled"));
+
+        // If we are already disabled (could be due to airplane mode), avoid changing persist
+        // state here
+        if (wifiEnabled) setWifiEnabled(wifiEnabled);
+
+        mWifiWatchdogStateMachine = WifiWatchdogStateMachine.
+               makeWifiWatchdogStateMachine(mContext);
+
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#pingSupplicant()}
+     * @return {@code true} if the operation succeeds, {@code false} otherwise
+     */
+    public boolean pingSupplicant() {
+        enforceAccessPermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncPingSupplicant(mWifiStateMachineChannel);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return false;
+        }
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#startScan()}
+     *
+     * <p>If workSource is null, all blame is given to the calling uid.
+     */
+    public void startScan(WorkSource workSource) {
+        enforceChangePermission();
+        if (workSource != null) {
+            enforceWorkSourcePermission();
+            // WifiManager currently doesn't use names, so need to clear names out of the
+            // supplied WorkSource to allow future WorkSource combining.
+            workSource.clearNames();
+        }
+        mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);
+    }
+
+    private class BatchedScanRequest extends DeathRecipient {
+        final BatchedScanSettings settings;
+        final int uid;
+        final int pid;
+        final WorkSource workSource;
+
+        BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
+            super(0, null, binder, null);
+            this.settings = settings;
+            this.uid = getCallingUid();
+            this.pid = getCallingPid();
+            workSource = ws;
+        }
+        public void binderDied() {
+            stopBatchedScan(settings, uid, pid);
+        }
+        public String toString() {
+            return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}";
+        }
+
+        public boolean isSameApp(int uid, int pid) {
+            return (this.uid == uid && this.pid == pid);
+        }
+    }
+
+    private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>();
+
+    public boolean isBatchedScanSupported() {
+        return mBatchedScanSupported;
+    }
+
+    public void pollBatchedScan() {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return;
+        mWifiStateMachine.requestBatchedScanPoll();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#requestBatchedScan()}
+     */
+    public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
+            WorkSource workSource) {
+        enforceChangePermission();
+        if (workSource != null) {
+            enforceWorkSourcePermission();
+            // WifiManager currently doesn't use names, so need to clear names out of the
+            // supplied WorkSource to allow future WorkSource combining.
+            workSource.clearNames();
+        }
+        if (mBatchedScanSupported == false) return false;
+        requested = new BatchedScanSettings(requested);
+        if (requested.isInvalid()) return false;
+        BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
+        synchronized(mBatchedScanners) {
+            mBatchedScanners.add(r);
+            resolveBatchedScannersLocked();
+        }
+        return true;
+    }
+
+    public List<BatchedScanResult> getBatchedScanResults(String callingPackage) {
+        enforceAccessPermission();
+        if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>();
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<BatchedScanResult>();
+            }
+            int currentUser = ActivityManager.getCurrentUser();
+            if (userId != currentUser) {
+                return new ArrayList<BatchedScanResult>();
+            } else {
+                return mWifiStateMachine.syncGetBatchedScanResultsList();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+
+    public void stopBatchedScan(BatchedScanSettings settings) {
+        enforceChangePermission();
+        if (mBatchedScanSupported == false) return;
+        stopBatchedScan(settings, getCallingUid(), getCallingPid());
+    }
+
+    private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) {
+        ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>();
+        synchronized(mBatchedScanners) {
+            for (BatchedScanRequest r : mBatchedScanners) {
+                if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) {
+                    found.add(r);
+                    if (settings != null) break;
+                }
+            }
+            for (BatchedScanRequest r : found) {
+                mBatchedScanners.remove(r);
+            }
+            if (found.size() != 0) {
+                resolveBatchedScannersLocked();
+            }
+        }
+    }
+
+    private void resolveBatchedScannersLocked() {
+        BatchedScanSettings setting = new BatchedScanSettings();
+        WorkSource responsibleWorkSource = null;
+        int responsibleUid = 0;
+        double responsibleCsph = 0; // Channel Scans Per Hour
+
+        if (mBatchedScanners.size() == 0) {
+            mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
+            return;
+        }
+        for (BatchedScanRequest r : mBatchedScanners) {
+            BatchedScanSettings s = r.settings;
+
+            // evaluate responsibility
+            int currentChannelCount;
+            int currentScanInterval;
+            double currentCsph;
+
+            if (s.channelSet == null || s.channelSet.isEmpty()) {
+                // all channels - 11 B and 9 A channels roughly.
+                currentChannelCount = 9 + 11;
+            } else {
+                currentChannelCount = s.channelSet.size();
+                // these are rough est - no real need to correct for reg-domain;
+                if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
+                if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
+
+            }
+            if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+                currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+            } else {
+                currentScanInterval = s.scanIntervalSec;
+            }
+            currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
+
+            if (currentCsph > responsibleCsph) {
+                responsibleUid = r.uid;
+                responsibleWorkSource = r.workSource;
+                responsibleCsph = currentCsph;
+            }
+
+            if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
+                    s.maxScansPerBatch < setting.maxScansPerBatch) {
+                setting.maxScansPerBatch = s.maxScansPerBatch;
+            }
+            if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
+                    (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
+                    s.maxApPerScan > setting.maxApPerScan)) {
+                setting.maxApPerScan = s.maxApPerScan;
+            }
+            if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
+                    s.scanIntervalSec < setting.scanIntervalSec) {
+                setting.scanIntervalSec = s.scanIntervalSec;
+            }
+            if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
+                    (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
+                    s.maxApForDistance > setting.maxApForDistance)) {
+                setting.maxApForDistance = s.maxApForDistance;
+            }
+            if (s.channelSet != null && s.channelSet.size() != 0) {
+                if (setting.channelSet == null || setting.channelSet.size() != 0) {
+                    if (setting.channelSet == null) setting.channelSet = new ArrayList<String>();
+                    for (String i : s.channelSet) {
+                        if (setting.channelSet.contains(i) == false) setting.channelSet.add(i);
+                    }
+                } // else, ignore the constraint - we already use all channels
+            } else {
+                if (setting.channelSet == null || setting.channelSet.size() != 0) {
+                    setting.channelSet = new ArrayList<String>();
+                }
+            }
+        }
+
+        setting.constrain();
+        mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
+                responsibleWorkSource);
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
+                                                "WifiService");
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
+                                                "WifiService");
+
+    }
+
+    private void enforceWorkSourcePermission() {
+        mContext.enforceCallingPermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                                                "WifiService");
+
+    }
+
+    private void enforceMulticastChangePermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE,
+                "WifiService");
+    }
+
+    private void enforceConnectivityInternalPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                "ConnectivityService");
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public synchronized boolean setWifiEnabled(boolean enable) {
+        enforceChangePermission();
+        Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+        if (DBG) {
+            Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
+        }
+
+        /*
+        * Caller might not have WRITE_SECURE_SETTINGS,
+        * only CHANGE_WIFI_STATE is enforced
+        */
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (! mSettingsStore.handleWifiToggled(enable)) {
+                // Nothing to do if wifi cannot be toggled
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        mWifiController.sendMessage(CMD_WIFI_TOGGLED);
+        return true;
+    }
+
+    /**
+     * see {@link WifiManager#getWifiState()}
+     * @return One of {@link WifiManager#WIFI_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_STATE_UNKNOWN}
+     */
+    public int getWifiEnabledState() {
+        enforceAccessPermission();
+        return mWifiStateMachine.syncGetWifiState();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#setWifiApEnabled(WifiConfiguration, boolean)}
+     * @param wifiConfig SSID, security and channel details as
+     *        part of WifiConfiguration
+     * @param enabled true to enable and false to disable
+     */
+    public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) {
+        enforceChangePermission();
+        // null wifiConfig is a meaningful input for CMD_SET_AP
+        if (wifiConfig == null || wifiConfig.isValid()) {
+            mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget();
+        } else {
+            Slog.e(TAG, "Invalid WifiConfiguration");
+        }
+    }
+
+    /**
+     * see {@link WifiManager#getWifiApState()}
+     * @return One of {@link WifiManager#WIFI_AP_STATE_DISABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_DISABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLED},
+     *         {@link WifiManager#WIFI_AP_STATE_ENABLING},
+     *         {@link WifiManager#WIFI_AP_STATE_FAILED}
+     */
+    public int getWifiApEnabledState() {
+        enforceAccessPermission();
+        return mWifiStateMachine.syncGetWifiApState();
+    }
+
+    /**
+     * see {@link WifiManager#getWifiApConfiguration()}
+     * @return soft access point configuration
+     */
+    public WifiConfiguration getWifiApConfiguration() {
+        enforceAccessPermission();
+        return mWifiStateMachine.syncGetWifiApConfiguration();
+    }
+
+    /**
+     * see {@link WifiManager#setWifiApConfiguration(WifiConfiguration)}
+     * @param wifiConfig WifiConfiguration details for soft access point
+     */
+    public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+        enforceChangePermission();
+        if (wifiConfig == null)
+            return;
+        if (wifiConfig.isValid()) {
+            mWifiStateMachine.setWifiApConfiguration(wifiConfig);
+        } else {
+            Slog.e(TAG, "Invalid WifiConfiguration");
+        }
+    }
+
+    /**
+     * @param enable {@code true} to enable, {@code false} to disable.
+     * @return {@code true} if the enable/disable operation was
+     *         started or is already in the queue.
+     */
+    public boolean isScanAlwaysAvailable() {
+        enforceAccessPermission();
+        return mSettingsStore.isScanAlwaysAvailable();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#disconnect()}
+     */
+    public void disconnect() {
+        enforceChangePermission();
+        mWifiStateMachine.disconnectCommand();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#reconnect()}
+     */
+    public void reconnect() {
+        enforceChangePermission();
+        mWifiStateMachine.reconnectCommand();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#reassociate()}
+     */
+    public void reassociate() {
+        enforceChangePermission();
+        mWifiStateMachine.reassociateCommand();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#getConfiguredNetworks()}
+     * @return the list of configured networks
+     */
+    public List<WifiConfiguration> getConfiguredNetworks() {
+        enforceAccessPermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncGetConfiguredNetworks(mWifiStateMachineChannel);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return null;
+        }
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#addOrUpdateNetwork(WifiConfiguration)}
+     * @return the supplicant-assigned identifier for the new or updated
+     * network if the operation succeeds, or {@code -1} if it fails
+     */
+    public int addOrUpdateNetwork(WifiConfiguration config) {
+        enforceChangePermission();
+        if (config.proxySettings == ProxySettings.PAC) {
+            enforceConnectivityInternalPermission();
+        }
+        if (config.isValid()) {
+            if (mWifiStateMachineChannel != null) {
+                return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config);
+            } else {
+                Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+                return -1;
+            }
+        } else {
+            Slog.e(TAG, "bad network configuration");
+            return -1;
+        }
+    }
+
+     /**
+     * See {@link android.net.wifi.WifiManager#removeNetwork(int)}
+     * @param netId the integer that identifies the network configuration
+     * to the supplicant
+     * @return {@code true} if the operation succeeded
+     */
+    public boolean removeNetwork(int netId) {
+        enforceChangePermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncRemoveNetwork(mWifiStateMachineChannel, netId);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return false;
+        }
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#enableNetwork(int, boolean)}
+     * @param netId the integer that identifies the network configuration
+     * to the supplicant
+     * @param disableOthers if true, disable all other networks.
+     * @return {@code true} if the operation succeeded
+     */
+    public boolean enableNetwork(int netId, boolean disableOthers) {
+        enforceChangePermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncEnableNetwork(mWifiStateMachineChannel, netId,
+                    disableOthers);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return false;
+        }
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#disableNetwork(int)}
+     * @param netId the integer that identifies the network configuration
+     * to the supplicant
+     * @return {@code true} if the operation succeeded
+     */
+    public boolean disableNetwork(int netId) {
+        enforceChangePermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncDisableNetwork(mWifiStateMachineChannel, netId);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return false;
+        }
+    }
+
+    /**
+     * See {@link android.net.wifi.WifiManager#getConnectionInfo()}
+     * @return the Wi-Fi information, contained in {@link WifiInfo}.
+     */
+    public WifiInfo getConnectionInfo() {
+        enforceAccessPermission();
+        /*
+         * Make sure we have the latest information, by sending
+         * a status request to the supplicant.
+         */
+        return mWifiStateMachine.syncRequestConnectionInfo();
+    }
+
+    /**
+     * Return the results of the most recent access point scan, in the form of
+     * a list of {@link ScanResult} objects.
+     * @return the list of results
+     */
+    public List<ScanResult> getScanResults(String callingPackage) {
+        enforceAccessPermission();
+        int userId = UserHandle.getCallingUserId();
+        int uid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage)
+                    != AppOpsManager.MODE_ALLOWED) {
+                return new ArrayList<ScanResult>();
+            }
+            int currentUser = ActivityManager.getCurrentUser();
+            if (userId != currentUser) {
+                return new ArrayList<ScanResult>();
+            } else {
+                return mWifiStateMachine.syncGetScanResultsList();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Tell the supplicant to persist the current list of configured networks.
+     * @return {@code true} if the operation succeeded
+     *
+     * TODO: deprecate this
+     */
+    public boolean saveConfiguration() {
+        boolean result = true;
+        enforceChangePermission();
+        if (mWifiStateMachineChannel != null) {
+            return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
+        } else {
+            Slog.e(TAG, "mWifiStateMachineChannel is not initialized");
+            return false;
+        }
+    }
+
+    /**
+     * Set the country code
+     * @param countryCode ISO 3166 country code.
+     * @param persist {@code true} if the setting should be remembered.
+     *
+     * The persist behavior exists so that wifi can fall back to the last
+     * persisted country code on a restart, when the locale information is
+     * not available from telephony.
+     */
+    public void setCountryCode(String countryCode, boolean persist) {
+        Slog.i(TAG, "WifiService trying to set country code to " + countryCode +
+                " with persist set to " + persist);
+        enforceChangePermission();
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mWifiStateMachine.setCountryCode(countryCode, persist);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    /**
+     * Set the operational frequency band
+     * @param band One of
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_AUTO},
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_5GHZ},
+     *     {@link WifiManager#WIFI_FREQUENCY_BAND_2GHZ},
+     * @param persist {@code true} if the setting should be remembered.
+     *
+     */
+    public void setFrequencyBand(int band, boolean persist) {
+        enforceChangePermission();
+        if (!isDualBandSupported()) return;
+        Slog.i(TAG, "WifiService trying to set frequency band to " + band +
+                " with persist set to " + persist);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mWifiStateMachine.setFrequencyBand(band, persist);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+
+    /**
+     * Get the operational frequency band
+     */
+    public int getFrequencyBand() {
+        enforceAccessPermission();
+        return mWifiStateMachine.getFrequencyBand();
+    }
+
+    public boolean isDualBandSupported() {
+        //TODO: Should move towards adding a driver API that checks at runtime
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wifi_dual_band_support);
+    }
+
+    /**
+     * Return the DHCP-assigned addresses from the last successful DHCP request,
+     * if any.
+     * @return the DHCP information
+     * @deprecated
+     */
+    public DhcpInfo getDhcpInfo() {
+        enforceAccessPermission();
+        DhcpResults dhcpResults = mWifiStateMachine.syncGetDhcpResults();
+        if (dhcpResults.linkProperties == null) return null;
+
+        DhcpInfo info = new DhcpInfo();
+        for (LinkAddress la : dhcpResults.linkProperties.getLinkAddresses()) {
+            InetAddress addr = la.getAddress();
+            if (addr instanceof Inet4Address) {
+                info.ipAddress = NetworkUtils.inetAddressToInt((Inet4Address)addr);
+                break;
+            }
+        }
+        for (RouteInfo r : dhcpResults.linkProperties.getRoutes()) {
+            if (r.isDefaultRoute()) {
+                InetAddress gateway = r.getGateway();
+                if (gateway instanceof Inet4Address) {
+                    info.gateway = NetworkUtils.inetAddressToInt((Inet4Address)gateway);
+                }
+            } else if (r.hasGateway() == false) {
+                LinkAddress dest = r.getDestination();
+                if (dest.getAddress() instanceof Inet4Address) {
+                    info.netmask = NetworkUtils.prefixLengthToNetmaskInt(
+                            dest.getNetworkPrefixLength());
+                }
+            }
+        }
+        int dnsFound = 0;
+        for (InetAddress dns : dhcpResults.linkProperties.getDnses()) {
+            if (dns instanceof Inet4Address) {
+                if (dnsFound == 0) {
+                    info.dns1 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+                } else {
+                    info.dns2 = NetworkUtils.inetAddressToInt((Inet4Address)dns);
+                }
+                if (++dnsFound > 1) break;
+            }
+        }
+        InetAddress serverAddress = dhcpResults.serverAddress;
+        if (serverAddress instanceof Inet4Address) {
+            info.serverAddress = NetworkUtils.inetAddressToInt((Inet4Address)serverAddress);
+        }
+        info.leaseDuration = dhcpResults.leaseDuration;
+
+        return info;
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#startWifi}
+     *
+     */
+    public void startWifi() {
+        enforceConnectivityInternalPermission();
+        /* TODO: may be add permissions for access only to connectivity service
+         * TODO: if a start issued, keep wifi alive until a stop issued irrespective
+         * of WifiLock & device idle status unless wifi enabled status is toggled
+         */
+
+        mWifiStateMachine.setDriverStart(true);
+        mWifiStateMachine.reconnectCommand();
+    }
+
+    public void captivePortalCheckComplete() {
+        enforceConnectivityInternalPermission();
+        mWifiStateMachine.captivePortalCheckComplete();
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#stopWifi}
+     *
+     */
+    public void stopWifi() {
+        enforceConnectivityInternalPermission();
+        /*
+         * TODO: if a stop is issued, wifi is brought up only by startWifi
+         * unless wifi enabled status is toggled
+         */
+        mWifiStateMachine.setDriverStart(false);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#addToBlacklist}
+     *
+     */
+    public void addToBlacklist(String bssid) {
+        enforceChangePermission();
+
+        mWifiStateMachine.addToBlacklist(bssid);
+    }
+
+    /**
+     * see {@link android.net.wifi.WifiManager#clearBlacklist}
+     *
+     */
+    public void clearBlacklist() {
+        enforceChangePermission();
+
+        mWifiStateMachine.clearBlacklist();
+    }
+
+    /**
+     * enable TDLS for the local NIC to remote NIC
+     * The APPs don't know the remote MAC address to identify NIC though,
+     * so we need to do additional work to find it from remote IP address
+     */
+
+    class TdlsTaskParams {
+        public String remoteIpAddress;
+        public boolean enable;
+    }
+
+    class TdlsTask extends AsyncTask<TdlsTaskParams, Integer, Integer> {
+        @Override
+        protected Integer doInBackground(TdlsTaskParams... params) {
+
+            // Retrieve parameters for the call
+            TdlsTaskParams param = params[0];
+            String remoteIpAddress = param.remoteIpAddress.trim();
+            boolean enable = param.enable;
+
+            // Get MAC address of Remote IP
+            String macAddress = null;
+
+            BufferedReader reader = null;
+
+            try {
+                reader = new BufferedReader(new FileReader("/proc/net/arp"));
+
+                // Skip over the line bearing colum titles
+                String line = reader.readLine();
+
+                while ((line = reader.readLine()) != null) {
+                    String[] tokens = line.split("[ ]+");
+                    if (tokens.length < 6) {
+                        continue;
+                    }
+
+                    // ARP column format is
+                    // Address HWType HWAddress Flags Mask IFace
+                    String ip = tokens[0];
+                    String mac = tokens[3];
+
+                    if (remoteIpAddress.equals(ip)) {
+                        macAddress = mac;
+                        break;
+                    }
+                }
+
+                if (macAddress == null) {
+                    Slog.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " +
+                            "/proc/net/arp");
+                } else {
+                    enableTdlsWithMacAddress(macAddress, enable);
+                }
+
+            } catch (FileNotFoundException e) {
+                Slog.e(TAG, "Could not open /proc/net/arp to lookup mac address");
+            } catch (IOException e) {
+                Slog.e(TAG, "Could not read /proc/net/arp to lookup mac address");
+            } finally {
+                try {
+                    if (reader != null) {
+                        reader.close();
+                    }
+                }
+                catch (IOException e) {
+                    // Do nothing
+                }
+            }
+
+            return 0;
+        }
+    }
+
+    public void enableTdls(String remoteAddress, boolean enable) {
+        TdlsTaskParams params = new TdlsTaskParams();
+        params.remoteIpAddress = remoteAddress;
+        params.enable = enable;
+        new TdlsTask().execute(params);
+    }
+
+
+    public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) {
+        mWifiStateMachine.enableTdls(remoteMacAddress, enable);
+    }
+
+    /**
+     * Get a reference to handler. This is used by a client to establish
+     * an AsyncChannel communication with WifiService
+     */
+    public Messenger getWifiServiceMessenger() {
+        enforceAccessPermission();
+        enforceChangePermission();
+        return new Messenger(mClientHandler);
+    }
+
+    /** Get a reference to WifiStateMachine handler for AsyncChannel communication */
+    public Messenger getWifiStateMachineMessenger() {
+        enforceAccessPermission();
+        enforceChangePermission();
+        return mWifiStateMachine.getMessenger();
+    }
+
+    /**
+     * Get the IP and proxy configuration file
+     */
+    public String getConfigFile() {
+        enforceAccessPermission();
+        return mWifiStateMachine.getConfigFile();
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                mWifiController.sendMessage(CMD_SCREEN_ON);
+            } else if (action.equals(Intent.ACTION_USER_PRESENT)) {
+                mWifiController.sendMessage(CMD_USER_PRESENT);
+            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                mWifiController.sendMessage(CMD_SCREEN_OFF);
+            } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+                int pluggedType = intent.getIntExtra("plugged", 0);
+                mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null);
+            } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) {
+                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                        BluetoothAdapter.STATE_DISCONNECTED);
+                mWifiStateMachine.sendBluetoothAdapterStateChange(state);
+            } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+                boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false);
+                mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0);
+            }
+        }
+    };
+
+    /**
+     * Observes settings changes to scan always mode.
+     */
+    private void registerForScanModeChange() {
+        ContentObserver contentObserver = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mSettingsStore.handleWifiScanAlwaysAvailableToggled();
+                mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED);
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE),
+                false, contentObserver);
+    }
+
+    private void registerForBroadcasts() {
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_USER_PRESENT);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
+        intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        mContext.registerReceiver(mReceiver, intentFilter);
+    }
+
+    @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 WifiService from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        pw.println("Wi-Fi is " + mWifiStateMachine.syncGetWifiStateByName());
+        pw.println("Stay-awake conditions: " +
+                Settings.Global.getInt(mContext.getContentResolver(),
+                                       Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0));
+        pw.println("mMulticastEnabled " + mMulticastEnabled);
+        pw.println("mMulticastDisabled " + mMulticastDisabled);
+        mWifiController.dump(fd, pw, args);
+        mSettingsStore.dump(fd, pw, args);
+        mNotificationController.dump(fd, pw, args);
+        mTrafficPoller.dump(fd, pw, args);
+
+        pw.println("Latest scan results:");
+        List<ScanResult> scanResults = mWifiStateMachine.syncGetScanResultsList();
+        if (scanResults != null && scanResults.size() != 0) {
+            pw.println("  BSSID              Frequency   RSSI  Flags             SSID");
+            for (ScanResult r : scanResults) {
+                pw.printf("  %17s  %9d  %5d  %-16s  %s%n",
+                                         r.BSSID,
+                                         r.frequency,
+                                         r.level,
+                                         r.capabilities,
+                                         r.SSID == null ? "" : r.SSID);
+            }
+        }
+        pw.println();
+        pw.println("Locks acquired: " + mFullLocksAcquired + " full, " +
+                mFullHighPerfLocksAcquired + " full high perf, " +
+                mScanLocksAcquired + " scan");
+        pw.println("Locks released: " + mFullLocksReleased + " full, " +
+                mFullHighPerfLocksReleased + " full high perf, " +
+                mScanLocksReleased + " scan");
+        pw.println();
+        pw.println("Locks held:");
+        mLocks.dump(pw);
+
+        mWifiWatchdogStateMachine.dump(fd, pw, args);
+        pw.println();
+        mWifiStateMachine.dump(fd, pw, args);
+        pw.println();
+    }
+
+    private class WifiLock extends DeathRecipient {
+        WifiLock(int lockMode, String tag, IBinder binder, WorkSource ws) {
+            super(lockMode, tag, binder, ws);
+        }
+
+        public void binderDied() {
+            synchronized (mLocks) {
+                releaseWifiLockLocked(mBinder);
+            }
+        }
+
+        public String toString() {
+            return "WifiLock{" + mTag + " type=" + mMode + " binder=" + mBinder + "}";
+        }
+    }
+
+    class LockList {
+        private List<WifiLock> mList;
+
+        private LockList() {
+            mList = new ArrayList<WifiLock>();
+        }
+
+        synchronized boolean hasLocks() {
+            return !mList.isEmpty();
+        }
+
+        synchronized int getStrongestLockMode() {
+            if (mList.isEmpty()) {
+                return WifiManager.WIFI_MODE_FULL;
+            }
+
+            if (mFullHighPerfLocksAcquired > mFullHighPerfLocksReleased) {
+                return WifiManager.WIFI_MODE_FULL_HIGH_PERF;
+            }
+
+            if (mFullLocksAcquired > mFullLocksReleased) {
+                return WifiManager.WIFI_MODE_FULL;
+            }
+
+            return WifiManager.WIFI_MODE_SCAN_ONLY;
+        }
+
+        synchronized void updateWorkSource(WorkSource ws) {
+            for (int i = 0; i < mLocks.mList.size(); i++) {
+                ws.add(mLocks.mList.get(i).mWorkSource);
+            }
+        }
+
+        private void addLock(WifiLock lock) {
+            if (findLockByBinder(lock.mBinder) < 0) {
+                mList.add(lock);
+            }
+        }
+
+        private WifiLock removeLock(IBinder binder) {
+            int index = findLockByBinder(binder);
+            if (index >= 0) {
+                WifiLock ret = mList.remove(index);
+                ret.unlinkDeathRecipient();
+                return ret;
+            } else {
+                return null;
+            }
+        }
+
+        private int findLockByBinder(IBinder binder) {
+            int size = mList.size();
+            for (int i = size - 1; i >= 0; i--) {
+                if (mList.get(i).mBinder == binder)
+                    return i;
+            }
+            return -1;
+        }
+
+        private void dump(PrintWriter pw) {
+            for (WifiLock l : mList) {
+                pw.print("    ");
+                pw.println(l);
+            }
+        }
+    }
+
+    void enforceWakeSourcePermission(int uid, int pid) {
+        if (uid == android.os.Process.myUid()) {
+            return;
+        }
+        mContext.enforcePermission(android.Manifest.permission.UPDATE_DEVICE_STATS,
+                pid, uid, null);
+    }
+
+    public boolean acquireWifiLock(IBinder binder, int lockMode, String tag, WorkSource ws) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        if (lockMode != WifiManager.WIFI_MODE_FULL &&
+                lockMode != WifiManager.WIFI_MODE_SCAN_ONLY &&
+                lockMode != WifiManager.WIFI_MODE_FULL_HIGH_PERF) {
+            Slog.e(TAG, "Illegal argument, lockMode= " + lockMode);
+            if (DBG) throw new IllegalArgumentException("lockMode=" + lockMode);
+            return false;
+        }
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(Binder.getCallingUid(), Binder.getCallingPid());
+        }
+        if (ws == null) {
+            ws = new WorkSource(Binder.getCallingUid());
+        }
+        WifiLock wifiLock = new WifiLock(lockMode, tag, binder, ws);
+        synchronized (mLocks) {
+            return acquireWifiLockLocked(wifiLock);
+        }
+    }
+
+    private void noteAcquireWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteFullWifiLockAcquiredFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
+    private void noteReleaseWifiLock(WifiLock wifiLock) throws RemoteException {
+        switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                mBatteryStats.noteFullWifiLockReleasedFromSource(wifiLock.mWorkSource);
+                break;
+        }
+    }
+
+    private boolean acquireWifiLockLocked(WifiLock wifiLock) {
+        if (DBG) Slog.d(TAG, "acquireWifiLockLocked: " + wifiLock);
+
+        mLocks.addLock(wifiLock);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            noteAcquireWifiLock(wifiLock);
+            switch(wifiLock.mMode) {
+            case WifiManager.WIFI_MODE_FULL:
+                ++mFullLocksAcquired;
+                break;
+            case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                ++mFullHighPerfLocksAcquired;
+                break;
+
+            case WifiManager.WIFI_MODE_SCAN_ONLY:
+                ++mScanLocksAcquired;
+                break;
+            }
+            mWifiController.sendMessage(CMD_LOCKS_CHANGED);
+            return true;
+        } catch (RemoteException e) {
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    public void updateWifiLockWorkSource(IBinder lock, WorkSource ws) {
+        int uid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
+        if (ws != null && ws.size() == 0) {
+            ws = null;
+        }
+        if (ws != null) {
+            enforceWakeSourcePermission(uid, pid);
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLocks) {
+                int index = mLocks.findLockByBinder(lock);
+                if (index < 0) {
+                    throw new IllegalArgumentException("Wifi lock not active");
+                }
+                WifiLock wl = mLocks.mList.get(index);
+                noteReleaseWifiLock(wl);
+                wl.mWorkSource = ws != null ? new WorkSource(ws) : new WorkSource(uid);
+                noteAcquireWifiLock(wl);
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    public boolean releaseWifiLock(IBinder lock) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
+        synchronized (mLocks) {
+            return releaseWifiLockLocked(lock);
+        }
+    }
+
+    private boolean releaseWifiLockLocked(IBinder lock) {
+        boolean hadLock;
+
+        WifiLock wifiLock = mLocks.removeLock(lock);
+
+        if (DBG) Slog.d(TAG, "releaseWifiLockLocked: " + wifiLock);
+
+        hadLock = (wifiLock != null);
+
+        long ident = Binder.clearCallingIdentity();
+        try {
+            if (hadLock) {
+                noteReleaseWifiLock(wifiLock);
+                switch(wifiLock.mMode) {
+                    case WifiManager.WIFI_MODE_FULL:
+                        ++mFullLocksReleased;
+                        break;
+                    case WifiManager.WIFI_MODE_FULL_HIGH_PERF:
+                        ++mFullHighPerfLocksReleased;
+                        break;
+                    case WifiManager.WIFI_MODE_SCAN_ONLY:
+                        ++mScanLocksReleased;
+                        break;
+                }
+                mWifiController.sendMessage(CMD_LOCKS_CHANGED);
+            }
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+
+        return hadLock;
+    }
+
+    private abstract class DeathRecipient
+            implements IBinder.DeathRecipient {
+        String mTag;
+        int mMode;
+        IBinder mBinder;
+        WorkSource mWorkSource;
+
+        DeathRecipient(int mode, String tag, IBinder binder, WorkSource ws) {
+            super();
+            mTag = tag;
+            mMode = mode;
+            mBinder = binder;
+            mWorkSource = ws;
+            try {
+                mBinder.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                binderDied();
+            }
+        }
+
+        void unlinkDeathRecipient() {
+            mBinder.unlinkToDeath(this, 0);
+        }
+    }
+
+    private class Multicaster extends DeathRecipient {
+        Multicaster(String tag, IBinder binder) {
+            super(Binder.getCallingUid(), tag, binder, null);
+        }
+
+        public void binderDied() {
+            Slog.e(TAG, "Multicaster binderDied");
+            synchronized (mMulticasters) {
+                int i = mMulticasters.indexOf(this);
+                if (i != -1) {
+                    removeMulticasterLocked(i, mMode);
+                }
+            }
+        }
+
+        public String toString() {
+            return "Multicaster{" + mTag + " binder=" + mBinder + "}";
+        }
+
+        public int getUid() {
+            return mMode;
+        }
+    }
+
+    public void initializeMulticastFiltering() {
+        enforceMulticastChangePermission();
+
+        synchronized (mMulticasters) {
+            // if anybody had requested filters be off, leave off
+            if (mMulticasters.size() != 0) {
+                return;
+            } else {
+                mWifiStateMachine.startFilteringMulticastV4Packets();
+            }
+        }
+    }
+
+    public void acquireMulticastLock(IBinder binder, String tag) {
+        enforceMulticastChangePermission();
+
+        synchronized (mMulticasters) {
+            mMulticastEnabled++;
+            mMulticasters.add(new Multicaster(tag, binder));
+            // Note that we could call stopFilteringMulticastV4Packets only when
+            // our new size == 1 (first call), but this function won't
+            // be called often and by making the stopPacket call each
+            // time we're less fragile and self-healing.
+            mWifiStateMachine.stopFilteringMulticastV4Packets();
+        }
+
+        int uid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteWifiMulticastEnabled(uid);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    public void releaseMulticastLock() {
+        enforceMulticastChangePermission();
+
+        int uid = Binder.getCallingUid();
+        synchronized (mMulticasters) {
+            mMulticastDisabled++;
+            int size = mMulticasters.size();
+            for (int i = size - 1; i >= 0; i--) {
+                Multicaster m = mMulticasters.get(i);
+                if ((m != null) && (m.getUid() == uid)) {
+                    removeMulticasterLocked(i, uid);
+                }
+            }
+        }
+    }
+
+    private void removeMulticasterLocked(int i, int uid)
+    {
+        Multicaster removed = mMulticasters.remove(i);
+
+        if (removed != null) {
+            removed.unlinkDeathRecipient();
+        }
+        if (mMulticasters.size() == 0) {
+            mWifiStateMachine.startFilteringMulticastV4Packets();
+        }
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            mBatteryStats.noteWifiMulticastDisabled(uid);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    public boolean isMulticastEnabled() {
+        enforceAccessPermission();
+
+        synchronized (mMulticasters) {
+            return (mMulticasters.size() > 0);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wifi/WifiSettingsStore.java b/services/core/java/com/android/server/wifi/WifiSettingsStore.java
new file mode 100644
index 0000000..3ff8061
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/WifiSettingsStore.java
@@ -0,0 +1,197 @@
+/*
+ * 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.wifi;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/* Tracks persisted settings for Wi-Fi and airplane mode interaction */
+final class WifiSettingsStore {
+    /* Values tracked in Settings.Global.WIFI_ON */
+    private static final int WIFI_DISABLED                      = 0;
+    private static final int WIFI_ENABLED                       = 1;
+    /* Wifi enabled while in airplane mode */
+    private static final int WIFI_ENABLED_AIRPLANE_OVERRIDE     = 2;
+    /* Wifi disabled due to airplane mode on */
+    private static final int WIFI_DISABLED_AIRPLANE_ON          = 3;
+
+    /* Persisted state that tracks the wifi & airplane interaction from settings */
+    private int mPersistWifiState = WIFI_DISABLED;
+    /* Tracks current airplane mode state */
+    private boolean mAirplaneModeOn = false;
+
+    /* Tracks the setting of scan being available even when wi-fi is turned off
+     */
+    private boolean mScanAlwaysAvailable;
+
+    private final Context mContext;
+
+    /* Tracks if we have checked the saved wi-fi state after boot */
+    private boolean mCheckSavedStateAtBoot = false;
+
+    WifiSettingsStore(Context context) {
+        mContext = context;
+        mAirplaneModeOn = getPersistedAirplaneModeOn();
+        mPersistWifiState = getPersistedWifiState();
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+    }
+
+    synchronized boolean isWifiToggleEnabled() {
+        if (!mCheckSavedStateAtBoot) {
+            mCheckSavedStateAtBoot = true;
+            if (testAndClearWifiSavedState()) return true;
+        }
+
+        if (mAirplaneModeOn) {
+            return mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE;
+        } else {
+            return mPersistWifiState != WIFI_DISABLED;
+        }
+    }
+
+    /**
+     * Returns true if airplane mode is currently on.
+     * @return {@code true} if airplane mode is on.
+     */
+    synchronized boolean isAirplaneModeOn() {
+       return mAirplaneModeOn;
+    }
+
+    synchronized boolean isScanAlwaysAvailable() {
+        return mScanAlwaysAvailable;
+    }
+
+    synchronized boolean handleWifiToggled(boolean wifiEnabled) {
+        // Can Wi-Fi be toggled in airplane mode ?
+        if (mAirplaneModeOn && !isAirplaneToggleable()) {
+            return false;
+        }
+
+        if (wifiEnabled) {
+            if (mAirplaneModeOn) {
+                persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);
+            } else {
+                persistWifiState(WIFI_ENABLED);
+            }
+        } else {
+            // When wifi state is disabled, we do not care
+            // if airplane mode is on or not. The scenario of
+            // wifi being disabled due to airplane mode being turned on
+            // is handled handleAirplaneModeToggled()
+            persistWifiState(WIFI_DISABLED);
+        }
+        return true;
+    }
+
+    synchronized boolean handleAirplaneModeToggled() {
+        // Is Wi-Fi sensitive to airplane mode changes ?
+        if (!isAirplaneSensitive()) {
+            return false;
+        }
+
+        mAirplaneModeOn = getPersistedAirplaneModeOn();
+        if (mAirplaneModeOn) {
+            // Wifi disabled due to airplane on
+            if (mPersistWifiState == WIFI_ENABLED) {
+                persistWifiState(WIFI_DISABLED_AIRPLANE_ON);
+            }
+        } else {
+            /* On airplane mode disable, restore wifi state if necessary */
+            if (testAndClearWifiSavedState() ||
+                    mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE) {
+                persistWifiState(WIFI_ENABLED);
+            }
+        }
+        return true;
+    }
+
+    synchronized void handleWifiScanAlwaysAvailableToggled() {
+        mScanAlwaysAvailable = getPersistedScanAlwaysAvailable();
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mPersistWifiState " + mPersistWifiState);
+        pw.println("mAirplaneModeOn " + mAirplaneModeOn);
+    }
+
+    private void persistWifiState(int state) {
+        final ContentResolver cr = mContext.getContentResolver();
+        mPersistWifiState = state;
+        Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);
+    }
+
+    /* Does Wi-Fi need to be disabled when airplane mode is on ? */
+    private boolean isAirplaneSensitive() {
+        String airplaneModeRadios = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_RADIOS);
+        return airplaneModeRadios == null
+                || airplaneModeRadios.contains(Settings.Global.RADIO_WIFI);
+    }
+
+    /* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */
+    private boolean isAirplaneToggleable() {
+        String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
+        return toggleableRadios != null
+                && toggleableRadios.contains(Settings.Global.RADIO_WIFI);
+    }
+
+     /* After a reboot, we restore wi-fi to be on if it was turned off temporarily for tethering.
+      * The settings app tracks the saved state, but the framework has to check it at boot to
+      * make sure the wi-fi is turned on in case it was turned off for the purpose of tethering.
+      *
+      * Note that this is not part of the regular WIFI_ON setting because this only needs to
+      * be controlled through the settings app and not the Wi-Fi public API.
+      */
+    private boolean testAndClearWifiSavedState() {
+        final ContentResolver cr = mContext.getContentResolver();
+        int wifiSavedState = 0;
+        try {
+            wifiSavedState = Settings.Global.getInt(cr, Settings.Global.WIFI_SAVED_STATE);
+            if(wifiSavedState == 1)
+                Settings.Global.putInt(cr, Settings.Global.WIFI_SAVED_STATE, 0);
+        } catch (Settings.SettingNotFoundException e) {
+            ;
+        }
+        return (wifiSavedState == 1);
+    }
+
+    private int getPersistedWifiState() {
+        final ContentResolver cr = mContext.getContentResolver();
+        try {
+            return Settings.Global.getInt(cr, Settings.Global.WIFI_ON);
+        } catch (Settings.SettingNotFoundException e) {
+            Settings.Global.putInt(cr, Settings.Global.WIFI_ON, WIFI_DISABLED);
+            return WIFI_DISABLED;
+        }
+    }
+
+    private boolean getPersistedAirplaneModeOn() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+    }
+
+    private boolean getPersistedScanAlwaysAvailable() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
+                0) == 1;
+    }
+}
diff --git a/services/core/java/com/android/server/wifi/WifiTrafficPoller.java b/services/core/java/com/android/server/wifi/WifiTrafficPoller.java
new file mode 100644
index 0000000..b498550
--- /dev/null
+++ b/services/core/java/com/android/server/wifi/WifiTrafficPoller.java
@@ -0,0 +1,191 @@
+/*
+ * 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.wifi;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import static android.net.NetworkInfo.DetailedState.CONNECTED;
+import android.net.TrafficStats;
+import android.net.wifi.WifiManager;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+import android.os.Handler;
+import android.os.Message;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.android.internal.util.AsyncChannel;
+
+/* Polls for traffic stats and notifies the clients */
+final class WifiTrafficPoller {
+    /**
+     * Interval in milliseconds between polling for traffic
+     * statistics
+     */
+    private static final int POLL_TRAFFIC_STATS_INTERVAL_MSECS = 1000;
+
+    private static final int ENABLE_TRAFFIC_STATS_POLL  = 1;
+    private static final int TRAFFIC_STATS_POLL         = 2;
+    private static final int ADD_CLIENT                 = 3;
+    private static final int REMOVE_CLIENT              = 4;
+
+    private boolean mEnableTrafficStatsPoll = false;
+    private int mTrafficStatsPollToken = 0;
+    private long mTxPkts;
+    private long mRxPkts;
+    /* Tracks last reported data activity */
+    private int mDataActivity;
+
+    private final List<Messenger> mClients = new ArrayList<Messenger>();
+    // err on the side of updating at boot since screen on broadcast may be missed
+    // the first time
+    private AtomicBoolean mScreenOn = new AtomicBoolean(true);
+    private final TrafficHandler mTrafficHandler;
+    private NetworkInfo mNetworkInfo;
+    private final String mInterface;
+
+    WifiTrafficPoller(Context context, String iface) {
+        mInterface = iface;
+        mTrafficHandler = new TrafficHandler();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(Intent.ACTION_SCREEN_OFF);
+        filter.addAction(Intent.ACTION_SCREEN_ON);
+
+        context.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if (intent.getAction().equals(
+                                WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                            mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
+                                    WifiManager.EXTRA_NETWORK_INFO);
+                        } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+                            mScreenOn.set(false);
+                        } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+                            mScreenOn.set(true);
+                        }
+                        evaluateTrafficStatsPolling();
+                    }
+                }, filter);
+    }
+
+    void addClient(Messenger client) {
+        Message.obtain(mTrafficHandler, ADD_CLIENT, client).sendToTarget();
+    }
+
+    void removeClient(Messenger client) {
+        Message.obtain(mTrafficHandler, REMOVE_CLIENT, client).sendToTarget();
+    }
+
+
+    private class TrafficHandler extends Handler {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ENABLE_TRAFFIC_STATS_POLL:
+                    mEnableTrafficStatsPoll = (msg.arg1 == 1);
+                    mTrafficStatsPollToken++;
+                    if (mEnableTrafficStatsPoll) {
+                        notifyOnDataActivity();
+                        sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+                                mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+                    }
+                    break;
+                case TRAFFIC_STATS_POLL:
+                    if (msg.arg1 == mTrafficStatsPollToken) {
+                        notifyOnDataActivity();
+                        sendMessageDelayed(Message.obtain(this, TRAFFIC_STATS_POLL,
+                                mTrafficStatsPollToken, 0), POLL_TRAFFIC_STATS_INTERVAL_MSECS);
+                    }
+                    break;
+                case ADD_CLIENT:
+                    mClients.add((Messenger) msg.obj);
+                    break;
+                case REMOVE_CLIENT:
+                    mClients.remove(msg.obj);
+                    break;
+            }
+
+        }
+    }
+
+    private void evaluateTrafficStatsPolling() {
+        Message msg;
+        if (mNetworkInfo == null) return;
+        if (mNetworkInfo.getDetailedState() == CONNECTED && mScreenOn.get()) {
+            msg = Message.obtain(mTrafficHandler,
+                    ENABLE_TRAFFIC_STATS_POLL, 1, 0);
+        } else {
+            msg = Message.obtain(mTrafficHandler,
+                    ENABLE_TRAFFIC_STATS_POLL, 0, 0);
+        }
+        msg.sendToTarget();
+    }
+
+    private void notifyOnDataActivity() {
+        long sent, received;
+        long preTxPkts = mTxPkts, preRxPkts = mRxPkts;
+        int dataActivity = WifiManager.DATA_ACTIVITY_NONE;
+
+        mTxPkts = TrafficStats.getTxPackets(mInterface);
+        mRxPkts = TrafficStats.getRxPackets(mInterface);
+
+        if (preTxPkts > 0 || preRxPkts > 0) {
+            sent = mTxPkts - preTxPkts;
+            received = mRxPkts - preRxPkts;
+            if (sent > 0) {
+                dataActivity |= WifiManager.DATA_ACTIVITY_OUT;
+            }
+            if (received > 0) {
+                dataActivity |= WifiManager.DATA_ACTIVITY_IN;
+            }
+
+            if (dataActivity != mDataActivity && mScreenOn.get()) {
+                mDataActivity = dataActivity;
+                for (Messenger client : mClients) {
+                    Message msg = Message.obtain();
+                    msg.what = WifiManager.DATA_ACTIVITY_NOTIFICATION;
+                    msg.arg1 = mDataActivity;
+                    try {
+                        client.send(msg);
+                    } catch (RemoteException e) {
+                        // Failed to reach, skip
+                        // Client removal is handled in WifiService
+                    }
+                }
+            }
+        }
+    }
+
+    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mEnableTrafficStatsPoll " + mEnableTrafficStatsPoll);
+        pw.println("mTrafficStatsPollToken " + mTrafficStatsPollToken);
+        pw.println("mTxPkts " + mTxPkts);
+        pw.println("mRxPkts " + mRxPkts);
+        pw.println("mDataActivity " + mDataActivity);
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
new file mode 100644
index 0000000..756e06a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -0,0 +1,767 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.util.Slog;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.ScaleAnimation;
+
+import com.android.internal.util.DumpUtils.Dump;
+import com.android.server.AttributeCache;
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToFrontExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_taskToBackExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_wallpaperIntraCloseExitAnimation;
+
+// State management of app transitions.  When we are preparing for a
+// transition, mNextAppTransition will be the kind of transition to
+// perform or TRANSIT_NONE if we are not waiting.  If we are waiting,
+// mOpeningApps and mClosingApps are the lists of tokens that will be
+// made visible or hidden at the next transition.
+public class AppTransition implements Dump {
+    private static final String TAG = "AppTransition";
+    private static final boolean DEBUG_APP_TRANSITIONS =
+            WindowManagerService.DEBUG_APP_TRANSITIONS;
+    private static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM;
+
+    /** Bit mask that is set for all enter transition. */
+    public static final int TRANSIT_ENTER_MASK = 0x1000;
+
+    /** Bit mask that is set for all exit transitions. */
+    public static final int TRANSIT_EXIT_MASK = 0x2000;
+
+    /** Not set up for a transition. */
+    public static final int TRANSIT_UNSET = -1;
+    /** No animation for transition. */
+    public static final int TRANSIT_NONE = 0;
+    /** A window in a new activity is being opened on top of an existing one in the same task. */
+    public static final int TRANSIT_ACTIVITY_OPEN = 6 | TRANSIT_ENTER_MASK;
+    /** The window in the top-most activity is being closed to reveal the
+     * previous activity in the same task. */
+    public static final int TRANSIT_ACTIVITY_CLOSE = 7 | TRANSIT_EXIT_MASK;
+    /** A window in a new task is being opened on top of an existing one
+     * in another activity's task. */
+    public static final int TRANSIT_TASK_OPEN = 8 | TRANSIT_ENTER_MASK;
+    /** A window in the top-most activity is being closed to reveal the
+     * previous activity in a different task. */
+    public static final int TRANSIT_TASK_CLOSE = 9 | TRANSIT_EXIT_MASK;
+    /** A window in an existing task is being displayed on top of an existing one
+     * in another activity's task. */
+    public static final int TRANSIT_TASK_TO_FRONT = 10 | TRANSIT_ENTER_MASK;
+    /** A window in an existing task is being put below all other tasks. */
+    public static final int TRANSIT_TASK_TO_BACK = 11 | TRANSIT_EXIT_MASK;
+    /** A window in a new activity that doesn't have a wallpaper is being opened on top of one that
+     * does, effectively closing the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_CLOSE = 12 | TRANSIT_EXIT_MASK;
+    /** A window in a new activity that does have a wallpaper is being opened on one that didn't,
+     * effectively opening the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_OPEN = 13 | TRANSIT_ENTER_MASK;
+    /** A window in a new activity is being opened on top of an existing one, and both are on top
+     * of the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_INTRA_OPEN = 14 | TRANSIT_ENTER_MASK;
+    /** The window in the top-most activity is being closed to reveal the previous activity, and
+     * both are on top of the wallpaper. */
+    public static final int TRANSIT_WALLPAPER_INTRA_CLOSE = 15 | TRANSIT_EXIT_MASK;
+
+    /** Fraction of animation at which the recents thumbnail becomes completely transparent */
+    private static final float RECENTS_THUMBNAIL_FADEOUT_FRACTION = 0.25f;
+
+    private static final long DEFAULT_APP_TRANSITION_DURATION = 250;
+
+    private final Context mContext;
+    private final Handler mH;
+
+    private int mNextAppTransition = TRANSIT_UNSET;
+
+    private static final int NEXT_TRANSIT_TYPE_NONE = 0;
+    private static final int NEXT_TRANSIT_TYPE_CUSTOM = 1;
+    private static final int NEXT_TRANSIT_TYPE_SCALE_UP = 2;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP = 3;
+    private static final int NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN = 4;
+    private int mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+
+    private String mNextAppTransitionPackage;
+    private Bitmap mNextAppTransitionThumbnail;
+    // Used for thumbnail transitions. True if we're scaling up, false if scaling down
+    private boolean mNextAppTransitionScaleUp;
+    private IRemoteCallback mNextAppTransitionCallback;
+    private int mNextAppTransitionEnter;
+    private int mNextAppTransitionExit;
+    private int mNextAppTransitionStartX;
+    private int mNextAppTransitionStartY;
+    private int mNextAppTransitionStartWidth;
+    private int mNextAppTransitionStartHeight;
+
+    private final static int APP_STATE_IDLE = 0;
+    private final static int APP_STATE_READY = 1;
+    private final static int APP_STATE_RUNNING = 2;
+    private final static int APP_STATE_TIMEOUT = 3;
+    private int mAppTransitionState = APP_STATE_IDLE;
+
+    private final int mConfigShortAnimTime;
+    private final Interpolator mDecelerateInterpolator;
+    private final Interpolator mThumbnailFadeoutInterpolator;
+
+    private int mCurrentUserId = 0;
+
+    AppTransition(Context context, Handler h) {
+        mContext = context;
+        mH = h;
+        mConfigShortAnimTime = context.getResources().getInteger(
+                com.android.internal.R.integer.config_shortAnimTime);
+        mDecelerateInterpolator = AnimationUtils.loadInterpolator(context,
+                com.android.internal.R.interpolator.decelerate_cubic);
+        mThumbnailFadeoutInterpolator = new Interpolator() {
+            @Override
+            public float getInterpolation(float input) {
+                // Linear response for first fraction, then complete after that.
+                if (input < RECENTS_THUMBNAIL_FADEOUT_FRACTION) {
+                    return input / RECENTS_THUMBNAIL_FADEOUT_FRACTION;
+                }
+                return 1.0f;
+            }
+        };
+    }
+
+    boolean isTransitionSet() {
+        return mNextAppTransition != TRANSIT_UNSET;
+    }
+
+    boolean isTransitionNone() {
+        return mNextAppTransition == TRANSIT_NONE;
+    }
+
+    boolean isTransitionEqual(int transit) {
+        return mNextAppTransition == transit;
+    }
+
+    int getAppTransition() {
+        return mNextAppTransition;
+     }
+
+    void setAppTransition(int transit) {
+        mNextAppTransition = transit;
+    }
+
+    boolean isReady() {
+        return mAppTransitionState == APP_STATE_READY
+                || mAppTransitionState == APP_STATE_TIMEOUT;
+    }
+
+    void setReady() {
+        mAppTransitionState = APP_STATE_READY;
+    }
+
+    boolean isRunning() {
+        return mAppTransitionState == APP_STATE_RUNNING;
+    }
+
+    void setIdle() {
+        mAppTransitionState = APP_STATE_IDLE;
+    }
+
+    boolean isTimeout() {
+        return mAppTransitionState == APP_STATE_TIMEOUT;
+    }
+
+    void setTimeout() {
+        mAppTransitionState = APP_STATE_TIMEOUT;
+    }
+
+    Bitmap getNextAppTransitionThumbnail() {
+        return mNextAppTransitionThumbnail;
+    }
+
+    void getStartingPoint(Point outPoint) {
+        outPoint.x = mNextAppTransitionStartX;
+        outPoint.y = mNextAppTransitionStartY;
+    }
+
+    void prepare() {
+        if (!isRunning()) {
+            mAppTransitionState = APP_STATE_IDLE;
+        }
+    }
+
+    void goodToGo() {
+        mNextAppTransition = TRANSIT_UNSET;
+        mAppTransitionState = APP_STATE_RUNNING;
+    }
+
+    void clear() {
+        mNextAppTransitionType = NEXT_TRANSIT_TYPE_NONE;
+        mNextAppTransitionPackage = null;
+        mNextAppTransitionThumbnail = null;
+    }
+
+    void freeze() {
+        setAppTransition(AppTransition.TRANSIT_UNSET);
+        clear();
+        setReady();
+    }
+
+    private AttributeCache.Entry getCachedAnimations(WindowManager.LayoutParams lp) {
+        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: layout params pkg="
+                + (lp != null ? lp.packageName : null)
+                + " resId=0x" + (lp != null ? Integer.toHexString(lp.windowAnimations) : null));
+        if (lp != null && lp.windowAnimations != 0) {
+            // If this is a system resource, don't try to load it from the
+            // application resources.  It is nice to avoid loading application
+            // resources if we can.
+            String packageName = lp.packageName != null ? lp.packageName : "android";
+            int resId = lp.windowAnimations;
+            if ((resId&0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+                    + packageName);
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+        }
+        return null;
+    }
+
+    private AttributeCache.Entry getCachedAnimations(String packageName, int resId) {
+        if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: package="
+                + packageName + " resId=0x" + Integer.toHexString(resId));
+        if (packageName != null) {
+            if ((resId&0xFF000000) == 0x01000000) {
+                packageName = "android";
+            }
+            if (DEBUG_ANIM) Slog.v(TAG, "Loading animations: picked package="
+                    + packageName);
+            return AttributeCache.instance().get(packageName, resId,
+                    com.android.internal.R.styleable.WindowAnimation, mCurrentUserId);
+        }
+        return null;
+    }
+
+    Animation loadAnimation(WindowManager.LayoutParams lp, int animAttr) {
+        int anim = 0;
+        Context context = mContext;
+        if (animAttr >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                context = ent.context;
+                anim = ent.array.getResourceId(animAttr, 0);
+            }
+        }
+        if (anim != 0) {
+            return AnimationUtils.loadAnimation(context, anim);
+        }
+        return null;
+    }
+
+    private Animation loadAnimation(String packageName, int resId) {
+        int anim = 0;
+        Context context = mContext;
+        if (resId >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(packageName, resId);
+            if (ent != null) {
+                context = ent.context;
+                anim = resId;
+            }
+        }
+        if (anim != 0) {
+            return AnimationUtils.loadAnimation(context, anim);
+        }
+        return null;
+    }
+
+    /**
+     * Compute the pivot point for an animation that is scaling from a small
+     * rect on screen to a larger rect.  The pivot point varies depending on
+     * the distance between the inner and outer edges on both sides.  This
+     * function computes the pivot point for one dimension.
+     * @param startPos  Offset from left/top edge of outer rectangle to
+     * left/top edge of inner rectangle.
+     * @param finalScale The scaling factor between the size of the outer
+     * and inner rectangles.
+     */
+    private static float computePivot(int startPos, float finalScale) {
+        final float denom = finalScale-1;
+        if (Math.abs(denom) < .0001f) {
+            return startPos;
+        }
+        return -startPos / denom;
+    }
+
+    private Animation createScaleUpAnimationLocked(int transit, boolean enter,
+                                                   int appWidth, int appHeight) {
+        Animation a = null;
+        if (enter) {
+            // Entering app zooms out from the center of the initial rect.
+            float scaleW = mNextAppTransitionStartWidth / (float) appWidth;
+            float scaleH = mNextAppTransitionStartHeight / (float) appHeight;
+            Animation scale = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                    computePivot(mNextAppTransitionStartX, scaleW),
+                    computePivot(mNextAppTransitionStartY, scaleH));
+            scale.setInterpolator(mDecelerateInterpolator);
+
+            Animation alpha = new AlphaAnimation(0, 1);
+            alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+
+            AnimationSet set = new AnimationSet(false);
+            set.addAnimation(scale);
+            set.addAnimation(alpha);
+            set.setDetachWallpaper(true);
+            a = set;
+        } else  if (transit == TRANSIT_WALLPAPER_INTRA_OPEN ||
+                    transit == TRANSIT_WALLPAPER_INTRA_CLOSE) {
+            // If we are on top of the wallpaper, we need an animation that
+            // correctly handles the wallpaper staying static behind all of
+            // the animated elements.  To do this, will just have the existing
+            // element fade out.
+            a = new AlphaAnimation(1, 0);
+            a.setDetachWallpaper(true);
+        } else {
+            // For normal animations, the exiting element just holds in place.
+            a = new AlphaAnimation(1, 1);
+        }
+
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final long duration;
+        switch (transit) {
+            case TRANSIT_ACTIVITY_OPEN:
+            case TRANSIT_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        a.setDuration(duration);
+        a.setFillAfter(true);
+        a.setInterpolator(mDecelerateInterpolator);
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+    Animation createThumbnailAnimationLocked(int transit, boolean enter, boolean thumb,
+                                    int appWidth, int appHeight) {
+        Animation a;
+        final int thumbWidthI = mNextAppTransitionThumbnail.getWidth();
+        final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1;
+        final int thumbHeightI = mNextAppTransitionThumbnail.getHeight();
+        final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1;
+        if (thumb) {
+            // Animation for zooming thumbnail from its initial size to
+            // filling the screen.
+            if (mNextAppTransitionScaleUp) {
+                float scaleW = appWidth / thumbWidth;
+                float scaleH = appHeight / thumbHeight;
+                Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+                        computePivot(mNextAppTransitionStartX, 1 / scaleW),
+                        computePivot(mNextAppTransitionStartY, 1 / scaleH));
+                scale.setInterpolator(mDecelerateInterpolator);
+
+                Animation alpha = new AlphaAnimation(1, 0);
+                alpha.setInterpolator(mThumbnailFadeoutInterpolator);
+
+                // This AnimationSet uses the Interpolators assigned above.
+                AnimationSet set = new AnimationSet(false);
+                set.addAnimation(scale);
+                set.addAnimation(alpha);
+                a = set;
+            } else {
+                float scaleW = appWidth / thumbWidth;
+                float scaleH = appHeight / thumbHeight;
+                a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                        computePivot(mNextAppTransitionStartX, 1 / scaleW),
+                        computePivot(mNextAppTransitionStartY, 1 / scaleH));
+            }
+        } else if (enter) {
+            // Entering app zooms out from the center of the thumbnail.
+            if (mNextAppTransitionScaleUp) {
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                a = new ScaleAnimation(scaleW, 1, scaleH, 1,
+                        computePivot(mNextAppTransitionStartX, scaleW),
+                        computePivot(mNextAppTransitionStartY, scaleH));
+            } else {
+                // noop animation
+                a = new AlphaAnimation(1, 1);
+            }
+        } else {
+            // Exiting app
+            if (mNextAppTransitionScaleUp) {
+                if (transit == TRANSIT_WALLPAPER_INTRA_OPEN) {
+                    // Fade out while bringing up selected activity. This keeps the
+                    // current activity from showing through a launching wallpaper
+                    // activity.
+                    a = new AlphaAnimation(1, 0);
+                } else {
+                    // noop animation
+                    a = new AlphaAnimation(1, 1);
+                }
+            } else {
+                float scaleW = thumbWidth / appWidth;
+                float scaleH = thumbHeight / appHeight;
+                Animation scale = new ScaleAnimation(1, scaleW, 1, scaleH,
+                        computePivot(mNextAppTransitionStartX, scaleW),
+                        computePivot(mNextAppTransitionStartY, scaleH));
+
+                Animation alpha = new AlphaAnimation(1, 0);
+
+                AnimationSet set = new AnimationSet(true);
+                set.addAnimation(scale);
+                set.addAnimation(alpha);
+                set.setZAdjustment(Animation.ZORDER_TOP);
+                a = set;
+            }
+        }
+
+        // Pick the desired duration.  If this is an inter-activity transition,
+        // it  is the standard duration for that.  Otherwise we use the longer
+        // task transition duration.
+        final long duration;
+        switch (transit) {
+            case TRANSIT_ACTIVITY_OPEN:
+            case TRANSIT_ACTIVITY_CLOSE:
+                duration = mConfigShortAnimTime;
+                break;
+            default:
+                duration = DEFAULT_APP_TRANSITION_DURATION;
+                break;
+        }
+        a.setDuration(duration);
+        a.setFillAfter(true);
+        a.setInterpolator(mDecelerateInterpolator);
+        a.initialize(appWidth, appHeight, appWidth, appHeight);
+        return a;
+    }
+
+
+    Animation loadAnimation(WindowManager.LayoutParams lp, int transit, boolean enter,
+                            int appWidth, int appHeight) {
+        Animation a;
+        if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM) {
+            a = loadAnimation(mNextAppTransitionPackage, enter ?
+                    mNextAppTransitionEnter : mNextAppTransitionExit);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=ANIM_CUSTOM"
+                    + " transit=" + transit + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_SCALE_UP) {
+            a = createScaleUpAnimationLocked(transit, enter, appWidth, appHeight);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a + " nextAppTransition=ANIM_SCALE_UP"
+                    + " transit=" + transit + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        } else if (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP ||
+                mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN) {
+            mNextAppTransitionScaleUp =
+                    (mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP);
+            a = createThumbnailAnimationLocked(transit, enter, false, appWidth, appHeight);
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+                String animName = mNextAppTransitionScaleUp ?
+                        "ANIM_THUMBNAIL_SCALE_UP" : "ANIM_THUMBNAIL_SCALE_DOWN";
+                Slog.v(TAG, "applyAnimation:"
+                        + " anim=" + a + " nextAppTransition=" + animName
+                        + " transit=" + transit + " isEntrance=" + enter
+                        + " Callers=" + Debug.getCallers(3));
+            }
+        } else {
+            int animAttr = 0;
+            switch (transit) {
+                case TRANSIT_ACTIVITY_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_activityOpenEnterAnimation
+                            : WindowAnimation_activityOpenExitAnimation;
+                    break;
+                case TRANSIT_ACTIVITY_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_activityCloseEnterAnimation
+                            : WindowAnimation_activityCloseExitAnimation;
+                    break;
+                case TRANSIT_TASK_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_taskOpenEnterAnimation
+                            : WindowAnimation_taskOpenExitAnimation;
+                    break;
+                case TRANSIT_TASK_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_taskCloseEnterAnimation
+                            : WindowAnimation_taskCloseExitAnimation;
+                    break;
+                case TRANSIT_TASK_TO_FRONT:
+                    animAttr = enter
+                            ? WindowAnimation_taskToFrontEnterAnimation
+                            : WindowAnimation_taskToFrontExitAnimation;
+                    break;
+                case TRANSIT_TASK_TO_BACK:
+                    animAttr = enter
+                            ? WindowAnimation_taskToBackEnterAnimation
+                            : WindowAnimation_taskToBackExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperOpenEnterAnimation
+                            : WindowAnimation_wallpaperOpenExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperCloseEnterAnimation
+                            : WindowAnimation_wallpaperCloseExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_INTRA_OPEN:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperIntraOpenEnterAnimation
+                            : WindowAnimation_wallpaperIntraOpenExitAnimation;
+                    break;
+                case TRANSIT_WALLPAPER_INTRA_CLOSE:
+                    animAttr = enter
+                            ? WindowAnimation_wallpaperIntraCloseEnterAnimation
+                            : WindowAnimation_wallpaperIntraCloseExitAnimation;
+                    break;
+            }
+            a = animAttr != 0 ? loadAnimation(lp, animAttr) : null;
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation:"
+                    + " anim=" + a
+                    + " animAttr=0x" + Integer.toHexString(animAttr)
+                    + " transit=" + transit + " isEntrance=" + enter
+                    + " Callers=" + Debug.getCallers(3));
+        }
+        return a;
+    }
+
+    void postAnimationCallback() {
+        if (mNextAppTransitionCallback != null) {
+            mH.sendMessage(mH.obtainMessage(H.DO_ANIMATION_CALLBACK, mNextAppTransitionCallback));
+            mNextAppTransitionCallback = null;
+        }
+    }
+
+    void overridePendingAppTransition(String packageName, int enterAnim, int exitAnim,
+                                             IRemoteCallback startedCallback) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+            mNextAppTransitionPackage = packageName;
+            mNextAppTransitionThumbnail = null;
+            mNextAppTransitionEnter = enterAnim;
+            mNextAppTransitionExit = exitAnim;
+            postAnimationCallback();
+            mNextAppTransitionCallback = startedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+                                                    int startHeight) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = NEXT_TRANSIT_TYPE_SCALE_UP;
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = null;
+            mNextAppTransitionStartX = startX;
+            mNextAppTransitionStartY = startY;
+            mNextAppTransitionStartWidth = startWidth;
+            mNextAppTransitionStartHeight = startHeight;
+            postAnimationCallback();
+            mNextAppTransitionCallback = null;
+        }
+    }
+
+    void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX, int startY,
+                                           IRemoteCallback startedCallback, boolean scaleUp) {
+        if (isTransitionSet()) {
+            mNextAppTransitionType = scaleUp ? NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP
+                    : NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN;
+            mNextAppTransitionPackage = null;
+            mNextAppTransitionThumbnail = srcThumb;
+            mNextAppTransitionScaleUp = scaleUp;
+            mNextAppTransitionStartX = startX;
+            mNextAppTransitionStartY = startY;
+            postAnimationCallback();
+            mNextAppTransitionCallback = startedCallback;
+        } else {
+            postAnimationCallback();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "mNextAppTransition=0x" + Integer.toHexString(mNextAppTransition);
+    }
+
+    /**
+     * Returns the human readable name of a window transition.
+     *
+     * @param transition The window transition.
+     * @return The transition symbolic name.
+     */
+    public static String appTransitionToString(int transition) {
+        switch (transition) {
+            case TRANSIT_UNSET: {
+                return "TRANSIT_UNSET";
+            }
+            case TRANSIT_NONE: {
+                return "TRANSIT_NONE";
+            }
+            case TRANSIT_EXIT_MASK: {
+                return "TRANSIT_EXIT_MASK";
+            }
+            case TRANSIT_ACTIVITY_OPEN: {
+                return "TRANSIT_ACTIVITY_OPEN";
+            }
+            case TRANSIT_ACTIVITY_CLOSE: {
+                return "TRANSIT_ACTIVITY_CLOSE";
+            }
+            case TRANSIT_TASK_OPEN: {
+                return "TRANSIT_TASK_OPEN";
+            }
+            case TRANSIT_TASK_CLOSE: {
+                return "TRANSIT_TASK_CLOSE";
+            }
+            case TRANSIT_TASK_TO_FRONT: {
+                return "TRANSIT_TASK_TO_FRONT";
+            }
+            case TRANSIT_TASK_TO_BACK: {
+                return "TRANSIT_TASK_TO_BACK";
+            }
+            case TRANSIT_WALLPAPER_CLOSE: {
+                return "TRANSIT_WALLPAPER_CLOSE";
+            }
+            case TRANSIT_WALLPAPER_OPEN: {
+                return "TRANSIT_WALLPAPER_OPEN";
+            }
+            case TRANSIT_WALLPAPER_INTRA_OPEN: {
+                return "TRANSIT_WALLPAPER_INTRA_OPEN";
+            }
+            case TRANSIT_WALLPAPER_INTRA_CLOSE: {
+                return "TRANSIT_WALLPAPER_INTRA_CLOSE";
+            }
+            default: {
+                return "<UNKNOWN>";
+            }
+        }
+    }
+
+    private String appStateToString() {
+        switch (mAppTransitionState) {
+            case APP_STATE_IDLE:
+                return "APP_STATE_IDLE";
+            case APP_STATE_READY:
+                return "APP_STATE_READY";
+            case APP_STATE_RUNNING:
+                return "APP_STATE_RUNNING";
+            case APP_STATE_TIMEOUT:
+                return "APP_STATE_TIMEOUT";
+            default:
+                return "unknown state=" + mAppTransitionState;
+        }
+    }
+
+    private String transitTypeToString() {
+        switch (mNextAppTransitionType) {
+            case NEXT_TRANSIT_TYPE_NONE:
+                return "NEXT_TRANSIT_TYPE_NONE";
+            case NEXT_TRANSIT_TYPE_CUSTOM:
+                return "NEXT_TRANSIT_TYPE_CUSTOM";
+            case NEXT_TRANSIT_TYPE_SCALE_UP:
+                return "NEXT_TRANSIT_TYPE_SCALE_UP";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP";
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+                return "NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN";
+            default:
+                return "unknown type=" + mNextAppTransitionType;
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.print(" " + this);
+        pw.print("  mAppTransitionState="); pw.println(appStateToString());
+        if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) {
+            pw.print("  mNextAppTransitionType="); pw.println(transitTypeToString());
+        }
+        switch (mNextAppTransitionType) {
+            case NEXT_TRANSIT_TYPE_CUSTOM:
+                pw.print("  mNextAppTransitionPackage=");
+                        pw.println(mNextAppTransitionPackage);
+                pw.print("  mNextAppTransitionEnter=0x");
+                        pw.print(Integer.toHexString(mNextAppTransitionEnter));
+                        pw.print(" mNextAppTransitionExit=0x");
+                        pw.println(Integer.toHexString(mNextAppTransitionExit));
+                break;
+            case NEXT_TRANSIT_TYPE_SCALE_UP:
+                pw.print("  mNextAppTransitionStartX="); pw.print(mNextAppTransitionStartX);
+                        pw.print(" mNextAppTransitionStartY=");
+                        pw.println(mNextAppTransitionStartY);
+                pw.print("  mNextAppTransitionStartWidth=");
+                        pw.print(mNextAppTransitionStartWidth);
+                        pw.print(" mNextAppTransitionStartHeight=");
+                        pw.println(mNextAppTransitionStartHeight);
+                break;
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_UP:
+            case NEXT_TRANSIT_TYPE_THUMBNAIL_SCALE_DOWN:
+                pw.print("  mNextAppTransitionThumbnail=");
+                        pw.print(mNextAppTransitionThumbnail);
+                        pw.print(" mNextAppTransitionStartX=");
+                        pw.print(mNextAppTransitionStartX);
+                        pw.print(" mNextAppTransitionStartY=");
+                        pw.println(mNextAppTransitionStartY);
+                pw.print("  mNextAppTransitionScaleUp="); pw.println(mNextAppTransitionScaleUp);
+                break;
+        }
+        if (mNextAppTransitionCallback != null) {
+            pw.print("  mNextAppTransitionCallback=");
+            pw.println(mNextAppTransitionCallback);
+        }
+    }
+
+    public void setCurrentUser(int newUserId) {
+        mCurrentUserId = newUserId;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
new file mode 100644
index 0000000..3cccf1d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -0,0 +1,334 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import android.graphics.Matrix;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class AppWindowAnimator {
+    static final String TAG = "AppWindowAnimator";
+
+    final AppWindowToken mAppToken;
+    final WindowManagerService mService;
+    final WindowAnimator mAnimator;
+
+    boolean animating;
+    Animation animation;
+    boolean hasTransformation;
+    final Transformation transformation = new Transformation();
+
+    // Have we been asked to have this token keep the screen frozen?
+    // Protect with mAnimator.
+    boolean freezingScreen;
+
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int lastFreezeDuration;
+
+    // Offset to the window of all layers in the token, for use by
+    // AppWindowToken animations.
+    int animLayerAdjustment;
+
+    // Propagated from AppWindowToken.allDrawn, to determine when
+    // the state changes.
+    boolean allDrawn;
+
+    // Special surface for thumbnail animation.
+    SurfaceControl thumbnail;
+    int thumbnailTransactionSeq;
+    int thumbnailX;
+    int thumbnailY;
+    int thumbnailLayer;
+    Animation thumbnailAnimation;
+    final Transformation thumbnailTransformation = new Transformation();
+
+    /** WindowStateAnimator from mAppAnimator.allAppWindows as of last performLayout */
+    ArrayList<WindowStateAnimator> mAllAppWinAnimators = new ArrayList<WindowStateAnimator>();
+
+    static final Animation sDummyAnimation = new DummyAnimation();
+
+    public AppWindowAnimator(final AppWindowToken atoken) {
+        mAppToken = atoken;
+        mService = atoken.service;
+        mAnimator = atoken.mAnimator;
+    }
+
+    public void setAnimation(Animation anim, int width, int height) {
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting animation in " + mAppToken
+                + ": " + anim + " wxh=" + width + "x" + height
+                + " isVisible=" + mAppToken.isVisible());
+        animation = anim;
+        animating = false;
+        if (!anim.isInitialized()) {
+            anim.initialize(width, height, width, height);
+        }
+        anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
+        anim.scaleCurrentDuration(mService.mTransitionAnimationScale);
+        int zorder = anim.getZAdjustment();
+        int adj = 0;
+        if (zorder == Animation.ZORDER_TOP) {
+            adj = WindowManagerService.TYPE_LAYER_OFFSET;
+        } else if (zorder == Animation.ZORDER_BOTTOM) {
+            adj = -WindowManagerService.TYPE_LAYER_OFFSET;
+        }
+
+        if (animLayerAdjustment != adj) {
+            animLayerAdjustment = adj;
+            updateLayers();
+        }
+        // Start out animation gone if window is gone, or visible if window is visible.
+        transformation.clear();
+        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+        hasTransformation = true;
+    }
+
+    public void setDummyAnimation() {
+        if (WindowManagerService.localLOGV) Slog.v(TAG, "Setting dummy animation in " + mAppToken
+                + " isVisible=" + mAppToken.isVisible());
+        animation = sDummyAnimation;
+        hasTransformation = true;
+        transformation.clear();
+        transformation.setAlpha(mAppToken.isVisible() ? 1 : 0);
+    }
+
+    public void clearAnimation() {
+        if (animation != null) {
+            animation = null;
+            animating = true;
+        }
+        clearThumbnail();
+        if (mAppToken.deferClearAllDrawn) {
+            mAppToken.allDrawn = false;
+            mAppToken.deferClearAllDrawn = false;
+        }
+    }
+
+    public void clearThumbnail() {
+        if (thumbnail != null) {
+            thumbnail.destroy();
+            thumbnail = null;
+        }
+    }
+
+    void updateLayers() {
+        final int N = mAppToken.allAppWindows.size();
+        final int adj = animLayerAdjustment;
+        thumbnailLayer = -1;
+        for (int i=0; i<N; i++) {
+            final WindowState w = mAppToken.allAppWindows.get(i);
+            final WindowStateAnimator winAnimator = w.mWinAnimator;
+            winAnimator.mAnimLayer = w.mLayer + adj;
+            if (winAnimator.mAnimLayer > thumbnailLayer) {
+                thumbnailLayer = winAnimator.mAnimLayer;
+            }
+            if (WindowManagerService.DEBUG_LAYERS) Slog.v(TAG, "Updating layer " + w + ": "
+                    + winAnimator.mAnimLayer);
+            if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) {
+                mService.setInputMethodAnimLayerAdjustment(adj);
+            }
+            if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) {
+                mService.setWallpaperAnimLayerAdjustmentLocked(adj);
+            }
+        }
+    }
+
+    private void stepThumbnailAnimation(long currentTime) {
+        thumbnailTransformation.clear();
+        thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation);
+        thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY);
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+        final boolean screenAnimation = screenRotationAnimation != null
+                && screenRotationAnimation.isAnimating();
+        if (screenAnimation) {
+            thumbnailTransformation.postCompose(screenRotationAnimation.getEnterTransformation());
+        }
+        // cache often used attributes locally
+        final float tmpFloats[] = mService.mTmpFloats;
+        thumbnailTransformation.getMatrix().getValues(tmpFloats);
+        if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X]
+                + ", " + tmpFloats[Matrix.MTRANS_Y], null);
+        thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
+        if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
+                "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
+                + " layer=" + thumbnailLayer
+                + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
+                + "," + tmpFloats[Matrix.MSKEW_Y]
+                + "][" + tmpFloats[Matrix.MSKEW_X]
+                + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null);
+        thumbnail.setAlpha(thumbnailTransformation.getAlpha());
+        // The thumbnail is layered below the window immediately above this
+        // token's anim layer.
+        thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+        thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
+                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
+    }
+
+    private boolean stepAnimation(long currentTime) {
+        if (animation == null) {
+            return false;
+        }
+        transformation.clear();
+        final boolean more = animation.getTransformation(currentTime, transformation);
+        if (false && WindowManagerService.DEBUG_ANIM) Slog.v(
+            TAG, "Stepped animation in " + mAppToken + ": more=" + more + ", xform=" + transformation);
+        if (!more) {
+            animation = null;
+            clearThumbnail();
+            if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                TAG, "Finished animation in " + mAppToken + " @ " + currentTime);
+        }
+        hasTransformation = more;
+        return more;
+    }
+
+    // This must be called while inside a transaction.
+    boolean stepAnimationLocked(long currentTime) {
+        if (mService.okToDisplay()) {
+            // We will run animations as long as the display isn't frozen.
+
+            if (animation == sDummyAnimation) {
+                // This guy is going to animate, but not yet.  For now count
+                // it as not animating for purposes of scheduling transactions;
+                // when it is really time to animate, this will be set to
+                // a real animation and the next call will execute normally.
+                return false;
+            }
+
+            if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed)
+                    && animation != null) {
+                if (!animating) {
+                    if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                        TAG, "Starting animation in " + mAppToken +
+                        " @ " + currentTime + " scale=" + mService.mTransitionAnimationScale
+                        + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating);
+                    animation.setStartTime(currentTime);
+                    animating = true;
+                    if (thumbnail != null) {
+                        thumbnail.show();
+                        thumbnailAnimation.setStartTime(currentTime);
+                    }
+                }
+                if (stepAnimation(currentTime)) {
+                    // animation isn't over, step any thumbnail and that's
+                    // it for now.
+                    if (thumbnail != null) {
+                        stepThumbnailAnimation(currentTime);
+                    }
+                    return true;
+                }
+            }
+        } else if (animation != null) {
+            // If the display is frozen, and there is a pending animation,
+            // clear it and make sure we run the cleanup code.
+            animating = true;
+            animation = null;
+        }
+
+        hasTransformation = false;
+
+        if (!animating && animation == null) {
+            return false;
+        }
+
+        mAnimator.setAppLayoutChanges(this, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
+                "AppWindowToken");
+
+        clearAnimation();
+        animating = false;
+        if (animLayerAdjustment != 0) {
+            animLayerAdjustment = 0;
+            updateLayers();
+        }
+        if (mService.mInputMethodTarget != null
+                && mService.mInputMethodTarget.mAppToken == mAppToken) {
+            mService.moveInputMethodWindowsIfNeededLocked(true);
+        }
+
+        if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                TAG, "Animation done in " + mAppToken
+                + ": reportedVisible=" + mAppToken.reportedVisible);
+
+        transformation.clear();
+
+        final int N = mAllAppWinAnimators.size();
+        for (int i=0; i<N; i++) {
+            mAllAppWinAnimators.get(i).finishExit();
+        }
+        mAppToken.updateReportedVisibilityLocked();
+
+        return false;
+    }
+
+    boolean showAllWindowsLocked() {
+        boolean isAnimating = false;
+        final int NW = mAllAppWinAnimators.size();
+        for (int i=0; i<NW; i++) {
+            WindowStateAnimator winAnimator = mAllAppWinAnimators.get(i);
+            if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                    "performing show on: " + winAnimator);
+            winAnimator.performShowLocked();
+            isAnimating |= winAnimator.isAnimating();
+        }
+        return isAnimating;
+    }
+
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+        pw.print(prefix); pw.print("mAnimator="); pw.println(mAnimator);
+        pw.print(prefix); pw.print("freezingScreen="); pw.print(freezingScreen);
+                pw.print(" allDrawn="); pw.print(allDrawn);
+                pw.print(" animLayerAdjustment="); pw.println(animLayerAdjustment);
+        if (lastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("lastFreezeDuration=");
+                    TimeUtils.formatDuration(lastFreezeDuration, pw); pw.println();
+        }
+        if (animating || animation != null) {
+            pw.print(prefix); pw.print("animating="); pw.println(animating);
+            pw.print(prefix); pw.print("animation="); pw.println(animation);
+        }
+        if (hasTransformation) {
+            pw.print(prefix); pw.print("XForm: ");
+                    transformation.printShortString(pw);
+                    pw.println();
+        }
+        if (thumbnail != null) {
+            pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
+                    pw.print(" x="); pw.print(thumbnailX);
+                    pw.print(" y="); pw.print(thumbnailY);
+                    pw.print(" layer="); pw.println(thumbnailLayer);
+            pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
+            pw.print(prefix); pw.print("thumbnailTransformation=");
+                    pw.println(thumbnailTransformation.toShortString());
+        }
+        for (int i=0; i<mAllAppWinAnimators.size(); i++) {
+            WindowStateAnimator wanim = mAllAppWinAnimators.get(i);
+            pw.print(prefix); pw.print("App Win Anim #"); pw.print(i);
+                    pw.print(": "); pw.println(wanim);
+        }
+    }
+
+    // This is an animation that does nothing: it just immediately finishes
+    // itself every time it is called.  It is used as a stub animation in cases
+    // where we want to synchronize multiple things that may be animating.
+    static final class DummyAnimation extends Animation {
+        @Override
+        public boolean getTransformation(long currentTime, Transformation outTransformation) {
+            return false;
+        }
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
new file mode 100644
index 0000000..e98014b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.wm.WindowManagerService.H;
+
+import android.content.pm.ActivityInfo;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.IApplicationToken;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class AppTokenList extends ArrayList<AppWindowToken> {
+}
+
+/**
+ * Version of WindowToken that is specifically for a particular application (or
+ * really activity) that is displaying windows.
+ */
+class AppWindowToken extends WindowToken {
+    // Non-null only for application tokens.
+    final IApplicationToken appToken;
+
+    // All of the windows and child windows that are included in this
+    // application token.  Note this list is NOT sorted!
+    final WindowList allAppWindows = new WindowList();
+    final AppWindowAnimator mAppAnimator;
+
+    final WindowAnimator mAnimator;
+
+    int groupId = -1;
+    boolean appFullscreen;
+    int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    boolean layoutConfigChanges;
+    boolean showWhenLocked;
+
+    // The input dispatching timeout for this application token in nanoseconds.
+    long inputDispatchingTimeoutNanos;
+
+    // These are used for determining when all windows associated with
+    // an activity have been drawn, so they can be made visible together
+    // at the same time.
+    // initialize so that it doesn't match mTransactionSequence which is an int.
+    long lastTransactionSequence = Long.MIN_VALUE;
+    int numInterestingWindows;
+    int numDrawnWindows;
+    boolean inPendingTransaction;
+    boolean allDrawn;
+    // Set to true when this app creates a surface while in the middle of an animation. In that
+    // case do not clear allDrawn until the animation completes.
+    boolean deferClearAllDrawn;
+
+    // Is this token going to be hidden in a little while?  If so, it
+    // won't be taken into account for setting the screen orientation.
+    boolean willBeHidden;
+
+    // Is this window's surface needed?  This is almost like hidden, except
+    // it will sometimes be true a little earlier: when the token has
+    // been shown, but is still waiting for its app transition to execute
+    // before making its windows shown.
+    boolean hiddenRequested;
+
+    // Have we told the window clients to hide themselves?
+    boolean clientHidden;
+
+    // Last visibility state we reported to the app token.
+    boolean reportedVisible;
+
+    // Last drawn state we reported to the app token.
+    boolean reportedDrawn;
+
+    // Set to true when the token has been removed from the window mgr.
+    boolean removed;
+
+    // Information about an application starting window if displayed.
+    StartingData startingData;
+    WindowState startingWindow;
+    View startingView;
+    boolean startingDisplayed;
+    boolean startingMoved;
+    boolean firstWindowDrawn;
+
+    // Input application handle used by the input dispatcher.
+    final InputApplicationHandle mInputApplicationHandle;
+
+    AppWindowToken(WindowManagerService _service, IApplicationToken _token) {
+        super(_service, _token.asBinder(),
+                WindowManager.LayoutParams.TYPE_APPLICATION, true);
+        appWindowToken = this;
+        appToken = _token;
+        mInputApplicationHandle = new InputApplicationHandle(this);
+        mAnimator = service.mAnimator;
+        mAppAnimator = new AppWindowAnimator(this);
+    }
+
+    void sendAppVisibilityToClients() {
+        final int N = allAppWindows.size();
+        for (int i=0; i<N; i++) {
+            WindowState win = allAppWindows.get(i);
+            if (win == startingWindow && clientHidden) {
+                // Don't hide the starting window.
+                continue;
+            }
+            try {
+                if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG,
+                        "Setting visibility of " + win + ": " + (!clientHidden));
+                win.mClient.dispatchAppVisibility(!clientHidden);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void updateReportedVisibilityLocked() {
+        if (appToken == null) {
+            return;
+        }
+
+        int numInteresting = 0;
+        int numVisible = 0;
+        int numDrawn = 0;
+        boolean nowGone = true;
+
+        if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG,
+                "Update reported visibility: " + this);
+        final int N = allAppWindows.size();
+        for (int i=0; i<N; i++) {
+            WindowState win = allAppWindows.get(i);
+            if (win == startingWindow || win.mAppFreezing
+                    || win.mViewVisibility != View.VISIBLE
+                    || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                    || win.mDestroying) {
+                continue;
+            }
+            if (WindowManagerService.DEBUG_VISIBILITY) {
+                Slog.v(WindowManagerService.TAG, "Win " + win + ": isDrawn="
+                        + win.isDrawnLw()
+                        + ", isAnimating=" + win.mWinAnimator.isAnimating());
+                if (!win.isDrawnLw()) {
+                    Slog.v(WindowManagerService.TAG, "Not displayed: s=" + win.mWinAnimator.mSurfaceControl
+                            + " pv=" + win.mPolicyVisibility
+                            + " mDrawState=" + win.mWinAnimator.mDrawState
+                            + " ah=" + win.mAttachedHidden
+                            + " th="
+                            + (win.mAppToken != null
+                                    ? win.mAppToken.hiddenRequested : false)
+                            + " a=" + win.mWinAnimator.mAnimating);
+                }
+            }
+            numInteresting++;
+            if (win.isDrawnLw()) {
+                numDrawn++;
+                if (!win.mWinAnimator.isAnimating()) {
+                    numVisible++;
+                }
+                nowGone = false;
+            } else if (win.mWinAnimator.isAnimating()) {
+                nowGone = false;
+            }
+        }
+
+        boolean nowDrawn = numInteresting > 0 && numDrawn >= numInteresting;
+        boolean nowVisible = numInteresting > 0 && numVisible >= numInteresting;
+        if (!nowGone) {
+            // If the app is not yet gone, then it can only become visible/drawn.
+            if (!nowDrawn) {
+                nowDrawn = reportedDrawn;
+            }
+            if (!nowVisible) {
+                nowVisible = reportedVisible;
+            }
+        }
+        if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(WindowManagerService.TAG, "VIS " + this + ": interesting="
+                + numInteresting + " visible=" + numVisible);
+        if (nowDrawn != reportedDrawn) {
+            if (nowDrawn) {
+                Message m = service.mH.obtainMessage(
+                        H.REPORT_APPLICATION_TOKEN_DRAWN, this);
+                service.mH.sendMessage(m);
+            }
+            reportedDrawn = nowDrawn;
+        }
+        if (nowVisible != reportedVisible) {
+            if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(
+                    WindowManagerService.TAG, "Visibility changed in " + this
+                    + ": vis=" + nowVisible);
+            reportedVisible = nowVisible;
+            Message m = service.mH.obtainMessage(
+                    H.REPORT_APPLICATION_TOKEN_WINDOWS,
+                    nowVisible ? 1 : 0,
+                    nowGone ? 1 : 0,
+                    this);
+            service.mH.sendMessage(m);
+        }
+    }
+
+    WindowState findMainWindow() {
+        int j = windows.size();
+        while (j > 0) {
+            j--;
+            WindowState win = windows.get(j);
+            if (win.mAttrs.type == WindowManager.LayoutParams.TYPE_BASE_APPLICATION
+                    || win.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+                return win;
+            }
+        }
+        return null;
+    }
+
+    boolean isVisible() {
+        final int N = allAppWindows.size();
+        for (int i=0; i<N; i++) {
+            WindowState win = allAppWindows.get(i);
+            if (!win.mAppFreezing
+                    && (win.mViewVisibility == View.VISIBLE ||
+                        (win.mWinAnimator.isAnimating() &&
+                                !service.mAppTransition.isTransitionSet()))
+                    && !win.mDestroying && win.isDrawnLw()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    void dump(PrintWriter pw, String prefix) {
+        super.dump(pw, prefix);
+        if (appToken != null) {
+            pw.print(prefix); pw.println("app=true");
+        }
+        if (allAppWindows.size() > 0) {
+            pw.print(prefix); pw.print("allAppWindows="); pw.println(allAppWindows);
+        }
+        pw.print(prefix); pw.print("groupId="); pw.print(groupId);
+                pw.print(" appFullscreen="); pw.print(appFullscreen);
+                pw.print(" requestedOrientation="); pw.println(requestedOrientation);
+        pw.print(prefix); pw.print("hiddenRequested="); pw.print(hiddenRequested);
+                pw.print(" clientHidden="); pw.print(clientHidden);
+                pw.print(" willBeHidden="); pw.print(willBeHidden);
+                pw.print(" reportedDrawn="); pw.print(reportedDrawn);
+                pw.print(" reportedVisible="); pw.println(reportedVisible);
+        if (paused) {
+            pw.print(prefix); pw.print("paused="); pw.println(paused);
+        }
+        if (numInterestingWindows != 0 || numDrawnWindows != 0
+                || allDrawn || mAppAnimator.allDrawn) {
+            pw.print(prefix); pw.print("numInterestingWindows=");
+                    pw.print(numInterestingWindows);
+                    pw.print(" numDrawnWindows="); pw.print(numDrawnWindows);
+                    pw.print(" inPendingTransaction="); pw.print(inPendingTransaction);
+                    pw.print(" allDrawn="); pw.print(allDrawn);
+                    pw.print(" (animator="); pw.print(mAppAnimator.allDrawn);
+                    pw.println(")");
+        }
+        if (inPendingTransaction) {
+            pw.print(prefix); pw.print("inPendingTransaction=");
+                    pw.println(inPendingTransaction);
+        }
+        if (startingData != null || removed || firstWindowDrawn) {
+            pw.print(prefix); pw.print("startingData="); pw.print(startingData);
+                    pw.print(" removed="); pw.print(removed);
+                    pw.print(" firstWindowDrawn="); pw.println(firstWindowDrawn);
+        }
+        if (startingWindow != null || startingView != null
+                || startingDisplayed || startingMoved) {
+            pw.print(prefix); pw.print("startingWindow="); pw.print(startingWindow);
+                    pw.print(" startingView="); pw.print(startingView);
+                    pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+                    pw.print(" startingMoved"); pw.println(startingMoved);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("AppWindowToken{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" token="); sb.append(token); sb.append('}');
+            stringName = sb.toString();
+        }
+        return stringName;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/BlackFrame.java b/services/core/java/com/android/server/wm/BlackFrame.java
new file mode 100644
index 0000000..5aa266d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BlackFrame.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import java.io.PrintWriter;
+
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Four black surfaces put together to make a black frame.
+ */
+public class BlackFrame {
+    class BlackSurface {
+        final int left;
+        final int top;
+        final int layer;
+        final SurfaceControl surface;
+
+        BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
+                throws OutOfResourcesException {
+            left = l;
+            top = t;
+            this.layer = layer;
+            int w = r-l;
+            int h = b-t;
+
+            if (WindowManagerService.DEBUG_SURFACE_TRACE) {
+                surface = new WindowStateAnimator.SurfaceTrace(session, "BlackSurface("
+                        + l + ", " + t + ")",
+                        w, h, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            } else {
+                surface = new SurfaceControl(session, "BlackSurface",
+                        w, h, PixelFormat.OPAQUE, SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            }
+
+            surface.setAlpha(1);
+            surface.setLayerStack(layerStack);
+            surface.setLayer(layer);
+            surface.show();
+            if (WindowManagerService.SHOW_TRANSACTIONS ||
+                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
+                            "  BLACK " + surface + ": CREATE layer=" + layer);
+        }
+
+        void setAlpha(float alpha) {
+            surface.setAlpha(alpha);
+        }
+
+        void setMatrix(Matrix matrix) {
+            mTmpMatrix.setTranslate(left, top);
+            mTmpMatrix.postConcat(matrix);
+            mTmpMatrix.getValues(mTmpFloats);
+            surface.setPosition(mTmpFloats[Matrix.MTRANS_X],
+                    mTmpFloats[Matrix.MTRANS_Y]);
+            surface.setMatrix(
+                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+            if (false) {
+                Slog.i(WindowManagerService.TAG, "Black Surface @ (" + left + "," + top + "): ("
+                        + mTmpFloats[Matrix.MTRANS_X] + ","
+                        + mTmpFloats[Matrix.MTRANS_Y] + ") matrix=["
+                        + mTmpFloats[Matrix.MSCALE_X] + ","
+                        + mTmpFloats[Matrix.MSCALE_Y] + "]["
+                        + mTmpFloats[Matrix.MSKEW_X] + ","
+                        + mTmpFloats[Matrix.MSKEW_Y] + "]");
+            }
+        }
+
+        void clearMatrix() {
+            surface.setMatrix(1, 0, 0, 1);
+        }
+    }
+
+    final Rect mOuterRect;
+    final Rect mInnerRect;
+    final Matrix mTmpMatrix = new Matrix();
+    final float[] mTmpFloats = new float[9];
+    final BlackSurface[] mBlackSurfaces = new BlackSurface[4];
+
+    final boolean mForceDefaultOrientation;
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw);
+                pw.print(" / Inner: "); mInnerRect.printShortString(pw);
+                pw.println();
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            BlackSurface bs = mBlackSurfaces[i];
+            pw.print(prefix); pw.print("#"); pw.print(i);
+                    pw.print(": "); pw.print(bs.surface);
+                    pw.print(" left="); pw.print(bs.left);
+                    pw.print(" top="); pw.println(bs.top);
+        }
+    }
+
+    public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+            boolean forceDefaultOrientation) throws OutOfResourcesException {
+        boolean success = false;
+
+        mForceDefaultOrientation = forceDefaultOrientation;
+
+        mOuterRect = new Rect(outer);
+        mInnerRect = new Rect(inner);
+        try {
+            if (outer.top < inner.top) {
+                mBlackSurfaces[0] = new BlackSurface(session, layer,
+                        outer.left, outer.top, inner.right, inner.top, layerStack);
+            }
+            if (outer.left < inner.left) {
+                mBlackSurfaces[1] = new BlackSurface(session, layer,
+                        outer.left, inner.top, inner.left, outer.bottom, layerStack);
+            }
+            if (outer.bottom > inner.bottom) {
+                mBlackSurfaces[2] = new BlackSurface(session, layer,
+                        inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+            }
+            if (outer.right > inner.right) {
+                mBlackSurfaces[3] = new BlackSurface(session, layer,
+                        inner.right, outer.top, outer.right, inner.bottom, layerStack);
+            }
+            success = true;
+        } finally {
+            if (!success) {
+                kill();
+            }
+        }
+    }
+
+    public void kill() {
+        if (mBlackSurfaces != null) {
+            for (int i=0; i<mBlackSurfaces.length; i++) {
+                if (mBlackSurfaces[i] != null) {
+                    if (WindowManagerService.SHOW_TRANSACTIONS ||
+                            WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(
+                                    WindowManagerService.TAG,
+                                    "  BLACK " + mBlackSurfaces[i].surface + ": DESTROY");
+                    mBlackSurfaces[i].surface.destroy();
+                    mBlackSurfaces[i] = null;
+                }
+            }
+        }
+    }
+
+    public void hide() {
+        if (mBlackSurfaces != null) {
+            for (int i=0; i<mBlackSurfaces.length; i++) {
+                if (mBlackSurfaces[i] != null) {
+                    mBlackSurfaces[i].surface.hide();
+                }
+            }
+        }
+    }
+
+    public void setAlpha(float alpha) {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].setAlpha(alpha);
+            }
+        }
+    }
+
+    public void setMatrix(Matrix matrix) {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].setMatrix(matrix);
+            }
+        }
+    }
+
+    public void clearMatrix() {
+        for (int i=0; i<mBlackSurfaces.length; i++) {
+            if (mBlackSurfaces[i] != null) {
+                mBlackSurfaces[i].clearMatrix();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
new file mode 100644
index 0000000..978d5b7
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -0,0 +1,290 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.SurfaceControl;
+
+import java.io.PrintWriter;
+
+public class DimLayer {
+    private static final String TAG = "DimLayer";
+    private static final boolean DEBUG = false;
+
+    /** Reference to the owner of this object. */
+    final DisplayContent mDisplayContent;
+
+    /** Actual surface that dims */
+    SurfaceControl mDimSurface;
+
+    /** Last value passed to mDimSurface.setAlpha() */
+    float mAlpha = 0;
+
+    /** Last value passed to mDimSurface.setLayer() */
+    int mLayer = -1;
+
+    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
+    Rect mBounds = new Rect();
+
+    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
+    Rect mLastBounds = new Rect();
+
+    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
+    private boolean mShowing = false;
+
+    /** Value of mAlpha when beginning transition to mTargetAlpha */
+    float mStartAlpha = 0;
+
+    /** Final value of mAlpha following transition */
+    float mTargetAlpha = 0;
+
+    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
+    long mStartTime;
+
+    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
+    long mDuration;
+
+    /** Owning stack */
+    final TaskStack mStack;
+
+    DimLayer(WindowManagerService service, TaskStack stack) {
+        mStack = stack;
+        mDisplayContent = stack.getDisplayContent();
+        final int displayId = mDisplayContent.getDisplayId();
+        if (DEBUG) Slog.v(TAG, "Ctor: displayId=" + displayId);
+        SurfaceControl.openTransaction();
+        try {
+            if (WindowManagerService.DEBUG_SURFACE_TRACE) {
+                mDimSurface = new WindowStateAnimator.SurfaceTrace(service.mFxSession,
+                    "DimSurface",
+                    16, 16, PixelFormat.OPAQUE,
+                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            } else {
+                mDimSurface = new SurfaceControl(service.mFxSession, TAG,
+                    16, 16, PixelFormat.OPAQUE,
+                    SurfaceControl.FX_SURFACE_DIM | SurfaceControl.HIDDEN);
+            }
+            if (WindowManagerService.SHOW_TRANSACTIONS ||
+                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(TAG,
+                            "  DIM " + mDimSurface + ": CREATE");
+            mDimSurface.setLayerStack(displayId);
+        } catch (Exception e) {
+            Slog.e(WindowManagerService.TAG, "Exception creating Dim surface", e);
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
+    }
+
+    /** Return true if dim layer is showing */
+    boolean isDimming() {
+        return mTargetAlpha != 0;
+    }
+
+    /** Return true if in a transition period */
+    boolean isAnimating() {
+        return mTargetAlpha != mAlpha;
+    }
+
+    float getTargetAlpha() {
+        return mTargetAlpha;
+    }
+
+    void setLayer(int layer) {
+        if (mLayer != layer) {
+            mLayer = layer;
+            mDimSurface.setLayer(layer);
+        }
+    }
+
+    int getLayer() {
+        return mLayer;
+    }
+
+    private void setAlpha(float alpha) {
+        if (mAlpha != alpha) {
+            if (DEBUG) Slog.v(TAG, "setAlpha alpha=" + alpha);
+            try {
+                mDimSurface.setAlpha(alpha);
+                if (alpha == 0 && mShowing) {
+                    if (DEBUG) Slog.v(TAG, "setAlpha hiding");
+                    mDimSurface.hide();
+                    mShowing = false;
+                } else if (alpha > 0 && !mShowing) {
+                    if (DEBUG) Slog.v(TAG, "setAlpha showing");
+                    mDimSurface.show();
+                    mShowing = true;
+                }
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failure setting alpha immediately", e);
+            }
+            mAlpha = alpha;
+        }
+    }
+
+    void setBounds(Rect bounds) {
+        mBounds.set(bounds);
+    }
+
+    /**
+     * @param duration The time to test.
+     * @return True if the duration would lead to an earlier end to the current animation.
+     */
+    private boolean durationEndsEarlier(long duration) {
+        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
+    }
+
+    /** Jump to the end of the animation.
+     * NOTE: Must be called with Surface transaction open. */
+    void show() {
+        if (isAnimating()) {
+            if (DEBUG) Slog.v(TAG, "show: immediate");
+            show(mLayer, mTargetAlpha, 0);
+        }
+    }
+
+    /**
+     * Begin an animation to a new dim value.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @param layer The layer to set the surface to.
+     * @param alpha The dim value to end at.
+     * @param duration How long to take to get there in milliseconds.
+     */
+    void show(int layer, float alpha, long duration) {
+        if (DEBUG) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
+                + " duration=" + duration);
+        if (mDimSurface == null) {
+            Slog.e(TAG, "show: no Surface");
+            // Make sure isAnimating() returns false.
+            mTargetAlpha = mAlpha = 0;
+            return;
+        }
+
+        final int dw, dh;
+        final float xPos, yPos;
+        if (!mStack.isFullscreen()) {
+            dw = mBounds.width();
+            dh = mBounds.height();
+            xPos = mBounds.left;
+            yPos = mBounds.right;
+        } else {
+            // Set surface size to screen size.
+            final DisplayInfo info = mDisplayContent.getDisplayInfo();
+            // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose a
+            // corner.
+            dw = (int) (info.logicalWidth * 1.5);
+            dh = (int) (info.logicalHeight * 1.5);
+            // back off position so 1/4 of Surface is before and 1/4 is after.
+            xPos = -1 * dw / 6;
+            yPos = -1 * dh / 6;
+        }
+
+        if (!mLastBounds.equals(mBounds) || mLayer != layer) {
+            try {
+                mDimSurface.setPosition(xPos, yPos);
+                mDimSurface.setSize(dw, dh);
+                mDimSurface.setLayer(layer);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Failure setting size or layer", e);
+            }
+            mLastBounds.set(mBounds);
+            mLayer = layer;
+        }
+
+        long curTime = SystemClock.uptimeMillis();
+        final boolean animating = isAnimating();
+        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
+                || (!animating && mAlpha != alpha)) {
+            if (duration <= 0) {
+                // No animation required, just set values.
+                setAlpha(alpha);
+            } else {
+                // Start or continue animation with new parameters.
+                mStartAlpha = mAlpha;
+                mStartTime = curTime;
+                mDuration = duration;
+            }
+        }
+        if (DEBUG) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime=" + mStartTime);
+        mTargetAlpha = alpha;
+    }
+
+    /** Immediate hide.
+     * NOTE: Must be called with Surface transaction open. */
+    void hide() {
+        if (mShowing) {
+            if (DEBUG) Slog.v(TAG, "hide: immediate");
+            hide(0);
+        }
+    }
+
+    /**
+     * Gradually fade to transparent.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @param duration Time to fade in milliseconds.
+     */
+    void hide(long duration) {
+        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
+            if (DEBUG) Slog.v(TAG, "hide: duration=" + duration);
+            show(mLayer, 0, duration);
+        }
+    }
+
+    /**
+     * Advance the dimming per the last #show(int, float, long) call.
+     * NOTE: Must be called with Surface transaction open.
+     *
+     * @return True if animation is still required after this step.
+     */
+    boolean stepAnimation() {
+        if (mDimSurface == null) {
+            Slog.e(TAG, "stepAnimation: null Surface");
+            // Ensure that isAnimating() returns false;
+            mTargetAlpha = mAlpha = 0;
+            return false;
+        }
+
+        if (isAnimating()) {
+            final long curTime = SystemClock.uptimeMillis();
+            final float alphaDelta = mTargetAlpha - mStartAlpha;
+            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
+            if (alphaDelta > 0 && alpha > mTargetAlpha ||
+                    alphaDelta < 0 && alpha < mTargetAlpha) {
+                // Don't exceed limits.
+                alpha = mTargetAlpha;
+            }
+            if (DEBUG) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
+            setAlpha(alpha);
+        }
+
+        return isAnimating();
+    }
+
+    /** Cleanup */
+    void destroySurface() {
+        if (DEBUG) Slog.v(TAG, "destroySurface.");
+        if (mDimSurface != null) {
+            mDimSurface.destroy();
+            mDimSurface = null;
+        }
+    }
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
+                pw.print(" mLayer="); pw.print(mLayer);
+                pw.print(" mAlpha="); pw.println(mAlpha);
+        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
+                pw.print(" mBounds="); pw.println(mBounds.toShortString());
+        pw.print(prefix); pw.print("Last animation: ");
+                pw.print(" mDuration="); pw.print(mDuration);
+                pw.print(" mStartTime="); pw.print(mStartTime);
+                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
+        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
+                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
new file mode 100644
index 0000000..4064377
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerService.TAG;
+
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.EventLog;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface;
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class DisplayContentList extends ArrayList<DisplayContent> {
+}
+
+/**
+ * Utility class for keeping track of the WindowStates and other pertinent contents of a
+ * particular Display.
+ *
+ * IMPORTANT: No method from this class should ever be used without holding
+ * WindowManagerService.mWindowMap.
+ */
+class DisplayContent {
+
+    /** Unique identifier of this stack. */
+    private final int mDisplayId;
+
+    /** Z-ordered (bottom-most first) list of all Window objects. Assigned to an element
+     * from mDisplayWindows; */
+    private WindowList mWindows = new WindowList();
+
+    // This protects the following display size properties, so that
+    // getDisplaySize() doesn't need to acquire the global lock.  This is
+    // needed because the window manager sometimes needs to use ActivityThread
+    // while it has its global state locked (for example to load animation
+    // resources), but the ActivityThread also needs get the current display
+    // size sometimes when it has its package lock held.
+    //
+    // These will only be modified with both mWindowMap and mDisplaySizeLock
+    // held (in that order) so the window manager doesn't need to acquire this
+    // lock when needing these values in its normal operation.
+    final Object mDisplaySizeLock = new Object();
+    int mInitialDisplayWidth = 0;
+    int mInitialDisplayHeight = 0;
+    int mInitialDisplayDensity = 0;
+    int mBaseDisplayWidth = 0;
+    int mBaseDisplayHeight = 0;
+    int mBaseDisplayDensity = 0;
+    private final DisplayInfo mDisplayInfo = new DisplayInfo();
+    private final Display mDisplay;
+
+    Rect mBaseDisplayRect = new Rect();
+    Rect mContentRect = new Rect();
+
+    // Accessed directly by all users.
+    boolean layoutNeeded;
+    int pendingLayoutChanges;
+    final boolean isDefaultDisplay;
+
+    /**
+     * Window tokens that are in the process of exiting, but still
+     * on screen for animations.
+     */
+    final ArrayList<WindowToken> mExitingTokens = new ArrayList<WindowToken>();
+
+    /**
+     * Application tokens that are in the process of exiting, but still
+     * on screen for animations.
+     */
+    final AppTokenList mExitingAppTokens = new AppTokenList();
+
+    /** Array containing all TaskStacks on this display.  Array
+     * is stored in display order with the current bottom stack at 0. */
+    private ArrayList<TaskStack> mStacks = new ArrayList<TaskStack>();
+
+    /** A special TaskStack with id==HOME_STACK_ID that moves to the bottom whenever any TaskStack
+     * (except a future lockscreen TaskStack) moves to the top. */
+    private TaskStack mHomeStack = null;
+
+    /** Detect user tapping outside of current focused stack bounds .*/
+    StackTapPointerEventListener mTapDetector;
+
+    /** Detect user tapping outside of current focused stack bounds .*/
+    Region mTouchExcludeRegion = new Region();
+
+    /** Save allocating when retrieving tasks */
+    private ArrayList<Task> mTaskHistory = new ArrayList<Task>();
+
+    /** Save allocating when calculating rects */
+    Rect mTmpRect = new Rect();
+
+    final WindowManagerService mService;
+
+    /**
+     * @param display May not be null.
+     * @param service TODO(cmautner):
+     */
+    DisplayContent(Display display, WindowManagerService service) {
+        mDisplay = display;
+        mDisplayId = display.getDisplayId();
+        display.getDisplayInfo(mDisplayInfo);
+        isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
+        mService = service;
+    }
+
+    int getDisplayId() {
+        return mDisplayId;
+    }
+
+    WindowList getWindowList() {
+        return mWindows;
+    }
+
+    Display getDisplay() {
+        return mDisplay;
+    }
+
+    DisplayInfo getDisplayInfo() {
+        return mDisplayInfo;
+    }
+
+    /**
+     * Returns true if the specified UID has access to this display.
+     */
+    public boolean hasAccess(int uid) {
+        return mDisplay.hasAccess(uid);
+    }
+
+    public boolean isPrivate() {
+        return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0;
+    }
+
+    /**
+     * Retrieve the tasks on this display in stack order from the bottommost TaskStack up.
+     * @return All the Tasks, in order, on this display.
+     */
+    ArrayList<Task> getTasks() {
+        return mTaskHistory;
+    }
+
+    void addTask(Task task, boolean toTop) {
+        mTaskHistory.remove(task);
+
+        final int userId = task.mUserId;
+        int taskNdx;
+        final int numTasks = mTaskHistory.size();
+        if (toTop) {
+            for (taskNdx = numTasks - 1; taskNdx >= 0; --taskNdx) {
+                if (mTaskHistory.get(taskNdx).mUserId == userId) {
+                    break;
+                }
+            }
+            ++taskNdx;
+        } else {
+            for (taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+                if (mTaskHistory.get(taskNdx).mUserId == userId) {
+                    break;
+                }
+            }
+        }
+
+        mTaskHistory.add(taskNdx, task);
+        EventLog.writeEvent(EventLogTags.WM_TASK_MOVED, task.taskId, toTop ? 1 : 0, taskNdx);
+    }
+
+    void removeTask(Task task) {
+        mTaskHistory.remove(task);
+    }
+
+    TaskStack getHomeStack() {
+        return mHomeStack;
+    }
+
+    void updateDisplayInfo() {
+        // Save old size.
+        int oldWidth = mDisplayInfo.logicalWidth;
+        int oldHeight = mDisplayInfo.logicalHeight;
+        mDisplay.getDisplayInfo(mDisplayInfo);
+
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mStacks.get(i);
+            if (!stack.isFullscreen()) {
+                stack.resizeBounds(oldWidth, oldHeight, mDisplayInfo.logicalWidth,
+                        mDisplayInfo.logicalHeight);
+            }
+        }
+    }
+
+    void getLogicalDisplayRect(Rect out) {
+        updateDisplayInfo();
+        // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
+        final int orientation = mDisplayInfo.rotation;
+        boolean rotated = (orientation == Surface.ROTATION_90
+                || orientation == Surface.ROTATION_270);
+        final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+        final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+        int width = mDisplayInfo.logicalWidth;
+        int left = (physWidth - width) / 2;
+        int height = mDisplayInfo.logicalHeight;
+        int top = (physHeight - height) / 2;
+        out.set(left, top, left + width, top + height);
+    }
+
+    /** @return The number of tokens in all of the Tasks on this display. */
+    int numTokens() {
+        int count = 0;
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            count += mTaskHistory.get(taskNdx).mAppTokens.size();
+        }
+        return count;
+    }
+
+    /** Refer to {@link WindowManagerService#createStack(int, int)} */
+    TaskStack createStack(int stackId) {
+        if (DEBUG_STACK) Slog.d(TAG, "createStack: stackId=" + stackId);
+        TaskStack newStack = new TaskStack(mService, stackId, this);
+        if (stackId == HOME_STACK_ID) {
+            if (mHomeStack != null) {
+                throw new IllegalArgumentException("createStack: HOME_STACK_ID (0) not first.");
+            }
+            mHomeStack = newStack;
+        }
+        mStacks.add(newStack);
+        layoutNeeded = true;
+        return newStack;
+    }
+
+    void moveStack(TaskStack stack, boolean toTop) {
+        mStacks.remove(stack);
+        mStacks.add(toTop ? mStacks.size() : 0, stack);
+    }
+
+    TaskStack removeStack(TaskStack stack) {
+        mStacks.remove(stack);
+        if (!mStacks.isEmpty()) {
+            return mStacks.get(mStacks.size() - 1);
+        }
+        return null;
+    }
+
+    /**
+     * Propagate the new bounds to all child stacks.
+     * @param contentRect The bounds to apply at the top level.
+     */
+    void resize(Rect contentRect) {
+        mContentRect.set(contentRect);
+    }
+
+    boolean getStackBounds(int stackId, Rect bounds) {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mStacks.get(stackNdx);
+            if (stackId == stack.mStackId) {
+                bounds.set(stack.mBounds);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    int stackIdFromPoint(int x, int y) {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mStacks.get(stackNdx);
+            stack.getBounds(mTmpRect);
+            if (mTmpRect.contains(x, y)) {
+                return stack.mStackId;
+            }
+        }
+        return -1;
+    }
+
+    void setTouchExcludeRegion(TaskStack focusedStack) {
+        mTouchExcludeRegion.set(mBaseDisplayRect);
+        WindowList windows = getWindowList();
+        for (int i = windows.size() - 1; i >= 0; --i) {
+            final WindowState win = windows.get(i);
+            final TaskStack stack = win.getStack();
+            if (win.isVisibleLw() && stack != null && stack != focusedStack) {
+                mTmpRect.set(win.mVisibleFrame);
+                mTmpRect.intersect(win.mVisibleInsets);
+                mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE);
+            }
+        }
+    }
+
+    void switchUserStacks(int oldUserId, int newUserId) {
+        final WindowList windows = getWindowList();
+        for (int i = 0; i < windows.size(); i++) {
+            final WindowState win = windows.get(i);
+            if (win.isHiddenFromUserLocked()) {
+                if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing " + newUserId + " hiding "
+                        + win + ", attrs=" + win.mAttrs.type + ", belonging to "
+                        + win.mOwnerUid);
+                win.hideLw(false);
+            }
+        }
+
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).switchUser(newUserId);
+        }
+    }
+
+    void resetAnimationBackgroundAnimator() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).resetAnimationBackgroundAnimator();
+        }
+    }
+
+    boolean animateDimLayers() {
+        boolean result = false;
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            result |= mStacks.get(stackNdx).animateDimLayers();
+        }
+        return result;
+    }
+
+    void resetDimming() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).resetDimmingTag();
+        }
+    }
+
+    boolean isDimming() {
+        boolean result = false;
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            result |= mStacks.get(stackNdx).isDimming();
+        }
+        return result;
+    }
+
+    void stopDimmingIfNeeded() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).stopDimmingIfNeeded();
+        }
+    }
+
+    void close() {
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            mStacks.get(stackNdx).close();
+        }
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId);
+        final String subPrefix = "  " + prefix;
+        pw.print(subPrefix); pw.print("init="); pw.print(mInitialDisplayWidth); pw.print("x");
+            pw.print(mInitialDisplayHeight); pw.print(" "); pw.print(mInitialDisplayDensity);
+            pw.print("dpi");
+            if (mInitialDisplayWidth != mBaseDisplayWidth
+                    || mInitialDisplayHeight != mBaseDisplayHeight
+                    || mInitialDisplayDensity != mBaseDisplayDensity) {
+                pw.print(" base=");
+                pw.print(mBaseDisplayWidth); pw.print("x"); pw.print(mBaseDisplayHeight);
+                pw.print(" "); pw.print(mBaseDisplayDensity); pw.print("dpi");
+            }
+            pw.print(" cur=");
+            pw.print(mDisplayInfo.logicalWidth);
+            pw.print("x"); pw.print(mDisplayInfo.logicalHeight);
+            pw.print(" app=");
+            pw.print(mDisplayInfo.appWidth);
+            pw.print("x"); pw.print(mDisplayInfo.appHeight);
+            pw.print(" rng="); pw.print(mDisplayInfo.smallestNominalAppWidth);
+            pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight);
+            pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
+            pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
+            pw.print(subPrefix); pw.print("layoutNeeded="); pw.println(layoutNeeded);
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final TaskStack stack = mStacks.get(stackNdx);
+            pw.print(prefix); pw.print("mStacks[" + stackNdx + "]"); pw.println(stack.mStackId);
+            stack.dump(prefix + "  ", pw);
+        }
+        int ndx = numTokens();
+        if (ndx > 0) {
+            pw.println();
+            pw.println("  Application tokens in Z order:");
+            getTasks();
+            for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+                AppTokenList tokens = mTaskHistory.get(taskNdx).mAppTokens;
+                for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
+                    final AppWindowToken wtoken = tokens.get(tokenNdx);
+                    pw.print("  App #"); pw.print(ndx--);
+                            pw.print(' '); pw.print(wtoken); pw.println(":");
+                    wtoken.dump(pw, "    ");
+                }
+            }
+        }
+        if (mExitingTokens.size() > 0) {
+            pw.println();
+            pw.println("  Exiting tokens:");
+            for (int i=mExitingTokens.size()-1; i>=0; i--) {
+                WindowToken token = mExitingTokens.get(i);
+                pw.print("  Exiting #"); pw.print(i);
+                pw.print(' '); pw.print(token);
+                pw.println(':');
+                token.dump(pw, "    ");
+            }
+        }
+        if (mExitingAppTokens.size() > 0) {
+            pw.println();
+            pw.println("  Exiting application tokens:");
+            for (int i=mExitingAppTokens.size()-1; i>=0; i--) {
+                WindowToken token = mExitingAppTokens.get(i);
+                pw.print("  Exiting App #"); pw.print(i);
+                  pw.print(' '); pw.print(token);
+                  pw.println(':');
+                  token.dump(pw, "    ");
+            }
+        }
+        pw.println();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayMagnifier.java b/services/core/java/com/android/server/wm/DisplayMagnifier.java
new file mode 100644
index 0000000..382d7b4
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayMagnifier.java
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.app.Service;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Pools.SimplePool;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.IMagnificationCallbacks;
+import android.view.MagnificationSpec;
+import android.view.Surface;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+import com.android.internal.R;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * This class is a part of the window manager and encapsulates the
+ * functionality related to display magnification.
+ */
+final class DisplayMagnifier {
+    private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName();
+
+    private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
+    private static final boolean DEBUG_ROTATION = false;
+    private static final boolean DEBUG_LAYERS = false;
+    private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
+    private static final boolean DEBUG_VIEWPORT_WINDOW = false;
+
+    private final Rect mTempRect1 = new Rect();
+    private final Rect mTempRect2 = new Rect();
+
+    private final Region mTempRegion1 = new Region();
+    private final Region mTempRegion2 = new Region();
+    private final Region mTempRegion3 = new Region();
+    private final Region mTempRegion4 = new Region();
+
+    private final Context mContext;
+    private final WindowManagerService mWindowManagerService;
+    private final MagnifiedViewport mMagnifedViewport;
+    private final Handler mHandler;
+
+    private final IMagnificationCallbacks mCallbacks;
+
+    private final long mLongAnimationDuration;
+
+    public DisplayMagnifier(WindowManagerService windowManagerService,
+            IMagnificationCallbacks callbacks) {
+        mContext = windowManagerService.mContext;
+        mWindowManagerService = windowManagerService;
+        mCallbacks = callbacks;
+        mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+        mMagnifedViewport = new MagnifiedViewport();
+        mLongAnimationDuration = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_longAnimTime);
+    }
+
+    public void setMagnificationSpecLocked(MagnificationSpec spec) {
+        mMagnifedViewport.updateMagnificationSpecLocked(spec);
+        mMagnifedViewport.recomputeBoundsLocked();
+        mWindowManagerService.scheduleAnimationLocked();
+    }
+
+    public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
+        if (DEBUG_RECTANGLE_REQUESTED) {
+            Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
+        }
+        if (!mMagnifedViewport.isMagnifyingLocked()) {
+            return;
+        }
+        Rect magnifiedRegionBounds = mTempRect2;
+        mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
+        if (magnifiedRegionBounds.contains(rectangle)) {
+            return;
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.argi1 = rectangle.left;
+        args.argi2 = rectangle.top;
+        args.argi3 = rectangle.right;
+        args.argi4 = rectangle.bottom;
+        mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
+                args).sendToTarget();
+    }
+
+    public void onWindowLayersChangedLocked() {
+        if (DEBUG_LAYERS) {
+            Slog.i(LOG_TAG, "Layers changed.");
+        }
+        mMagnifedViewport.recomputeBoundsLocked();
+        mWindowManagerService.scheduleAnimationLocked();
+    }
+
+    public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
+        if (DEBUG_ROTATION) {
+            Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
+                    + " displayId: " + displayContent.getDisplayId());
+        }
+        mMagnifedViewport.onRotationChangedLocked();
+        mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
+    }
+
+    public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
+        if (DEBUG_WINDOW_TRANSITIONS) {
+            Slog.i(LOG_TAG, "Window transition: "
+                    + AppTransition.appTransitionToString(transition)
+                    + " displayId: " + windowState.getDisplayId());
+        }
+        final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+        if (magnifying) {
+            switch (transition) {
+                case AppTransition.TRANSIT_ACTIVITY_OPEN:
+                case AppTransition.TRANSIT_TASK_OPEN:
+                case AppTransition.TRANSIT_TASK_TO_FRONT:
+                case AppTransition.TRANSIT_WALLPAPER_OPEN:
+                case AppTransition.TRANSIT_WALLPAPER_CLOSE:
+                case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
+                    mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
+                }
+            }
+        }
+    }
+
+    public void onWindowTransitionLocked(WindowState windowState, int transition) {
+        if (DEBUG_WINDOW_TRANSITIONS) {
+            Slog.i(LOG_TAG, "Window transition: "
+                    + AppTransition.appTransitionToString(transition)
+                    + " displayId: " + windowState.getDisplayId());
+        }
+        final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
+        final int type = windowState.mAttrs.type;
+        switch (transition) {
+            case WindowManagerPolicy.TRANSIT_ENTER:
+            case WindowManagerPolicy.TRANSIT_SHOW: {
+                if (!magnifying) {
+                    break;
+                }
+                switch (type) {
+                    case WindowManager.LayoutParams.TYPE_APPLICATION:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
+                    case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
+                    case WindowManager.LayoutParams.TYPE_PHONE:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
+                    case WindowManager.LayoutParams.TYPE_TOAST:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
+                    case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
+                    case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
+                    case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
+                    case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+                    case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
+                        Rect magnifiedRegionBounds = mTempRect2;
+                        mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
+                                magnifiedRegionBounds);
+                        Rect touchableRegionBounds = mTempRect1;
+                        windowState.getTouchableRegion(mTempRegion1);
+                        mTempRegion1.getBounds(touchableRegionBounds);
+                        if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
+                            try {
+                                mCallbacks.onRectangleOnScreenRequested(
+                                        touchableRegionBounds.left,
+                                        touchableRegionBounds.top,
+                                        touchableRegionBounds.right,
+                                        touchableRegionBounds.bottom);
+                            } catch (RemoteException re) {
+                                /* ignore */
+                            }
+                        }
+                    } break;
+                } break;
+            }
+        }
+    }
+
+    public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
+        MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
+        if (spec != null && !spec.isNop()) {
+            WindowManagerPolicy policy = mWindowManagerService.mPolicy;
+            final int windowType = windowState.mAttrs.type;
+            if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null
+                    && !policy.canMagnifyWindow(windowType)) {
+                return null;
+            }
+            if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
+                return null;
+            }
+        }
+        return spec;
+    }
+
+    public void destroyLocked() {
+        mMagnifedViewport.destroyWindow();
+    }
+
+    /** NOTE: This has to be called within a surface transaction. */
+    public void drawMagnifiedRegionBorderIfNeededLocked() {
+        mMagnifedViewport.drawWindowIfNeededLocked();
+    }
+
+    private final class MagnifiedViewport {
+
+        private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
+
+        private final SparseArray<WindowStateInfo> mTempWindowStateInfos =
+                new SparseArray<WindowStateInfo>();
+
+        private final float[] mTempFloats = new float[9];
+
+        private final RectF mTempRectF = new RectF();
+
+        private final Point mTempPoint = new Point();
+
+        private final Matrix mTempMatrix = new Matrix();
+
+        private final Region mMagnifiedBounds = new Region();
+        private final Region mOldMagnifiedBounds = new Region();
+
+        private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
+
+        private final WindowManager mWindowManager;
+
+        private final int mBorderWidth;
+        private final int mHalfBorderWidth;
+
+        private final ViewportWindow mWindow;
+
+        private boolean mFullRedrawNeeded;
+
+        public MagnifiedViewport() {
+            mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
+            mBorderWidth = (int) TypedValue.applyDimension(
+                    TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
+                            mContext.getResources().getDisplayMetrics());
+            mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
+            mWindow = new ViewportWindow(mContext);
+            recomputeBoundsLocked();
+        }
+
+        public void updateMagnificationSpecLocked(MagnificationSpec spec) {
+            if (spec != null) {
+                mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
+            } else {
+                mMagnificationSpec.clear();
+            }
+            // If this message is pending we are in a rotation animation and do not want
+            // to show the border. We will do so when the pending message is handled.
+            if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
+                setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
+            }
+        }
+
+        public void recomputeBoundsLocked() {
+            mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+            final int screenWidth = mTempPoint.x;
+            final int screenHeight = mTempPoint.y;
+
+            Region magnifiedBounds = mMagnifiedBounds;
+            magnifiedBounds.set(0, 0, 0, 0);
+
+            Region availableBounds = mTempRegion1;
+            availableBounds.set(0, 0, screenWidth, screenHeight);
+
+            Region nonMagnifiedBounds = mTempRegion4;
+            nonMagnifiedBounds.set(0,  0,  0,  0);
+
+            SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos;
+            visibleWindows.clear();
+            getWindowsOnScreenLocked(visibleWindows);
+
+            final int visibleWindowCount = visibleWindows.size();
+            for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                WindowStateInfo info = visibleWindows.valueAt(i);
+                if (info.mWindowState.mAttrs.type == WindowManager
+                        .LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+                    continue;
+                }
+
+                Region windowBounds = mTempRegion2;
+                Matrix matrix = mTempMatrix;
+                populateTransformationMatrix(info.mWindowState, matrix);
+                RectF windowFrame = mTempRectF;
+
+                if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) {
+                    windowFrame.set(info.mWindowState.mFrame);
+                    windowFrame.offset(-windowFrame.left, -windowFrame.top);
+                    matrix.mapRect(windowFrame);
+                    windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                            (int) windowFrame.right, (int) windowFrame.bottom);
+                    magnifiedBounds.op(windowBounds, Region.Op.UNION);
+                    magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
+                } else {
+                    windowFrame.set(info.mTouchableRegion);
+                    windowFrame.offset(-info.mWindowState.mFrame.left,
+                            -info.mWindowState.mFrame.top);
+                    matrix.mapRect(windowFrame);
+                    windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
+                            (int) windowFrame.right, (int) windowFrame.bottom);
+                    nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
+                    windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
+                    availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
+                }
+
+                Region accountedBounds = mTempRegion2;
+                accountedBounds.set(magnifiedBounds);
+                accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
+                accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
+
+                if (accountedBounds.isRect()) {
+                    Rect accountedFrame = mTempRect1;
+                    accountedBounds.getBounds(accountedFrame);
+                    if (accountedFrame.width() == screenWidth
+                            && accountedFrame.height() == screenHeight) {
+                        break;
+                    }
+                }
+            }
+
+            for (int i = visibleWindowCount - 1; i >= 0; i--) {
+                WindowStateInfo info = visibleWindows.valueAt(i);
+                info.recycle();
+                visibleWindows.removeAt(i);
+            }
+
+            magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth,
+                    screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth,
+                    Region.Op.INTERSECT);
+
+            if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
+                Region bounds = Region.obtain();
+                bounds.set(magnifiedBounds);
+                mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
+                        bounds).sendToTarget();
+
+                mWindow.setBounds(magnifiedBounds);
+                Rect dirtyRect = mTempRect1;
+                if (mFullRedrawNeeded) {
+                    mFullRedrawNeeded = false;
+                    dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth,
+                            screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth);
+                    mWindow.invalidate(dirtyRect);
+                } else {
+                    Region dirtyRegion = mTempRegion3;
+                    dirtyRegion.set(magnifiedBounds);
+                    dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
+                    dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
+                    dirtyRegion.getBounds(dirtyRect);
+                    mWindow.invalidate(dirtyRect);
+                }
+
+                mOldMagnifiedBounds.set(magnifiedBounds);
+            }
+        }
+
+        private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) {
+            mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
+            mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
+            mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
+            mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
+            mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
+            mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
+            mTempFloats[Matrix.MPERSP_0] = 0;
+            mTempFloats[Matrix.MPERSP_1] = 0;
+            mTempFloats[Matrix.MPERSP_2] = 1;
+            outMatrix.setValues(mTempFloats);
+        }
+
+        private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) {
+            DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked();
+            WindowList windowList = displayContent.getWindowList();
+            final int windowCount = windowList.size();
+            for (int i = 0; i < windowCount; i++) {
+                WindowState windowState = windowList.get(i);
+                if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager
+                        .LayoutParams.TYPE_UNIVERSE_BACKGROUND)
+                        && !windowState.mWinAnimator.mEnterAnimationPending) {
+                    outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState));
+                }
+            }
+        }
+
+        public void onRotationChangedLocked() {
+            // If we are magnifying, hide the magnified border window immediately so
+            // the user does not see strange artifacts during rotation. The screenshot
+            // used for rotation has already the border. After the rotation is complete
+            // we will show the border.
+            if (isMagnifyingLocked()) {
+                setMagnifiedRegionBorderShownLocked(false, false);
+                final long delay = (long) (mLongAnimationDuration
+                        * mWindowManagerService.mWindowAnimationScale);
+                Message message = mHandler.obtainMessage(
+                        MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
+                mHandler.sendMessageDelayed(message, delay);
+            }
+            recomputeBoundsLocked();
+            mWindow.updateSize();
+        }
+
+        public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
+            if (shown) {
+                mFullRedrawNeeded = true;
+                mOldMagnifiedBounds.set(0,  0,  0,  0);
+            }
+            mWindow.setShown(shown, animate);
+        }
+
+        public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
+            MagnificationSpec spec = mMagnificationSpec;
+            mMagnifiedBounds.getBounds(rect);
+            rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
+            rect.scale(1.0f / spec.scale);
+        }
+
+        public boolean isMagnifyingLocked() {
+            return mMagnificationSpec.scale > 1.0f;
+        }
+
+        public MagnificationSpec getMagnificationSpecLocked() {
+            return mMagnificationSpec;
+        }
+
+        /** NOTE: This has to be called within a surface transaction. */
+        public void drawWindowIfNeededLocked() {
+            recomputeBoundsLocked();
+            mWindow.drawIfNeeded();
+        }
+
+        public void destroyWindow() {
+            mWindow.releaseSurface();
+        }
+
+        private final class ViewportWindow {
+            private static final String SURFACE_TITLE = "Magnification Overlay";
+
+            private static final String PROPERTY_NAME_ALPHA = "alpha";
+
+            private static final int MIN_ALPHA = 0;
+            private static final int MAX_ALPHA = 255;
+
+            private final Region mBounds = new Region();
+            private final Rect mDirtyRect = new Rect();
+            private final Paint mPaint = new Paint();
+
+            private final ValueAnimator mShowHideFrameAnimator;
+            private final SurfaceControl mSurfaceControl;
+            private final Surface mSurface = new Surface();
+
+            private boolean mShown;
+            private int mAlpha;
+
+            private boolean mInvalidated;
+
+            public ViewportWindow(Context context) {
+                SurfaceControl surfaceControl = null;
+                try {
+                    mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                    surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, SURFACE_TITLE,
+                            mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+                } catch (OutOfResourcesException oore) {
+                    /* ignore */
+                }
+                mSurfaceControl = surfaceControl;
+                mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack());
+                mSurfaceControl.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw(
+                        WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
+                        * WindowManagerService.TYPE_LAYER_MULTIPLIER);
+                mSurfaceControl.setPosition(0, 0);
+                mSurface.copyFrom(mSurfaceControl);
+
+                TypedValue typedValue = new TypedValue();
+                context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
+                        typedValue, true);
+                final int borderColor = context.getResources().getColor(typedValue.resourceId);
+
+                mPaint.setStyle(Paint.Style.STROKE);
+                mPaint.setStrokeWidth(mBorderWidth);
+                mPaint.setColor(borderColor);
+
+                Interpolator interpolator = new DecelerateInterpolator(2.5f);
+                final long longAnimationDuration = context.getResources().getInteger(
+                        com.android.internal.R.integer.config_longAnimTime);
+
+                mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
+                        MIN_ALPHA, MAX_ALPHA);
+                mShowHideFrameAnimator.setInterpolator(interpolator);
+                mShowHideFrameAnimator.setDuration(longAnimationDuration);
+                mInvalidated = true;
+            }
+
+            public void setShown(boolean shown, boolean animate) {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    if (mShown == shown) {
+                        return;
+                    }
+                    mShown = shown;
+                    if (animate) {
+                        if (mShowHideFrameAnimator.isRunning()) {
+                            mShowHideFrameAnimator.reverse();
+                        } else {
+                            if (shown) {
+                                mShowHideFrameAnimator.start();
+                            } else {
+                                mShowHideFrameAnimator.reverse();
+                            }
+                        }
+                    } else {
+                        mShowHideFrameAnimator.cancel();
+                        if (shown) {
+                            setAlpha(MAX_ALPHA);
+                        } else {
+                            setAlpha(MIN_ALPHA);
+                        }
+                    }
+                    if (DEBUG_VIEWPORT_WINDOW) {
+                        Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
+                    }
+                }
+            }
+
+            @SuppressWarnings("unused")
+            // Called reflectively from an animator.
+            public int getAlpha() {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    return mAlpha;
+                }
+            }
+
+            public void setAlpha(int alpha) {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    if (mAlpha == alpha) {
+                        return;
+                    }
+                    mAlpha = alpha;
+                    invalidate(null);
+                    if (DEBUG_VIEWPORT_WINDOW) {
+                        Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
+                    }
+                }
+            }
+
+            public void setBounds(Region bounds) {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    if (mBounds.equals(bounds)) {
+                        return;
+                    }
+                    mBounds.set(bounds);
+                    invalidate(mDirtyRect);
+                    if (DEBUG_VIEWPORT_WINDOW) {
+                        Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
+                    }
+                }
+            }
+
+            public void updateSize() {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
+                    mSurfaceControl.setSize(mTempPoint.x, mTempPoint.y);
+                    invalidate(mDirtyRect);
+                }
+            }
+
+            public void invalidate(Rect dirtyRect) {
+                if (dirtyRect != null) {
+                    mDirtyRect.set(dirtyRect);
+                } else {
+                    mDirtyRect.setEmpty();
+                }
+                mInvalidated = true;
+                mWindowManagerService.scheduleAnimationLocked();
+            }
+
+            /** NOTE: This has to be called within a surface transaction. */
+            public void drawIfNeeded() {
+                synchronized (mWindowManagerService.mWindowMap) {
+                    if (!mInvalidated) {
+                        return;
+                    }
+                    mInvalidated = false;
+                    Canvas canvas = null;
+                    try {
+                        // Empty dirty rectangle means unspecified.
+                        if (mDirtyRect.isEmpty()) {
+                            mBounds.getBounds(mDirtyRect);
+                        }
+                        mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
+                        canvas = mSurface.lockCanvas(mDirtyRect);
+                        if (DEBUG_VIEWPORT_WINDOW) {
+                            Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
+                        }
+                    } catch (IllegalArgumentException iae) {
+                        /* ignore */
+                    } catch (Surface.OutOfResourcesException oore) {
+                        /* ignore */
+                    }
+                    if (canvas == null) {
+                        return;
+                    }
+                    if (DEBUG_VIEWPORT_WINDOW) {
+                        Slog.i(LOG_TAG, "Bounds: " + mBounds);
+                    }
+                    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
+                    mPaint.setAlpha(mAlpha);
+                    Path path = mBounds.getBoundaryPath();
+                    canvas.drawPath(path, mPaint);
+
+                    mSurface.unlockCanvasAndPost(canvas);
+
+                    if (mAlpha > 0) {
+                        mSurfaceControl.show();
+                    } else {
+                        mSurfaceControl.hide();
+                    }
+                }
+            }
+
+            public void releaseSurface() {
+                mSurfaceControl.release();
+                mSurface.release();
+            }
+        }
+    }
+
+    private static final class WindowStateInfo {
+        private static final int MAX_POOL_SIZE = 30;
+
+        private static final SimplePool<WindowStateInfo> sPool =
+                new SimplePool<WindowStateInfo>(MAX_POOL_SIZE);
+
+        private static final Region mTempRegion = new Region();
+
+        public WindowState mWindowState;
+        public final Rect mTouchableRegion = new Rect();
+
+        public static WindowStateInfo obtain(WindowState windowState) {
+            WindowStateInfo info = sPool.acquire();
+            if (info == null) {
+                info = new WindowStateInfo();
+            }
+            info.mWindowState = windowState;
+            windowState.getTouchableRegion(mTempRegion);
+            mTempRegion.getBounds(info.mTouchableRegion);
+            return info;
+        }
+
+        public void recycle() {
+            mWindowState = null;
+            mTouchableRegion.setEmpty();
+            sPool.release(this);
+        }
+    }
+
+    private class MyHandler extends Handler {
+        public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1;
+        public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
+        public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
+        public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
+        public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
+
+        public MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
+                    Region bounds = (Region) message.obj;
+                    try {
+                        mCallbacks.onMagnifedBoundsChanged(bounds);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    } finally {
+                        bounds.recycle();
+                    }
+                } break;
+                case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
+                    SomeArgs args = (SomeArgs) message.obj;
+                    final int left = args.argi1;
+                    final int top = args.argi2;
+                    final int right = args.argi3;
+                    final int bottom = args.argi4;
+                    try {
+                        mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    } finally {
+                        args.recycle();
+                    }
+                } break;
+                case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
+                    try {
+                        mCallbacks.onUserContextChanged();
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    }
+                } break;
+                case MESSAGE_NOTIFY_ROTATION_CHANGED: {
+                    final int rotation = message.arg1;
+                    try {
+                        mCallbacks.onRotationChanged(rotation);
+                    } catch (RemoteException re) {
+                        /* ignore */
+                    }
+                } break;
+                case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
+                    synchronized (mWindowManagerService.mWindowMap) {
+                        if (mMagnifedViewport.isMagnifyingLocked()) {
+                            mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
+                            mWindowManagerService.scheduleAnimationLocked();
+                        }
+                    }
+                } break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplaySettings.java b/services/core/java/com/android/server/wm/DisplaySettings.java
new file mode 100644
index 0000000..34d1a64
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplaySettings.java
@@ -0,0 +1,224 @@
+/*
+ * 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.wm;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * Current persistent settings about a display
+ */
+public class DisplaySettings {
+    private static final String TAG = WindowManagerService.TAG;
+
+    private final Context mContext;
+    private final AtomicFile mFile;
+    private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
+
+    public static class Entry {
+        public final String name;
+        public int overscanLeft;
+        public int overscanTop;
+        public int overscanRight;
+        public int overscanBottom;
+
+        public Entry(String _name) {
+            name = _name;
+        }
+    }
+
+    public DisplaySettings(Context context) {
+        mContext = context;
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
+    }
+
+    public void getOverscanLocked(String name, Rect outRect) {
+        Entry entry = mEntries.get(name);
+        if (entry != null) {
+            outRect.left = entry.overscanLeft;
+            outRect.top = entry.overscanTop;
+            outRect.right = entry.overscanRight;
+            outRect.bottom = entry.overscanBottom;
+        } else {
+            outRect.set(0, 0, 0, 0);
+        }
+    }
+
+    public void setOverscanLocked(String name, int left, int top, int right, int bottom) {
+        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
+            // Right now all we are storing is overscan; if there is no overscan,
+            // we have no need for the entry.
+            mEntries.remove(name);
+            return;
+        }
+        Entry entry = mEntries.get(name);
+        if (entry == null) {
+            entry = new Entry(name);
+            mEntries.put(name, entry);
+        }
+        entry.overscanLeft = left;
+        entry.overscanTop = top;
+        entry.overscanRight = right;
+        entry.overscanBottom = bottom;
+    }
+
+    public void readSettingsLocked() {
+        FileInputStream stream;
+        try {
+            stream = mFile.openRead();
+        } catch (FileNotFoundException e) {
+            Slog.i(TAG, "No existing display settings " + mFile.getBaseFile()
+                    + "; starting empty");
+            return;
+        }
+        boolean success = false;
+        try {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(stream, null);
+            int type;
+            while ((type = parser.next()) != XmlPullParser.START_TAG
+                    && type != XmlPullParser.END_DOCUMENT) {
+                ;
+            }
+
+            if (type != XmlPullParser.START_TAG) {
+                throw new IllegalStateException("no start tag found");
+            }
+
+            int outerDepth = parser.getDepth();
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                    continue;
+                }
+
+                String tagName = parser.getName();
+                if (tagName.equals("display")) {
+                    readDisplay(parser);
+                } else {
+                    Slog.w(TAG, "Unknown element under <display-settings>: "
+                            + parser.getName());
+                    XmlUtils.skipCurrentTag(parser);
+                }
+            }
+            success = true;
+        } catch (IllegalStateException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (NumberFormatException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } catch (IndexOutOfBoundsException e) {
+            Slog.w(TAG, "Failed parsing " + e);
+        } finally {
+            if (!success) {
+                mEntries.clear();
+            }
+            try {
+                stream.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private int getIntAttribute(XmlPullParser parser, String name) {
+        try {
+            String str = parser.getAttributeValue(null, name);
+            return str != null ? Integer.parseInt(str) : 0;
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    private void readDisplay(XmlPullParser parser) throws NumberFormatException,
+            XmlPullParserException, IOException {
+        String name = parser.getAttributeValue(null, "name");
+        if (name != null) {
+            Entry entry = new Entry(name);
+            entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
+            entry.overscanTop = getIntAttribute(parser, "overscanTop");
+            entry.overscanRight = getIntAttribute(parser, "overscanRight");
+            entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
+            mEntries.put(name, entry);
+        }
+        XmlUtils.skipCurrentTag(parser);
+    }
+
+    public void writeSettingsLocked() {
+        FileOutputStream stream;
+        try {
+            stream = mFile.startWrite();
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings: " + e);
+            return;
+        }
+
+        try {
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(stream, "utf-8");
+            out.startDocument(null, true);
+            out.startTag(null, "display-settings");
+
+            for (Entry entry : mEntries.values()) {
+                out.startTag(null, "display");
+                out.attribute(null, "name", entry.name);
+                if (entry.overscanLeft != 0) {
+                    out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
+                }
+                if (entry.overscanTop != 0) {
+                    out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
+                }
+                if (entry.overscanRight != 0) {
+                    out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
+                }
+                if (entry.overscanBottom != 0) {
+                    out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
+                }
+                out.endTag(null, "display");
+            }
+
+            out.endTag(null, "display-settings");
+            out.endDocument();
+            mFile.finishWrite(stream);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed to write display settings, restoring backup.", e);
+            mFile.failWrite(stream);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
new file mode 100644
index 0000000..a737939
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
+import com.android.server.wm.WindowManagerService.H;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.graphics.Point;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DragEvent;
+import android.view.InputChannel;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+/**
+ * Drag/drop state
+ */
+class DragState {
+    final WindowManagerService mService;
+    IBinder mToken;
+    SurfaceControl mSurfaceControl;
+    int mFlags;
+    IBinder mLocalWin;
+    ClipData mData;
+    ClipDescription mDataDescription;
+    boolean mDragResult;
+    float mCurrentX, mCurrentY;
+    float mThumbOffsetX, mThumbOffsetY;
+    InputChannel mServerChannel, mClientChannel;
+    DragInputEventReceiver mInputEventReceiver;
+    InputApplicationHandle mDragApplicationHandle;
+    InputWindowHandle mDragWindowHandle;
+    WindowState mTargetWindow;
+    ArrayList<WindowState> mNotifiedWindows;
+    boolean mDragInProgress;
+    Display mDisplay;
+
+    private final Region mTmpRegion = new Region();
+
+    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
+            int flags, IBinder localWin) {
+        mService = service;
+        mToken = token;
+        mSurfaceControl = surface;
+        mFlags = flags;
+        mLocalWin = localWin;
+        mNotifiedWindows = new ArrayList<WindowState>();
+    }
+
+    void reset() {
+        if (mSurfaceControl != null) {
+            mSurfaceControl.destroy();
+        }
+        mSurfaceControl = null;
+        mFlags = 0;
+        mLocalWin = null;
+        mToken = null;
+        mData = null;
+        mThumbOffsetX = mThumbOffsetY = 0;
+        mNotifiedWindows = null;
+    }
+
+    /**
+     * @param display The Display that the window being dragged is on.
+     */
+    void register(Display display) {
+        mDisplay = display;
+        if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel");
+        if (mClientChannel != null) {
+            Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel");
+        } else {
+            InputChannel[] channels = InputChannel.openInputChannelPair("drag");
+            mServerChannel = channels[0];
+            mClientChannel = channels[1];
+            mService.mInputManager.registerInputChannel(mServerChannel, null);
+            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
+                    mService.mH.getLooper());
+
+            mDragApplicationHandle = new InputApplicationHandle(null);
+            mDragApplicationHandle.name = "drag";
+            mDragApplicationHandle.dispatchingTimeoutNanos =
+                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+                    mDisplay.getDisplayId());
+            mDragWindowHandle.name = "drag";
+            mDragWindowHandle.inputChannel = mServerChannel;
+            mDragWindowHandle.layer = getDragLayerLw();
+            mDragWindowHandle.layoutParamsFlags = 0;
+            mDragWindowHandle.layoutParamsPrivateFlags = 0;
+            mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
+            mDragWindowHandle.dispatchingTimeoutNanos =
+                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+            mDragWindowHandle.visible = true;
+            mDragWindowHandle.canReceiveKeys = false;
+            mDragWindowHandle.hasFocus = true;
+            mDragWindowHandle.hasWallpaper = false;
+            mDragWindowHandle.paused = false;
+            mDragWindowHandle.ownerPid = Process.myPid();
+            mDragWindowHandle.ownerUid = Process.myUid();
+            mDragWindowHandle.inputFeatures = 0;
+            mDragWindowHandle.scaleFactor = 1.0f;
+
+            // The drag window cannot receive new touches.
+            mDragWindowHandle.touchableRegion.setEmpty();
+
+            // The drag window covers the entire display
+            mDragWindowHandle.frameLeft = 0;
+            mDragWindowHandle.frameTop = 0;
+            Point p = new Point();
+            mDisplay.getRealSize(p);
+            mDragWindowHandle.frameRight = p.x;
+            mDragWindowHandle.frameBottom = p.y;
+
+            // Pause rotations before a drag.
+            if (WindowManagerService.DEBUG_ORIENTATION) {
+                Slog.d(WindowManagerService.TAG, "Pausing rotation during drag");
+            }
+            mService.pauseRotationLocked();
+        }
+    }
+
+    void unregister() {
+        if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel");
+        if (mClientChannel == null) {
+            Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel");
+        } else {
+            mService.mInputManager.unregisterInputChannel(mServerChannel);
+            mInputEventReceiver.dispose();
+            mInputEventReceiver = null;
+            mClientChannel.dispose();
+            mServerChannel.dispose();
+            mClientChannel = null;
+            mServerChannel = null;
+
+            mDragWindowHandle = null;
+            mDragApplicationHandle = null;
+
+            // Resume rotations after a drag.
+            if (WindowManagerService.DEBUG_ORIENTATION) {
+                Slog.d(WindowManagerService.TAG, "Resuming rotation after drag");
+            }
+            mService.resumeRotationLocked();
+        }
+    }
+
+    int getDragLayerLw() {
+        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    /* call out to each visible window/session informing it about the drag
+     */
+    void broadcastDragStartedLw(final float touchX, final float touchY) {
+        // Cache a base-class instance of the clip metadata so that parceling
+        // works correctly in calling out to the apps.
+        mDataDescription = (mData != null) ? mData.getDescription() : null;
+        mNotifiedWindows.clear();
+        mDragInProgress = true;
+
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
+        }
+
+        final WindowList windows = mService.getWindowListLocked(mDisplay);
+        if (windows != null) {
+            final int N = windows.size();
+            for (int i = 0; i < N; i++) {
+                sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription);
+            }
+        }
+    }
+
+    /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the
+     * designated window is potentially a drop recipient.  There are race situations
+     * around DRAG_ENDED broadcast, so we make sure that once we've declared that
+     * the drag has ended, we never send out another DRAG_STARTED for this drag action.
+     *
+     * This method clones the 'event' parameter if it's being delivered to the same
+     * process, so it's safe for the caller to call recycle() on the event afterwards.
+     */
+    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
+            ClipDescription desc) {
+        // Don't actually send the event if the drag is supposed to be pinned
+        // to the originating window but 'newWin' is not that window.
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
+            final IBinder winBinder = newWin.mClient.asBinder();
+            if (winBinder != mLocalWin) {
+                if (WindowManagerService.DEBUG_DRAG) {
+                    Slog.d(WindowManagerService.TAG, "Not dispatching local DRAG_STARTED to " + newWin);
+                }
+                return;
+            }
+        }
+
+        if (mDragInProgress && newWin.isPotentialDragTarget()) {
+            DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
+                    touchX, touchY, null, desc, null, false);
+            try {
+                newWin.mClient.dispatchDragEvent(event);
+                // track each window that we've notified that the drag is starting
+                mNotifiedWindows.add(newWin);
+            } catch (RemoteException e) {
+                Slog.w(WindowManagerService.TAG, "Unable to drag-start window " + newWin);
+            } finally {
+                // if the callee was local, the dispatch has already recycled the event
+                if (Process.myPid() != newWin.mSession.mPid) {
+                    event.recycle();
+                }
+            }
+        }
+    }
+
+    /* helper - construct and send a DRAG_STARTED event only if the window has not
+     * previously been notified, i.e. it became visible after the drag operation
+     * was begun.  This is a rare case.
+     */
+    void sendDragStartedIfNeededLw(WindowState newWin) {
+        if (mDragInProgress) {
+            // If we have sent the drag-started, we needn't do so again
+            for (WindowState ws : mNotifiedWindows) {
+                if (ws == newWin) {
+                    return;
+                }
+            }
+            if (WindowManagerService.DEBUG_DRAG) {
+                Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin);
+            }
+            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
+        }
+    }
+
+    void broadcastDragEndedLw() {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED");
+        }
+        DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
+                0, 0, null, null, null, mDragResult);
+        for (WindowState ws: mNotifiedWindows) {
+            try {
+                ws.mClient.dispatchDragEvent(evt);
+            } catch (RemoteException e) {
+                Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws);
+            }
+        }
+        mNotifiedWindows.clear();
+        mDragInProgress = false;
+        evt.recycle();
+    }
+
+    void endDragLw() {
+        mService.mDragState.broadcastDragEndedLw();
+
+        // stop intercepting input
+        mService.mDragState.unregister();
+        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+        // free our resources and drop all the object references
+        mService.mDragState.reset();
+        mService.mDragState = null;
+    }
+
+    void notifyMoveLw(float x, float y) {
+        final int myPid = Process.myPid();
+
+        // Move the surface to the given touch
+        if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw");
+        SurfaceControl.openTransaction();
+        try {
+            mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
+            if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, "  DRAG "
+                    + mSurfaceControl + ": pos=(" +
+                    (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                    WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw");
+        }
+
+        // Tell the affected window
+        WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+        if (touchedWin == null) {
+            if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "No touched win at x=" + x + " y=" + y);
+            return;
+        }
+        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) {
+            final IBinder touchedBinder = touchedWin.mClient.asBinder();
+            if (touchedBinder != mLocalWin) {
+                // This drag is pinned only to the originating window, but the drag
+                // point is outside that window.  Pretend it's over empty space.
+                touchedWin = null;
+            }
+        }
+        try {
+            // have we dragged over a new window?
+            if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
+                if (WindowManagerService.DEBUG_DRAG) {
+                    Slog.d(WindowManagerService.TAG, "sending DRAG_EXITED to " + mTargetWindow);
+                }
+                // force DRAG_EXITED_EVENT if appropriate
+                DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
+                        x, y, null, null, null, false);
+                mTargetWindow.mClient.dispatchDragEvent(evt);
+                if (myPid != mTargetWindow.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+            if (touchedWin != null) {
+                if (false && WindowManagerService.DEBUG_DRAG) {
+                    Slog.d(WindowManagerService.TAG, "sending DRAG_LOCATION to " + touchedWin);
+                }
+                DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
+                        x, y, null, null, null, false);
+                touchedWin.mClient.dispatchDragEvent(evt);
+                if (myPid != touchedWin.mSession.mPid) {
+                    evt.recycle();
+                }
+            }
+        } catch (RemoteException e) {
+            Slog.w(WindowManagerService.TAG, "can't send drag notification to windows");
+        }
+        mTargetWindow = touchedWin;
+    }
+
+    // Tell the drop target about the data.  Returns 'true' if we can immediately
+    // dispatch the global drag-ended message, 'false' if we need to wait for a
+    // result from the recipient.
+    boolean notifyDropLw(float x, float y) {
+        WindowState touchedWin = getTouchedWinAtPointLw(x, y);
+        if (touchedWin == null) {
+            // "drop" outside a valid window -- no recipient to apply a
+            // timeout to, and we can send the drag-ended message immediately.
+            mDragResult = false;
+            return true;
+        }
+
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "sending DROP to " + touchedWin);
+        }
+        final int myPid = Process.myPid();
+        final IBinder token = touchedWin.mClient.asBinder();
+        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
+                null, null, mData, false);
+        try {
+            touchedWin.mClient.dispatchDragEvent(evt);
+
+            // 5 second timeout for this window to respond to the drop
+            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
+            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
+            mService.mH.sendMessageDelayed(msg, 5000);
+        } catch (RemoteException e) {
+            Slog.w(WindowManagerService.TAG, "can't send drop notification to win " + touchedWin);
+            return true;
+        } finally {
+            if (myPid != touchedWin.mSession.mPid) {
+                evt.recycle();
+            }
+        }
+        mToken = token;
+        return false;
+    }
+
+    // Find the visible, touch-deliverable window under the given point
+    private WindowState getTouchedWinAtPointLw(float xf, float yf) {
+        WindowState touchedWin = null;
+        final int x = (int) xf;
+        final int y = (int) yf;
+
+        final WindowList windows = mService.getWindowListLocked(mDisplay);
+        if (windows == null) {
+            return null;
+        }
+        final int N = windows.size();
+        for (int i = N - 1; i >= 0; i--) {
+            WindowState child = windows.get(i);
+            final int flags = child.mAttrs.flags;
+            if (!child.isVisibleLw()) {
+                // not visible == don't tell about drags
+                continue;
+            }
+            if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                // not touchable == don't tell about drags
+                continue;
+            }
+
+            child.getTouchableRegion(mTmpRegion);
+
+            final int touchFlags = flags &
+                    (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+            if (mTmpRegion.contains(x, y) || touchFlags == 0) {
+                // Found it
+                touchedWin = child;
+                break;
+            }
+        }
+
+        return touchedWin;
+    }
+
+    private static DragEvent obtainDragEvent(WindowState win, int action,
+            float x, float y, Object localState,
+            ClipDescription description, ClipData data, boolean result) {
+        float winX = x - win.mFrame.left;
+        float winY = y - win.mFrame.top;
+        if (win.mEnforceSizeCompat) {
+            winX *= win.mGlobalScale;
+            winY *= win.mGlobalScale;
+        }
+        return DragEvent.obtain(action, winX, winY, localState, description, data, result);
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/FakeWindowImpl.java b/services/core/java/com/android/server/wm/FakeWindowImpl.java
new file mode 100644
index 0000000..5a3471b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FakeWindowImpl.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+
+import android.os.Looper;
+import android.os.Process;
+import android.util.Slog;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.InputEventReceiver;
+import android.view.InputQueue;
+import android.view.WindowManagerPolicy;
+
+public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow {
+    final WindowManagerService mService;
+    final InputChannel mServerChannel, mClientChannel;
+    final InputApplicationHandle mApplicationHandle;
+    final InputWindowHandle mWindowHandle;
+    final InputEventReceiver mInputEventReceiver;
+    final int mWindowLayer;
+
+    boolean mTouchFullscreen;
+
+    public FakeWindowImpl(WindowManagerService service,
+            Looper looper, InputEventReceiver.Factory inputEventReceiverFactory,
+            String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags,
+            boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) {
+        mService = service;
+
+        InputChannel[] channels = InputChannel.openInputChannelPair(name);
+        mServerChannel = channels[0];
+        mClientChannel = channels[1];
+        mService.mInputManager.registerInputChannel(mServerChannel, null);
+
+        mInputEventReceiver = inputEventReceiverFactory.createInputEventReceiver(
+                mClientChannel, looper);
+
+        mApplicationHandle = new InputApplicationHandle(null);
+        mApplicationHandle.name = name;
+        mApplicationHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+
+        mWindowHandle = new InputWindowHandle(mApplicationHandle, null, Display.DEFAULT_DISPLAY);
+        mWindowHandle.name = name;
+        mWindowHandle.inputChannel = mServerChannel;
+        mWindowLayer = getLayerLw(windowType);
+        mWindowHandle.layer = mWindowLayer;
+        mWindowHandle.layoutParamsFlags = layoutParamsFlags;
+        mWindowHandle.layoutParamsPrivateFlags = layoutParamsPrivateFlags;
+        mWindowHandle.layoutParamsType = windowType;
+        mWindowHandle.dispatchingTimeoutNanos =
+                WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        mWindowHandle.visible = true;
+        mWindowHandle.canReceiveKeys = canReceiveKeys;
+        mWindowHandle.hasFocus = hasFocus;
+        mWindowHandle.hasWallpaper = false;
+        mWindowHandle.paused = false;
+        mWindowHandle.ownerPid = Process.myPid();
+        mWindowHandle.ownerUid = Process.myUid();
+        mWindowHandle.inputFeatures = 0;
+        mWindowHandle.scaleFactor = 1.0f;
+
+        mTouchFullscreen = touchFullscreen;
+    }
+
+    void layout(int dw, int dh) {
+        if (mTouchFullscreen) {
+            mWindowHandle.touchableRegion.set(0, 0, dw, dh);
+        } else {
+            mWindowHandle.touchableRegion.setEmpty();
+        }
+        mWindowHandle.frameLeft = 0;
+        mWindowHandle.frameTop = 0;
+        mWindowHandle.frameRight = dw;
+        mWindowHandle.frameBottom = dh;
+    }
+
+    @Override
+    public void dismiss() {
+        synchronized (mService.mWindowMap) {
+            if (mService.removeFakeWindowLocked(this)) {
+                mInputEventReceiver.dispose();
+                mService.mInputManager.unregisterInputChannel(mServerChannel);
+                mClientChannel.dispose();
+                mServerChannel.dispose();
+            }
+        }
+    }
+
+    private int getLayerLw(int windowType) {
+        return mService.mPolicy.windowTypeToLayerLw(windowType)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/FocusedStackFrame.java b/services/core/java/com/android/server/wm/FocusedStackFrame.java
new file mode 100644
index 0000000..f1f5fe8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/FocusedStackFrame.java
@@ -0,0 +1,142 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.WindowManagerService.DEBUG_STACK;
+import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import com.android.server.wm.WindowStateAnimator.SurfaceTrace;
+
+class FocusedStackFrame {
+    private static final String TAG = "FocusedStackFrame";
+    private static final int THICKNESS = 10;
+    private static final float ALPHA = 0.3f;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private final Rect mLastBounds = new Rect();
+    final Rect mBounds = new Rect();
+    private final Rect mTmpDrawRect = new Rect();
+
+    public FocusedStackFrame(Display display, SurfaceSession session) {
+        SurfaceControl ctrl = null;
+        try {
+            if (DEBUG_SURFACE_TRACE) {
+                ctrl = new SurfaceTrace(session, "FocusedStackFrame",
+                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            } else {
+                ctrl = new SurfaceControl(session, "FocusedStackFrame",
+                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            }
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setAlpha(ALPHA);
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+    }
+
+    private void draw(Rect bounds, int color) {
+        if (false && DEBUG_STACK) Slog.i(TAG, "draw: bounds=" + bounds.toShortString() +
+                " color=" + Integer.toHexString(color));
+        mTmpDrawRect.set(bounds);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(mTmpDrawRect);
+        } catch (IllegalArgumentException e) {
+        } catch (Surface.OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+
+        final int w = bounds.width();
+        final int h = bounds.height();
+
+        // Top
+        mTmpDrawRect.set(0, 0, w, THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Left (not including Top or Bottom stripe).
+        mTmpDrawRect.set(0, THICKNESS, THICKNESS, h - THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Right (not including Top or Bottom stripe).
+        mTmpDrawRect.set(w - THICKNESS, THICKNESS, w, h - THICKNESS);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+        // Bottom
+        mTmpDrawRect.set(0, h - THICKNESS, w, h);
+        c.clipRect(mTmpDrawRect, Region.Op.REPLACE);
+        c.drawColor(color);
+
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    private void positionSurface(Rect bounds) {
+        if (false && DEBUG_STACK) Slog.i(TAG, "positionSurface: bounds=" + bounds.toShortString());
+        mSurfaceControl.setSize(bounds.width(), bounds.height());
+        mSurfaceControl.setPosition(bounds.left, bounds.top);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (false && DEBUG_STACK) Slog.i(TAG, "setVisibility: on=" + on +
+                " mLastBounds=" + mLastBounds.toShortString() +
+                " mBounds=" + mBounds.toShortString());
+        if (mSurfaceControl == null) {
+            return;
+        }
+        if (on) {
+            if (!mLastBounds.equals(mBounds)) {
+                // Erase the previous rectangle.
+                positionSurface(mLastBounds);
+                draw(mLastBounds, Color.TRANSPARENT);
+                // Draw the latest rectangle.
+                positionSurface(mBounds);
+                draw(mBounds, Color.WHITE);
+                // Update the history.
+                mLastBounds.set(mBounds);
+            }
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    public void setBounds(TaskStack stack) {
+        stack.getBounds(mBounds);
+        if (false && DEBUG_STACK) Slog.i(TAG, "setBounds: bounds=" + mBounds);
+    }
+
+    public void setLayer(int layer) {
+        mSurfaceControl.setLayer(layer);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
new file mode 100644
index 0000000..803b9ac
--- /dev/null
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2010 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.wm;
+
+import com.android.server.input.InputManagerService;
+import com.android.server.input.InputApplicationHandle;
+import com.android.server.input.InputWindowHandle;
+
+import android.app.ActivityManagerNative;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.view.Display;
+import android.view.InputChannel;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import java.util.Arrays;
+
+final class InputMonitor implements InputManagerService.WindowManagerCallbacks {
+    private final WindowManagerService mService;
+    
+    // Current window with input focus for keys and other non-touch events.  May be null.
+    private WindowState mInputFocus;
+    
+    // When true, prevents input dispatch from proceeding until set to false again.
+    private boolean mInputDispatchFrozen;
+    
+    // When true, input dispatch proceeds normally.  Otherwise all events are dropped.
+    // Initially false, so that input does not get dispatched until boot is finished at
+    // which point the ActivityManager will enable dispatching.
+    private boolean mInputDispatchEnabled;
+
+    // When true, need to call updateInputWindowsLw().
+    private boolean mUpdateInputWindowsNeeded = true;
+
+    // Array of window handles to provide to the input dispatcher.
+    private InputWindowHandle[] mInputWindowHandles;
+    private int mInputWindowHandleCount;
+
+    // Set to true when the first input device configuration change notification
+    // is received to indicate that the input devices are ready.
+    private final Object mInputDevicesReadyMonitor = new Object();
+    private boolean mInputDevicesReady;
+
+    Rect mTmpRect = new Rect();
+
+    public InputMonitor(WindowManagerService service) {
+        mService = service;
+    }
+    
+    /* Notifies the window manager about a broken input channel.
+     * 
+     * Called by the InputManager.
+     */
+    @Override
+    public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) {
+        if (inputWindowHandle == null) {
+            return;
+        }
+
+        synchronized (mService.mWindowMap) {
+            WindowState windowState = (WindowState) inputWindowHandle.windowState;
+            if (windowState != null) {
+                Slog.i(WindowManagerService.TAG, "WINDOW DIED " + windowState);
+                mService.removeWindowLocked(windowState.mSession, windowState);
+            }
+        }
+    }
+    
+    /* Notifies the window manager about an application that is not responding.
+     * Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
+     * 
+     * Called by the InputManager.
+     */
+    @Override
+    public long notifyANR(InputApplicationHandle inputApplicationHandle,
+            InputWindowHandle inputWindowHandle, String reason) {
+        AppWindowToken appWindowToken = null;
+        WindowState windowState = null;
+        boolean aboveSystem = false;
+        synchronized (mService.mWindowMap) {
+            if (inputWindowHandle != null) {
+                windowState = (WindowState) inputWindowHandle.windowState;
+                if (windowState != null) {
+                    appWindowToken = windowState.mAppToken;
+                }
+            }
+            if (appWindowToken == null && inputApplicationHandle != null) {
+                appWindowToken = (AppWindowToken)inputApplicationHandle.appWindowToken;
+            }
+
+            if (windowState != null) {
+                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
+                        + "sending to " + windowState.mAttrs.getTitle()
+                        + ".  Reason: " + reason);
+                // Figure out whether this window is layered above system windows.
+                // We need to do this here to help the activity manager know how to
+                // layer its ANR dialog.
+                int systemAlertLayer = mService.mPolicy.windowTypeToLayerLw(
+                        WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
+                aboveSystem = windowState.mBaseLayer > systemAlertLayer;
+            } else if (appWindowToken != null) {
+                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
+                        + "sending to application " + appWindowToken.stringName
+                        + ".  Reason: " + reason);
+            } else {
+                Slog.i(WindowManagerService.TAG, "Input event dispatching timed out "
+                        + ".  Reason: " + reason);
+            }
+
+            mService.saveANRStateLocked(appWindowToken, windowState, reason);
+        }
+
+        if (appWindowToken != null && appWindowToken.appToken != null) {
+            try {
+                // Notify the activity manager about the timeout and let it decide whether
+                // to abort dispatching or keep waiting.
+                boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason);
+                if (! abort) {
+                    // The activity manager declined to abort dispatching.
+                    // Wait a bit longer and timeout again later.
+                    return appWindowToken.inputDispatchingTimeoutNanos;
+                }
+            } catch (RemoteException ex) {
+            }
+        } else if (windowState != null) {
+            try {
+                // Notify the activity manager about the timeout and let it decide whether
+                // to abort dispatching or keep waiting.
+                long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut(
+                        windowState.mSession.mPid, aboveSystem, reason);
+                if (timeout >= 0) {
+                    // The activity manager declined to abort dispatching.
+                    // Wait a bit longer and timeout again later.
+                    return timeout;
+                }
+            } catch (RemoteException ex) {
+            }
+        }
+        return 0; // abort dispatching
+    }
+
+    private void addInputWindowHandleLw(final InputWindowHandle windowHandle) {
+        if (mInputWindowHandles == null) {
+            mInputWindowHandles = new InputWindowHandle[16];
+        }
+        if (mInputWindowHandleCount >= mInputWindowHandles.length) {
+            mInputWindowHandles = Arrays.copyOf(mInputWindowHandles,
+                    mInputWindowHandleCount * 2);
+        }
+        mInputWindowHandles[mInputWindowHandleCount++] = windowHandle;
+    }
+
+    private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle,
+            final WindowState child, int flags, int privateFlags, final int type,
+            final boolean isVisible, final boolean hasFocus, final boolean hasWallpaper) {
+        // Add a window to our list of input windows.
+        inputWindowHandle.name = child.toString();
+        final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0;
+        if (modal && child.mAppToken != null) {
+            // Limit the outer touch to the activity stack region.
+            flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+            child.getStackBounds(mTmpRect);
+            inputWindowHandle.touchableRegion.set(mTmpRect);
+        } else {
+            // Not modal or full screen modal
+            child.getTouchableRegion(inputWindowHandle.touchableRegion);
+        }
+        inputWindowHandle.layoutParamsFlags = flags;
+        inputWindowHandle.layoutParamsPrivateFlags = privateFlags;
+        inputWindowHandle.layoutParamsType = type;
+        inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos();
+        inputWindowHandle.visible = isVisible;
+        inputWindowHandle.canReceiveKeys = child.canReceiveKeys();
+        inputWindowHandle.hasFocus = hasFocus;
+        inputWindowHandle.hasWallpaper = hasWallpaper;
+        inputWindowHandle.paused = child.mAppToken != null ? child.mAppToken.paused : false;
+        inputWindowHandle.layer = child.mLayer;
+        inputWindowHandle.ownerPid = child.mSession.mPid;
+        inputWindowHandle.ownerUid = child.mSession.mUid;
+        inputWindowHandle.inputFeatures = child.mAttrs.inputFeatures;
+
+        final Rect frame = child.mFrame;
+        inputWindowHandle.frameLeft = frame.left;
+        inputWindowHandle.frameTop = frame.top;
+        inputWindowHandle.frameRight = frame.right;
+        inputWindowHandle.frameBottom = frame.bottom;
+
+        if (child.mGlobalScale != 1) {
+            // If we are scaling the window, input coordinates need
+            // to be inversely scaled to map from what is on screen
+            // to what is actually being touched in the UI.
+            inputWindowHandle.scaleFactor = 1.0f/child.mGlobalScale;
+        } else {
+            inputWindowHandle.scaleFactor = 1;
+        }
+
+
+        addInputWindowHandleLw(inputWindowHandle);
+    }
+
+    private void clearInputWindowHandlesLw() {
+        while (mInputWindowHandleCount != 0) {
+            mInputWindowHandles[--mInputWindowHandleCount] = null;
+        }
+    }
+
+    public void setUpdateInputWindowsNeededLw() {
+        mUpdateInputWindowsNeeded = true;
+    }
+
+    /* Updates the cached window information provided to the input dispatcher. */
+    public void updateInputWindowsLw(boolean force) {
+        if (!force && !mUpdateInputWindowsNeeded) {
+            return;
+        }
+        mUpdateInputWindowsNeeded = false;
+
+        if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED updateInputWindowsLw");
+
+        // Populate the input window list with information about all of the windows that
+        // could potentially receive input.
+        // As an optimization, we could try to prune the list of windows but this turns
+        // out to be difficult because only the native code knows for sure which window
+        // currently has touch focus.
+        final WindowStateAnimator universeBackground = mService.mAnimator.mUniverseBackground;
+        final int aboveUniverseLayer = mService.mAnimator.mAboveUniverseLayer;
+        boolean addedUniverse = false;
+
+        // If there's a drag in flight, provide a pseudowindow to catch drag input
+        final boolean inDrag = (mService.mDragState != null);
+        if (inDrag) {
+            if (WindowManagerService.DEBUG_DRAG) {
+                Log.d(WindowManagerService.TAG, "Inserting drag window");
+            }
+            final InputWindowHandle dragWindowHandle = mService.mDragState.mDragWindowHandle;
+            if (dragWindowHandle != null) {
+                addInputWindowHandleLw(dragWindowHandle);
+            } else {
+                Slog.w(WindowManagerService.TAG, "Drag is in progress but there is no "
+                        + "drag window handle.");
+            }
+        }
+
+        final int NFW = mService.mFakeWindows.size();
+        for (int i = 0; i < NFW; i++) {
+            addInputWindowHandleLw(mService.mFakeWindows.get(i).mWindowHandle);
+        }
+
+        // Add all windows on the default display.
+        final int numDisplays = mService.mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            WindowList windows = mService.mDisplayContents.valueAt(displayNdx).getWindowList();
+            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                final WindowState child = windows.get(winNdx);
+                final InputChannel inputChannel = child.mInputChannel;
+                final InputWindowHandle inputWindowHandle = child.mInputWindowHandle;
+                if (inputChannel == null || inputWindowHandle == null || child.mRemoved) {
+                    // Skip this window because it cannot possibly receive input.
+                    continue;
+                }
+
+                final int flags = child.mAttrs.flags;
+                final int privateFlags = child.mAttrs.privateFlags;
+                final int type = child.mAttrs.type;
+
+                final boolean hasFocus = (child == mInputFocus);
+                final boolean isVisible = child.isVisibleLw();
+                final boolean hasWallpaper = (child == mService.mWallpaperTarget)
+                        && (type != WindowManager.LayoutParams.TYPE_KEYGUARD);
+                final boolean onDefaultDisplay = (child.getDisplayId() == Display.DEFAULT_DISPLAY);
+
+                // If there's a drag in progress and 'child' is a potential drop target,
+                // make sure it's been told about the drag
+                if (inDrag && isVisible && onDefaultDisplay) {
+                    mService.mDragState.sendDragStartedIfNeededLw(child);
+                }
+
+                if (universeBackground != null && !addedUniverse
+                        && child.mBaseLayer < aboveUniverseLayer && onDefaultDisplay) {
+                    final WindowState u = universeBackground.mWin;
+                    if (u.mInputChannel != null && u.mInputWindowHandle != null) {
+                        addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags,
+                                u.mAttrs.privateFlags, u.mAttrs.type,
+                                true, u == mInputFocus, false);
+                    }
+                    addedUniverse = true;
+                }
+
+                if (child.mWinAnimator != universeBackground) {
+                    addInputWindowHandleLw(inputWindowHandle, child, flags, privateFlags, type,
+                            isVisible, hasFocus, hasWallpaper);
+                }
+            }
+        }
+
+        // Send windows to native code.
+        mService.mInputManager.setInputWindows(mInputWindowHandles);
+
+        // Clear the list in preparation for the next round.
+        clearInputWindowHandlesLw();
+
+        if (false) Slog.d(WindowManagerService.TAG, "<<<<<<< EXITED updateInputWindowsLw");
+    }
+
+    /* Notifies that the input device configuration has changed. */
+    @Override
+    public void notifyConfigurationChanged() {
+        mService.sendNewConfiguration();
+
+        synchronized (mInputDevicesReadyMonitor) {
+            if (!mInputDevicesReady) {
+                mInputDevicesReady = true;
+                mInputDevicesReadyMonitor.notifyAll();
+            }
+        }
+    }
+
+    /* Waits until the built-in input devices have been configured. */
+    public boolean waitForInputDevicesReady(long timeoutMillis) {
+        synchronized (mInputDevicesReadyMonitor) {
+            if (!mInputDevicesReady) {
+                try {
+                    mInputDevicesReadyMonitor.wait(timeoutMillis);
+                } catch (InterruptedException ex) {
+                }
+            }
+            return mInputDevicesReady;
+        }
+    }
+
+    /* Notifies that the lid switch changed state. */
+    @Override
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) {
+        mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen);
+    }
+
+    /* Provides an opportunity for the window manager policy to intercept early key
+     * processing as soon as the key has been read from the device. */
+    @Override
+    public int interceptKeyBeforeQueueing(
+            KeyEvent event, int policyFlags, boolean isScreenOn) {
+        return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn);
+    }
+
+    /* Provides an opportunity for the window manager policy to intercept early
+     * motion event processing when the screen is off since these events are normally
+     * dropped. */
+    @Override
+    public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) {
+        return mService.mPolicy.interceptMotionBeforeQueueingWhenScreenOff(policyFlags);
+    }
+
+    /* Provides an opportunity for the window manager policy to process a key before
+     * ordinary dispatch. */
+    @Override
+    public long interceptKeyBeforeDispatching(
+            InputWindowHandle focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+        return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags);
+    }
+
+    /* Provides an opportunity for the window manager policy to process a key that
+     * the application did not handle. */
+    @Override
+    public KeyEvent dispatchUnhandledKey(
+            InputWindowHandle focus, KeyEvent event, int policyFlags) {
+        WindowState windowState = focus != null ? (WindowState) focus.windowState : null;
+        return mService.mPolicy.dispatchUnhandledKey(windowState, event, policyFlags);
+    }
+
+    /* Callback to get pointer layer. */
+    @Override
+    public int getPointerLayer() {
+        return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_POINTER)
+                * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                + WindowManagerService.TYPE_LAYER_OFFSET;
+    }
+
+    /* Called when the current input focus changes.
+     * Layer assignment is assumed to be complete by the time this is called.
+     */
+    public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
+        if (WindowManagerService.DEBUG_FOCUS_LIGHT || WindowManagerService.DEBUG_INPUT) {
+            Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow);
+        }
+
+        if (newWindow != mInputFocus) {
+            if (newWindow != null && newWindow.canReceiveKeys()) {
+                // Displaying a window implicitly causes dispatching to be unpaused.
+                // This is to protect against bugs if someone pauses dispatching but
+                // forgets to resume.
+                newWindow.mToken.paused = false;
+            }
+
+            mInputFocus = newWindow;
+            setUpdateInputWindowsNeededLw();
+
+            if (updateInputWindows) {
+                updateInputWindowsLw(false /*force*/);
+            }
+        }
+    }
+
+    public void setFocusedAppLw(AppWindowToken newApp) {
+        // Focused app has changed.
+        if (newApp == null) {
+            mService.mInputManager.setFocusedApplication(null);
+        } else {
+            final InputApplicationHandle handle = newApp.mInputApplicationHandle;
+            handle.name = newApp.toString();
+            handle.dispatchingTimeoutNanos = newApp.inputDispatchingTimeoutNanos;
+
+            mService.mInputManager.setFocusedApplication(handle);
+        }
+    }
+
+    public void pauseDispatchingLw(WindowToken window) {
+        if (! window.paused) {
+            if (WindowManagerService.DEBUG_INPUT) {
+                Slog.v(WindowManagerService.TAG, "Pausing WindowToken " + window);
+            }
+            
+            window.paused = true;
+            updateInputWindowsLw(true /*force*/);
+        }
+    }
+    
+    public void resumeDispatchingLw(WindowToken window) {
+        if (window.paused) {
+            if (WindowManagerService.DEBUG_INPUT) {
+                Slog.v(WindowManagerService.TAG, "Resuming WindowToken " + window);
+            }
+            
+            window.paused = false;
+            updateInputWindowsLw(true /*force*/);
+        }
+    }
+    
+    public void freezeInputDispatchingLw() {
+        if (! mInputDispatchFrozen) {
+            if (WindowManagerService.DEBUG_INPUT) {
+                Slog.v(WindowManagerService.TAG, "Freezing input dispatching");
+            }
+            
+            mInputDispatchFrozen = true;
+            updateInputDispatchModeLw();
+        }
+    }
+    
+    public void thawInputDispatchingLw() {
+        if (mInputDispatchFrozen) {
+            if (WindowManagerService.DEBUG_INPUT) {
+                Slog.v(WindowManagerService.TAG, "Thawing input dispatching");
+            }
+            
+            mInputDispatchFrozen = false;
+            updateInputDispatchModeLw();
+        }
+    }
+    
+    public void setEventDispatchingLw(boolean enabled) {
+        if (mInputDispatchEnabled != enabled) {
+            if (WindowManagerService.DEBUG_INPUT) {
+                Slog.v(WindowManagerService.TAG, "Setting event dispatching to " + enabled);
+            }
+            
+            mInputDispatchEnabled = enabled;
+            updateInputDispatchModeLw();
+        }
+    }
+    
+    private void updateInputDispatchModeLw() {
+        mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/KeyguardDisableHandler.java b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
new file mode 100644
index 0000000..859df51
--- /dev/null
+++ b/services/core/java/com/android/server/wm/KeyguardDisableHandler.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.app.ActivityManagerNative;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.TokenWatcher;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+import android.view.WindowManagerPolicy;
+
+public class KeyguardDisableHandler extends Handler {
+    private static final String TAG = "KeyguardDisableHandler";
+
+    private static final int ALLOW_DISABLE_YES = 1;
+    private static final int ALLOW_DISABLE_NO = 0;
+    private static final int ALLOW_DISABLE_UNKNOWN = -1; // check with DevicePolicyManager
+    private int mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN; // sync'd by mKeyguardTokenWatcher
+
+    // Message.what constants
+    static final int KEYGUARD_DISABLE = 1;
+    static final int KEYGUARD_REENABLE = 2;
+    static final int KEYGUARD_POLICY_CHANGED = 3;
+
+    final Context mContext;
+    final WindowManagerPolicy mPolicy;
+    KeyguardTokenWatcher mKeyguardTokenWatcher;
+
+    public KeyguardDisableHandler(final Context context, final WindowManagerPolicy policy) {
+        mContext = context;
+        mPolicy = policy;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void handleMessage(Message msg) {
+        if (mKeyguardTokenWatcher == null) {
+            mKeyguardTokenWatcher = new KeyguardTokenWatcher(this);
+        }
+
+        switch (msg.what) {
+            case KEYGUARD_DISABLE:
+                final Pair<IBinder, String> pair = (Pair<IBinder, String>)msg.obj;
+                mKeyguardTokenWatcher.acquire(pair.first, pair.second);
+                break;
+
+            case KEYGUARD_REENABLE:
+                mKeyguardTokenWatcher.release((IBinder)msg.obj);
+                break;
+
+            case KEYGUARD_POLICY_CHANGED:
+                mPolicy.enableKeyguard(true);
+                // lazily evaluate this next time we're asked to disable keyguard
+                mAllowDisableKeyguard = ALLOW_DISABLE_UNKNOWN;
+                break;
+        }
+    }
+
+    class KeyguardTokenWatcher extends TokenWatcher {
+
+        public KeyguardTokenWatcher(final Handler handler) {
+            super(handler, TAG);
+        }
+
+        @Override
+        public void acquired() {
+            // We fail safe and prevent disabling keyguard in the unlikely event this gets
+            // called before DevicePolicyManagerService has started.
+            if (mAllowDisableKeyguard == ALLOW_DISABLE_UNKNOWN) {
+                DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+                        Context.DEVICE_POLICY_SERVICE);
+                if (dpm != null) {
+                    try {
+                        mAllowDisableKeyguard = dpm.getPasswordQuality(null, 
+                                ActivityManagerNative.getDefault().getCurrentUser().id)
+                                == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED ?
+                                        ALLOW_DISABLE_YES : ALLOW_DISABLE_NO;
+                    } catch (RemoteException re) {
+                        // Nothing much we can do
+                    }
+                }
+            }
+            if (mAllowDisableKeyguard == ALLOW_DISABLE_YES) {
+                mPolicy.enableKeyguard(false);
+            } else {
+                Log.v(TAG, "Not disabling keyguard since device policy is enforced");
+            }
+        }
+
+        @Override
+        public void released() {
+            mPolicy.enableKeyguard(true);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/PointerEventDispatcher.java b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
new file mode 100644
index 0000000..6b0e4c9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@@ -0,0 +1,90 @@
+/*
+ * 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.wm;
+
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy.PointerEventListener;
+
+import com.android.server.UiThread;
+
+import java.util.ArrayList;
+
+public class PointerEventDispatcher extends InputEventReceiver {
+    ArrayList<PointerEventListener> mListeners = new ArrayList<PointerEventListener>();
+    PointerEventListener[] mListenersArray = new PointerEventListener[0];
+
+    public PointerEventDispatcher(InputChannel inputChannel) {
+        super(inputChannel, UiThread.getHandler().getLooper());
+    }
+
+    @Override
+    public void onInputEvent(InputEvent event) {
+        try {
+            if (event instanceof MotionEvent
+                    && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                final MotionEvent motionEvent = (MotionEvent)event;
+                PointerEventListener[] listeners;
+                synchronized (mListeners) {
+                    if (mListenersArray == null) {
+                        mListenersArray = new PointerEventListener[mListeners.size()];
+                        mListeners.toArray(mListenersArray);
+                    }
+                    listeners = mListenersArray;
+                }
+                for (int i = 0; i < listeners.length; ++i) {
+                    listeners[i].onPointerEvent(motionEvent);
+                }
+            }
+        } finally {
+            finishInputEvent(event, false);
+        }
+    }
+
+    /**
+     * Add the specified listener to the list.
+     * @param listener The listener to add.
+     */
+    public void registerInputEventListener(PointerEventListener listener) {
+        synchronized (mListeners) {
+            if (mListeners.contains(listener)) {
+                throw new IllegalStateException("registerInputEventListener: trying to register" +
+                        listener + " twice.");
+            }
+            mListeners.add(listener);
+            mListenersArray = null;
+        }
+    }
+
+    /**
+     * Remove the specified listener from the list.
+     * @param listener The listener to remove.
+     */
+    public void unregisterInputEventListener(PointerEventListener listener) {
+        synchronized (mListeners) {
+            if (!mListeners.contains(listener)) {
+                throw new IllegalStateException("registerInputEventListener: " + listener +
+                        " not registered.");
+            }
+            mListeners.remove(listener);
+            mListenersArray = null;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
new file mode 100644
index 0000000..e630737
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (C) 2010 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.wm;
+
+import java.io.PrintWriter;
+
+import static com.android.server.wm.WindowStateAnimator.SurfaceTrace;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+class ScreenRotationAnimation {
+    static final String TAG = "ScreenRotationAnimation";
+    static final boolean DEBUG_STATE = false;
+    static final boolean DEBUG_TRANSFORMS = false;
+    static final boolean TWO_PHASE_ANIMATION = false;
+    static final boolean USE_CUSTOM_BLACK_FRAME = false;
+
+    static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200;
+
+    final Context mContext;
+    final DisplayContent mDisplayContent;
+    SurfaceControl mSurfaceControl;
+    BlackFrame mCustomBlackFrame;
+    BlackFrame mExitingBlackFrame;
+    BlackFrame mEnteringBlackFrame;
+    int mWidth, mHeight;
+
+    int mOriginalRotation;
+    int mOriginalWidth, mOriginalHeight;
+    int mCurRotation;
+    Rect mOriginalDisplayRect = new Rect();
+    Rect mCurrentDisplayRect = new Rect();
+
+    // For all animations, "exit" is for the UI elements that are going
+    // away (that is the snapshot of the old screen), and "enter" is for
+    // the new UI elements that are appearing (that is the active windows
+    // in their final orientation).
+
+    // The starting animation for the exiting and entering elements.  This
+    // animation applies a transformation while the rotation is in progress.
+    // It is started immediately, before the new entering UI is ready.
+    Animation mStartExitAnimation;
+    final Transformation mStartExitTransformation = new Transformation();
+    Animation mStartEnterAnimation;
+    final Transformation mStartEnterTransformation = new Transformation();
+    Animation mStartFrameAnimation;
+    final Transformation mStartFrameTransformation = new Transformation();
+
+    // The finishing animation for the exiting and entering elements.  This
+    // animation needs to undo the transformation of the starting animation.
+    // It starts running once the new rotation UI elements are ready to be
+    // displayed.
+    Animation mFinishExitAnimation;
+    final Transformation mFinishExitTransformation = new Transformation();
+    Animation mFinishEnterAnimation;
+    final Transformation mFinishEnterTransformation = new Transformation();
+    Animation mFinishFrameAnimation;
+    final Transformation mFinishFrameTransformation = new Transformation();
+
+    // The current active animation to move from the old to the new rotated
+    // state.  Which animation is run here will depend on the old and new
+    // rotations.
+    Animation mRotateExitAnimation;
+    final Transformation mRotateExitTransformation = new Transformation();
+    Animation mRotateEnterAnimation;
+    final Transformation mRotateEnterTransformation = new Transformation();
+    Animation mRotateFrameAnimation;
+    final Transformation mRotateFrameTransformation = new Transformation();
+
+    // A previously running rotate animation.  This will be used if we need
+    // to switch to a new rotation before finishing the previous one.
+    Animation mLastRotateExitAnimation;
+    final Transformation mLastRotateExitTransformation = new Transformation();
+    Animation mLastRotateEnterAnimation;
+    final Transformation mLastRotateEnterTransformation = new Transformation();
+    Animation mLastRotateFrameAnimation;
+    final Transformation mLastRotateFrameTransformation = new Transformation();
+
+    // Complete transformations being applied.
+    final Transformation mExitTransformation = new Transformation();
+    final Transformation mEnterTransformation = new Transformation();
+    final Transformation mFrameTransformation = new Transformation();
+
+    boolean mStarted;
+    boolean mAnimRunning;
+    boolean mFinishAnimReady;
+    long mFinishAnimStartTime;
+    boolean mForceDefaultOrientation;
+
+    final Matrix mFrameInitialMatrix = new Matrix();
+    final Matrix mSnapshotInitialMatrix = new Matrix();
+    final Matrix mSnapshotFinalMatrix = new Matrix();
+    final Matrix mExitFrameFinalMatrix = new Matrix();
+    final Matrix mTmpMatrix = new Matrix();
+    final float[] mTmpFloats = new float[9];
+    private boolean mMoreRotateEnter;
+    private boolean mMoreRotateExit;
+    private boolean mMoreRotateFrame;
+    private boolean mMoreFinishEnter;
+    private boolean mMoreFinishExit;
+    private boolean mMoreFinishFrame;
+    private boolean mMoreStartEnter;
+    private boolean mMoreStartExit;
+    private boolean mMoreStartFrame;
+    long mHalfwayPoint;
+
+    public void printTo(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mSurface="); pw.print(mSurfaceControl);
+                pw.print(" mWidth="); pw.print(mWidth);
+                pw.print(" mHeight="); pw.println(mHeight);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            pw.print(prefix); pw.print("mCustomBlackFrame="); pw.println(mCustomBlackFrame);
+            if (mCustomBlackFrame != null) {
+                mCustomBlackFrame.printTo(prefix + "  ", pw);
+            }
+        }
+        pw.print(prefix); pw.print("mExitingBlackFrame="); pw.println(mExitingBlackFrame);
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.printTo(prefix + "  ", pw);
+        }
+        pw.print(prefix); pw.print("mEnteringBlackFrame="); pw.println(mEnteringBlackFrame);
+        if (mEnteringBlackFrame != null) {
+            mEnteringBlackFrame.printTo(prefix + "  ", pw);
+        }
+        pw.print(prefix); pw.print("mCurRotation="); pw.print(mCurRotation);
+                pw.print(" mOriginalRotation="); pw.println(mOriginalRotation);
+        pw.print(prefix); pw.print("mOriginalWidth="); pw.print(mOriginalWidth);
+                pw.print(" mOriginalHeight="); pw.println(mOriginalHeight);
+        pw.print(prefix); pw.print("mStarted="); pw.print(mStarted);
+                pw.print(" mAnimRunning="); pw.print(mAnimRunning);
+                pw.print(" mFinishAnimReady="); pw.print(mFinishAnimReady);
+                pw.print(" mFinishAnimStartTime="); pw.println(mFinishAnimStartTime);
+        pw.print(prefix); pw.print("mStartExitAnimation="); pw.print(mStartExitAnimation);
+                pw.print(" "); mStartExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mStartEnterAnimation="); pw.print(mStartEnterAnimation);
+                pw.print(" "); mStartEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mStartFrameAnimation="); pw.print(mStartFrameAnimation);
+                pw.print(" "); mStartFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishExitAnimation="); pw.print(mFinishExitAnimation);
+                pw.print(" "); mFinishExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishEnterAnimation="); pw.print(mFinishEnterAnimation);
+                pw.print(" "); mFinishEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFinishFrameAnimation="); pw.print(mFinishFrameAnimation);
+                pw.print(" "); mFinishFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateExitAnimation="); pw.print(mRotateExitAnimation);
+                pw.print(" "); mRotateExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateEnterAnimation="); pw.print(mRotateEnterAnimation);
+                pw.print(" "); mRotateEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mRotateFrameAnimation="); pw.print(mRotateFrameAnimation);
+                pw.print(" "); mRotateFrameTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mExitTransformation=");
+                mExitTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mEnterTransformation=");
+                mEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFrameTransformation=");
+                mEnterTransformation.printShortString(pw); pw.println();
+        pw.print(prefix); pw.print("mFrameInitialMatrix=");
+                mFrameInitialMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mSnapshotInitialMatrix=");
+                mSnapshotInitialMatrix.printShortString(pw);
+                pw.print(" mSnapshotFinalMatrix="); mSnapshotFinalMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mExitFrameFinalMatrix=");
+                mExitFrameFinalMatrix.printShortString(pw);
+                pw.println();
+        pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation);
+        if (mForceDefaultOrientation) {
+            pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString());
+            pw.print(" mCurrentDisplayRect="); pw.println(mCurrentDisplayRect.toShortString());
+        }
+    }
+
+    public ScreenRotationAnimation(Context context, DisplayContent displayContent,
+            SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation) {
+        mContext = context;
+        mDisplayContent = displayContent;
+        displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
+
+        // Screenshot does NOT include rotation!
+        final Display display = displayContent.getDisplay();
+        int originalRotation = display.getRotation();
+        final int originalWidth;
+        final int originalHeight;
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        if (forceDefaultOrientation) {
+            // Emulated orientation.
+            mForceDefaultOrientation = true;
+            originalWidth = displayContent.mBaseDisplayWidth;
+            originalHeight = displayContent.mBaseDisplayHeight;
+        } else {
+            // Normal situation
+            originalWidth = displayInfo.logicalWidth;
+            originalHeight = displayInfo.logicalHeight;
+        }
+        if (originalRotation == Surface.ROTATION_90
+                || originalRotation == Surface.ROTATION_270) {
+            mWidth = originalHeight;
+            mHeight = originalWidth;
+        } else {
+            mWidth = originalWidth;
+            mHeight = originalHeight;
+        }
+
+        mOriginalRotation = originalRotation;
+        mOriginalWidth = originalWidth;
+        mOriginalHeight = originalHeight;
+
+        if (!inTransaction) {
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation");
+            SurfaceControl.openTransaction();
+        }
+
+        try {
+            try {
+                if (WindowManagerService.DEBUG_SURFACE_TRACE) {
+                    mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
+                            mWidth, mHeight,
+                            PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
+                    Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
+                            + mOriginalDisplayRect.toShortString());
+                } else {
+                    mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
+                            mWidth, mHeight,
+                            PixelFormat.OPAQUE, SurfaceControl.HIDDEN);
+                }
+                // capture a screenshot into the surface we just created
+                Surface sur = new Surface();
+                sur.copyFrom(mSurfaceControl);
+                // FIXME: we should use the proper display
+                SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
+                        SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
+                mSurfaceControl.setLayerStack(display.getLayerStack());
+                mSurfaceControl.setLayer(FREEZE_LAYER + 1);
+                mSurfaceControl.setAlpha(0);
+                mSurfaceControl.show();
+                sur.destroy();
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate freeze surface", e);
+            }
+
+            if (WindowManagerService.SHOW_TRANSACTIONS ||
+                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
+                            "  FREEZE " + mSurfaceControl + ": CREATE");
+
+            setRotationInTransaction(originalRotation);
+        } finally {
+            if (!inTransaction) {
+                SurfaceControl.closeTransaction();
+                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(WindowManagerService.TAG,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation");
+            }
+        }
+    }
+
+    boolean hasScreenshot() {
+        return mSurfaceControl != null;
+    }
+
+    static int deltaRotation(int oldRotation, int newRotation) {
+        int delta = newRotation - oldRotation;
+        if (delta < 0) delta += 4;
+        return delta;
+    }
+
+    private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) {
+        if (mSurfaceControl != null) {
+            matrix.getValues(mTmpFloats);
+            float x = mTmpFloats[Matrix.MTRANS_X];
+            float y = mTmpFloats[Matrix.MTRANS_Y];
+            if (mForceDefaultOrientation) {
+                mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect);
+                x -= mCurrentDisplayRect.left;
+                y -= mCurrentDisplayRect.top;
+            }
+            mSurfaceControl.setPosition(x, y);
+            mSurfaceControl.setMatrix(
+                    mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y],
+                    mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]);
+            mSurfaceControl.setAlpha(alpha);
+            if (DEBUG_TRANSFORMS) {
+                float[] srcPnts = new float[] { 0, 0, mWidth, mHeight };
+                float[] dstPnts = new float[4];
+                matrix.mapPoints(dstPnts, srcPnts);
+                Slog.i(TAG, "Original  : (" + srcPnts[0] + "," + srcPnts[1]
+                        + ")-(" + srcPnts[2] + "," + srcPnts[3] + ")");
+                Slog.i(TAG, "Transformed: (" + dstPnts[0] + "," + dstPnts[1]
+                        + ")-(" + dstPnts[2] + "," + dstPnts[3] + ")");
+            }
+        }
+    }
+
+    public static void createRotationMatrix(int rotation, int width, int height,
+            Matrix outMatrix) {
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                outMatrix.reset();
+                break;
+            case Surface.ROTATION_90:
+                outMatrix.setRotate(90, 0, 0);
+                outMatrix.postTranslate(height, 0);
+                break;
+            case Surface.ROTATION_180:
+                outMatrix.setRotate(180, 0, 0);
+                outMatrix.postTranslate(width, height);
+                break;
+            case Surface.ROTATION_270:
+                outMatrix.setRotate(270, 0, 0);
+                outMatrix.postTranslate(0, width);
+                break;
+        }
+    }
+
+    // Must be called while in a transaction.
+    private void setRotationInTransaction(int rotation) {
+        mCurRotation = rotation;
+
+        // Compute the transformation matrix that must be applied
+        // to the snapshot to make it stay in the same original position
+        // with the current screen rotation.
+        int delta = deltaRotation(rotation, Surface.ROTATION_0);
+        createRotationMatrix(delta, mWidth, mHeight, mSnapshotInitialMatrix);
+
+        if (DEBUG_STATE) Slog.v(TAG, "**** ROTATION: " + delta);
+        setSnapshotTransformInTransaction(mSnapshotInitialMatrix, 1.0f);
+    }
+
+    // Must be called while in a transaction.
+    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+            long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
+        setRotationInTransaction(rotation);
+        if (TWO_PHASE_ANIMATION) {
+            return startAnimation(session, maxAnimationDuration, animationScale,
+                    finalWidth, finalHeight, false, 0, 0);
+        }
+
+        // Don't start animation yet.
+        return false;
+    }
+
+    /**
+     * Returns true if animating.
+     */
+    private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+            float animationScale, int finalWidth, int finalHeight, boolean dismissing,
+            int exitAnim, int enterAnim) {
+        if (mSurfaceControl == null) {
+            // Can't do animation.
+            return false;
+        }
+        if (mStarted) {
+            return true;
+        }
+
+        mStarted = true;
+
+        boolean firstStart = false;
+
+        // Figure out how the screen has moved from the original rotation.
+        int delta = deltaRotation(mCurRotation, mOriginalRotation);
+
+        if (TWO_PHASE_ANIMATION && mFinishExitAnimation == null
+                && (!dismissing || delta != Surface.ROTATION_0)) {
+            if (DEBUG_STATE) Slog.v(TAG, "Creating start and finish animations");
+            firstStart = true;
+            mStartExitAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_start_exit);
+            mStartEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_start_enter);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_start_frame);
+            }
+            mFinishExitAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_finish_exit);
+            mFinishEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                    com.android.internal.R.anim.screen_rotate_finish_enter);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mFinishFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                        com.android.internal.R.anim.screen_rotate_finish_frame);
+            }
+        }
+
+        if (DEBUG_STATE) Slog.v(TAG, "Rotation delta: " + delta + " finalWidth="
+                + finalWidth + " finalHeight=" + finalHeight
+                + " origWidth=" + mOriginalWidth + " origHeight=" + mOriginalHeight);
+
+        final boolean customAnim;
+        if (exitAnim != 0 && enterAnim != 0) {
+            customAnim = true;
+            mRotateExitAnimation = AnimationUtils.loadAnimation(mContext, exitAnim);
+            mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext, enterAnim);
+        } else {
+            customAnim = false;
+            switch (delta) {
+                case Surface.ROTATION_0:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_0_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_0_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_0_frame);
+                    }
+                    break;
+                case Surface.ROTATION_90:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_plus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_plus_90_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_plus_90_frame);
+                    }
+                    break;
+                case Surface.ROTATION_180:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_180_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_180_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_180_frame);
+                    }
+                    break;
+                case Surface.ROTATION_270:
+                    mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_minus_90_exit);
+                    mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
+                            com.android.internal.R.anim.screen_rotate_minus_90_enter);
+                    if (USE_CUSTOM_BLACK_FRAME) {
+                        mRotateFrameAnimation = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.screen_rotate_minus_90_frame);
+                    }
+                    break;
+            }
+        }
+
+        // Initialize the animations.  This is a hack, redefining what "parent"
+        // means to allow supplying the last and next size.  In this definition
+        // "%p" is the original (let's call it "previous") size, and "%" is the
+        // screen's current/new size.
+        if (TWO_PHASE_ANIMATION && firstStart) {
+            // Compute partial steps between original and final sizes.  These
+            // are used for the dimensions of the exiting and entering elements,
+            // so they are never stretched too significantly.
+            final int halfWidth = (finalWidth + mOriginalWidth) / 2;
+            final int halfHeight = (finalHeight + mOriginalHeight) / 2;
+
+            if (DEBUG_STATE) Slog.v(TAG, "Initializing start and finish animations");
+            mStartEnterAnimation.initialize(finalWidth, finalHeight,
+                    halfWidth, halfHeight);
+            mStartExitAnimation.initialize(halfWidth, halfHeight,
+                    mOriginalWidth, mOriginalHeight);
+            mFinishEnterAnimation.initialize(finalWidth, finalHeight,
+                    halfWidth, halfHeight);
+            mFinishExitAnimation.initialize(halfWidth, halfHeight,
+                    mOriginalWidth, mOriginalHeight);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation.initialize(finalWidth, finalHeight,
+                        mOriginalWidth, mOriginalHeight);
+                mFinishFrameAnimation.initialize(finalWidth, finalHeight,
+                        mOriginalWidth, mOriginalHeight);
+            }
+        }
+        mRotateEnterAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        mRotateExitAnimation.initialize(finalWidth, finalHeight, mOriginalWidth, mOriginalHeight);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mRotateFrameAnimation.initialize(finalWidth, finalHeight, mOriginalWidth,
+                    mOriginalHeight);
+        }
+        mAnimRunning = false;
+        mFinishAnimReady = false;
+        mFinishAnimStartTime = -1;
+
+        if (TWO_PHASE_ANIMATION && firstStart) {
+            mStartExitAnimation.restrictDuration(maxAnimationDuration);
+            mStartExitAnimation.scaleCurrentDuration(animationScale);
+            mStartEnterAnimation.restrictDuration(maxAnimationDuration);
+            mStartEnterAnimation.scaleCurrentDuration(animationScale);
+            mFinishExitAnimation.restrictDuration(maxAnimationDuration);
+            mFinishExitAnimation.scaleCurrentDuration(animationScale);
+            mFinishEnterAnimation.restrictDuration(maxAnimationDuration);
+            mFinishEnterAnimation.scaleCurrentDuration(animationScale);
+            if (USE_CUSTOM_BLACK_FRAME) {
+                mStartFrameAnimation.restrictDuration(maxAnimationDuration);
+                mStartFrameAnimation.scaleCurrentDuration(animationScale);
+                mFinishFrameAnimation.restrictDuration(maxAnimationDuration);
+                mFinishFrameAnimation.scaleCurrentDuration(animationScale);
+            }
+        }
+        mRotateExitAnimation.restrictDuration(maxAnimationDuration);
+        mRotateExitAnimation.scaleCurrentDuration(animationScale);
+        mRotateEnterAnimation.restrictDuration(maxAnimationDuration);
+        mRotateEnterAnimation.scaleCurrentDuration(animationScale);
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mRotateFrameAnimation.restrictDuration(maxAnimationDuration);
+            mRotateFrameAnimation.scaleCurrentDuration(animationScale);
+        }
+
+        final int layerStack = mDisplayContent.getDisplay().getLayerStack();
+        if (USE_CUSTOM_BLACK_FRAME && mCustomBlackFrame == null) {
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    WindowManagerService.TAG,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            SurfaceControl.openTransaction();
+
+            // Compute the transformation matrix that must be applied
+            // the the black frame to make it stay in the initial position
+            // before the new screen rotation.  This is different than the
+            // snapshot transformation because the snapshot is always based
+            // of the native orientation of the screen, not the orientation
+            // we were last in.
+            createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+            try {
+                Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
+                        mOriginalWidth*2, mOriginalHeight*2);
+                Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
+                mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3,
+                        layerStack, false);
+                mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        WindowManagerService.TAG,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        if (!customAnim && mExitingBlackFrame == null) {
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    WindowManagerService.TAG,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            SurfaceControl.openTransaction();
+            try {
+                // Compute the transformation matrix that must be applied
+                // the the black frame to make it stay in the initial position
+                // before the new screen rotation.  This is different than the
+                // snapshot transformation because the snapshot is always based
+                // of the native orientation of the screen, not the orientation
+                // we were last in.
+                createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix);
+
+                final Rect outer;
+                final Rect inner;
+                if (mForceDefaultOrientation) {
+                    // Going from a smaller Display to a larger Display, add curtains to sides
+                    // or top and bottom. Going from a larger to smaller display will result in
+                    // no BlackSurfaces being constructed.
+                    outer = mCurrentDisplayRect;
+                    inner = mOriginalDisplayRect;
+                } else {
+                    outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
+                            mOriginalWidth*2, mOriginalHeight*2);
+                    inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
+                }
+                mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2,
+                        layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        WindowManagerService.TAG,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        if (customAnim && mEnteringBlackFrame == null) {
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                    WindowManagerService.TAG,
+                    ">>> OPEN TRANSACTION ScreenRotationAnimation.startAnimation");
+            SurfaceControl.openTransaction();
+
+            try {
+                Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
+                        finalWidth*2, finalHeight*2);
+                Rect inner = new Rect(0, 0, finalWidth, finalHeight);
+                mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER,
+                        layerStack, false);
+            } catch (OutOfResourcesException e) {
+                Slog.w(TAG, "Unable to allocate black surface", e);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i(
+                        WindowManagerService.TAG,
+                        "<<< CLOSE TRANSACTION ScreenRotationAnimation.startAnimation");
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns true if animating.
+     */
+    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+            float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
+        if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
+        if (mSurfaceControl == null) {
+            // Can't do animation.
+            return false;
+        }
+        if (!mStarted) {
+            startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+                    true, exitAnim, enterAnim);
+        }
+        if (!mStarted) {
+            return false;
+        }
+        if (DEBUG_STATE) Slog.v(TAG, "Setting mFinishAnimReady = true");
+        mFinishAnimReady = true;
+        return true;
+    }
+
+    public void kill() {
+        if (DEBUG_STATE) Slog.v(TAG, "Kill!");
+        if (mSurfaceControl != null) {
+            if (WindowManagerService.SHOW_TRANSACTIONS ||
+                    WindowManagerService.SHOW_SURFACE_ALLOC) Slog.i(WindowManagerService.TAG,
+                            "  FREEZE " + mSurfaceControl + ": DESTROY");
+            mSurfaceControl.destroy();
+            mSurfaceControl = null;
+        }
+        if (mCustomBlackFrame != null) {
+            mCustomBlackFrame.kill();
+            mCustomBlackFrame = null;
+        }
+        if (mExitingBlackFrame != null) {
+            mExitingBlackFrame.kill();
+            mExitingBlackFrame = null;
+        }
+        if (mEnteringBlackFrame != null) {
+            mEnteringBlackFrame.kill();
+            mEnteringBlackFrame = null;
+        }
+        if (TWO_PHASE_ANIMATION) {
+            if (mStartExitAnimation != null) {
+                mStartExitAnimation.cancel();
+                mStartExitAnimation = null;
+            }
+            if (mStartEnterAnimation != null) {
+                mStartEnterAnimation.cancel();
+                mStartEnterAnimation = null;
+            }
+            if (mFinishExitAnimation != null) {
+                mFinishExitAnimation.cancel();
+                mFinishExitAnimation = null;
+            }
+            if (mFinishEnterAnimation != null) {
+                mFinishEnterAnimation.cancel();
+                mFinishEnterAnimation = null;
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            if (mStartFrameAnimation != null) {
+                mStartFrameAnimation.cancel();
+                mStartFrameAnimation = null;
+            }
+            if (mRotateFrameAnimation != null) {
+                mRotateFrameAnimation.cancel();
+                mRotateFrameAnimation = null;
+            }
+            if (mFinishFrameAnimation != null) {
+                mFinishFrameAnimation.cancel();
+                mFinishFrameAnimation = null;
+            }
+        }
+        if (mRotateExitAnimation != null) {
+            mRotateExitAnimation.cancel();
+            mRotateExitAnimation = null;
+        }
+        if (mRotateEnterAnimation != null) {
+            mRotateEnterAnimation.cancel();
+            mRotateEnterAnimation = null;
+        }
+    }
+
+    public boolean isAnimating() {
+        return hasAnimations() || (TWO_PHASE_ANIMATION && mFinishAnimReady);
+    }
+
+    public boolean isRotating() {
+        return mCurRotation != mOriginalRotation;
+    }
+
+    private boolean hasAnimations() {
+        return (TWO_PHASE_ANIMATION &&
+                    (mStartEnterAnimation != null || mStartExitAnimation != null
+                    || mFinishEnterAnimation != null || mFinishExitAnimation != null))
+                || (USE_CUSTOM_BLACK_FRAME &&
+                        (mStartFrameAnimation != null || mRotateFrameAnimation != null
+                        || mFinishFrameAnimation != null))
+                || mRotateEnterAnimation != null || mRotateExitAnimation != null;
+    }
+
+    private boolean stepAnimation(long now) {
+        if (now > mHalfwayPoint) {
+            mHalfwayPoint = Long.MAX_VALUE;
+        }
+        if (mFinishAnimReady && mFinishAnimStartTime < 0) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: finish anim now ready");
+            mFinishAnimStartTime = now;
+        }
+
+        if (TWO_PHASE_ANIMATION) {
+            mMoreStartExit = false;
+            if (mStartExitAnimation != null) {
+                mMoreStartExit = mStartExitAnimation.getTransformation(now, mStartExitTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start exit: " + mStartExitTransformation);
+            }
+
+            mMoreStartEnter = false;
+            if (mStartEnterAnimation != null) {
+                mMoreStartEnter = mStartEnterAnimation.getTransformation(now, mStartEnterTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start enter: " + mStartEnterTransformation);
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreStartFrame = false;
+            if (mStartFrameAnimation != null) {
+                mMoreStartFrame = mStartFrameAnimation.getTransformation(now, mStartFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped start frame: " + mStartFrameTransformation);
+            }
+        }
+
+        long finishNow = mFinishAnimReady ? (now - mFinishAnimStartTime) : 0;
+        if (DEBUG_STATE) Slog.v(TAG, "Step: finishNow=" + finishNow);
+
+        if (TWO_PHASE_ANIMATION) {
+            mMoreFinishExit = false;
+            if (mFinishExitAnimation != null) {
+                mMoreFinishExit = mFinishExitAnimation.getTransformation(finishNow, mFinishExitTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish exit: " + mFinishExitTransformation);
+            }
+
+            mMoreFinishEnter = false;
+            if (mFinishEnterAnimation != null) {
+                mMoreFinishEnter = mFinishEnterAnimation.getTransformation(finishNow, mFinishEnterTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish enter: " + mFinishEnterTransformation);
+            }
+        }
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreFinishFrame = false;
+            if (mFinishFrameAnimation != null) {
+                mMoreFinishFrame = mFinishFrameAnimation.getTransformation(finishNow, mFinishFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped finish frame: " + mFinishFrameTransformation);
+            }
+        }
+
+        mMoreRotateExit = false;
+        if (mRotateExitAnimation != null) {
+            mMoreRotateExit = mRotateExitAnimation.getTransformation(now, mRotateExitTransformation);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate exit: " + mRotateExitTransformation);
+        }
+
+        mMoreRotateEnter = false;
+        if (mRotateEnterAnimation != null) {
+            mMoreRotateEnter = mRotateEnterAnimation.getTransformation(now, mRotateEnterTransformation);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate enter: " + mRotateEnterTransformation);
+        }
+
+        if (USE_CUSTOM_BLACK_FRAME) {
+            mMoreRotateFrame = false;
+            if (mRotateFrameAnimation != null) {
+                mMoreRotateFrame = mRotateFrameAnimation.getTransformation(now, mRotateFrameTransformation);
+                if (DEBUG_TRANSFORMS) Slog.v(TAG, "Stepped rotate frame: " + mRotateFrameTransformation);
+            }
+        }
+
+        if (!mMoreRotateExit && (!TWO_PHASE_ANIMATION || (!mMoreStartExit && !mMoreFinishExit))) {
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartExitAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing start exit anim!");
+                    mStartExitAnimation.cancel();
+                    mStartExitAnimation = null;
+                    mStartExitTransformation.clear();
+                }
+                if (mFinishExitAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing finish exit anim!");
+                    mFinishExitAnimation.cancel();
+                    mFinishExitAnimation = null;
+                    mFinishExitTransformation.clear();
+                }
+            }
+            if (mRotateExitAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, clearing rotate exit anim!");
+                mRotateExitAnimation.cancel();
+                mRotateExitAnimation = null;
+                mRotateExitTransformation.clear();
+            }
+        }
+
+        if (!mMoreRotateEnter && (!TWO_PHASE_ANIMATION || (!mMoreStartEnter && !mMoreFinishEnter))) {
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartEnterAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing start enter anim!");
+                    mStartEnterAnimation.cancel();
+                    mStartEnterAnimation = null;
+                    mStartEnterTransformation.clear();
+                }
+                if (mFinishEnterAnimation != null) {
+                    if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing finish enter anim!");
+                    mFinishEnterAnimation.cancel();
+                    mFinishEnterAnimation = null;
+                    mFinishEnterTransformation.clear();
+                }
+            }
+            if (mRotateEnterAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Enter animations done, clearing rotate enter anim!");
+                mRotateEnterAnimation.cancel();
+                mRotateEnterAnimation = null;
+                mRotateEnterTransformation.clear();
+            }
+        }
+
+        if (USE_CUSTOM_BLACK_FRAME && !mMoreStartFrame && !mMoreRotateFrame && !mMoreFinishFrame) {
+            if (mStartFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing start frame anim!");
+                mStartFrameAnimation.cancel();
+                mStartFrameAnimation = null;
+                mStartFrameTransformation.clear();
+            }
+            if (mFinishFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing finish frame anim!");
+                mFinishFrameAnimation.cancel();
+                mFinishFrameAnimation = null;
+                mFinishFrameTransformation.clear();
+            }
+            if (mRotateFrameAnimation != null) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, clearing rotate frame anim!");
+                mRotateFrameAnimation.cancel();
+                mRotateFrameAnimation = null;
+                mRotateFrameTransformation.clear();
+            }
+        }
+
+        mExitTransformation.set(mRotateExitTransformation);
+        mEnterTransformation.set(mRotateEnterTransformation);
+        if (TWO_PHASE_ANIMATION) {
+            mExitTransformation.compose(mStartExitTransformation);
+            mExitTransformation.compose(mFinishExitTransformation);
+
+            mEnterTransformation.compose(mStartEnterTransformation);
+            mEnterTransformation.compose(mFinishEnterTransformation);
+        }
+
+        if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final exit: " + mExitTransformation);
+        if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final enter: " + mEnterTransformation);
+
+        if (USE_CUSTOM_BLACK_FRAME) {
+            //mFrameTransformation.set(mRotateExitTransformation);
+            //mFrameTransformation.compose(mStartExitTransformation);
+            //mFrameTransformation.compose(mFinishExitTransformation);
+            mFrameTransformation.set(mRotateFrameTransformation);
+            mFrameTransformation.compose(mStartFrameTransformation);
+            mFrameTransformation.compose(mFinishFrameTransformation);
+            mFrameTransformation.getMatrix().preConcat(mFrameInitialMatrix);
+            if (DEBUG_TRANSFORMS) Slog.v(TAG, "Final frame: " + mFrameTransformation);
+        }
+
+        final boolean more = (TWO_PHASE_ANIMATION
+                    && (mMoreStartEnter || mMoreStartExit || mMoreFinishEnter || mMoreFinishExit))
+                || (USE_CUSTOM_BLACK_FRAME
+                        && (mMoreStartFrame || mMoreRotateFrame || mMoreFinishFrame))
+                || mMoreRotateEnter || mMoreRotateExit
+                || !mFinishAnimReady;
+
+        mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix);
+
+        if (DEBUG_STATE) Slog.v(TAG, "Step: more=" + more);
+
+        return more;
+    }
+
+    void updateSurfacesInTransaction() {
+        if (!mStarted) {
+            return;
+        }
+
+        if (mSurfaceControl != null) {
+            if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
+                if (DEBUG_STATE) Slog.v(TAG, "Exit animations done, hiding screenshot surface");
+                mSurfaceControl.hide();
+            }
+        }
+
+        if (mCustomBlackFrame != null) {
+            if (!mMoreStartFrame && !mMoreFinishFrame && !mMoreRotateFrame) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding black frame");
+                mCustomBlackFrame.hide();
+            } else {
+                mCustomBlackFrame.setMatrix(mFrameTransformation.getMatrix());
+            }
+        }
+
+        if (mExitingBlackFrame != null) {
+            if (!mMoreStartExit && !mMoreFinishExit && !mMoreRotateExit) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding exiting frame");
+                mExitingBlackFrame.hide();
+            } else {
+                mExitFrameFinalMatrix.setConcat(mExitTransformation.getMatrix(), mFrameInitialMatrix);
+                mExitingBlackFrame.setMatrix(mExitFrameFinalMatrix);
+                if (mForceDefaultOrientation) {
+                    mExitingBlackFrame.setAlpha(mExitTransformation.getAlpha());
+                }
+            }
+        }
+
+        if (mEnteringBlackFrame != null) {
+            if (!mMoreStartEnter && !mMoreFinishEnter && !mMoreRotateEnter) {
+                if (DEBUG_STATE) Slog.v(TAG, "Frame animations done, hiding entering frame");
+                mEnteringBlackFrame.hide();
+            } else {
+                mEnteringBlackFrame.setMatrix(mEnterTransformation.getMatrix());
+            }
+        }
+
+        setSnapshotTransformInTransaction(mSnapshotFinalMatrix, mExitTransformation.getAlpha());
+    }
+
+    public boolean stepAnimationLocked(long now) {
+        if (!hasAnimations()) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: no animations running");
+            mFinishAnimReady = false;
+            return false;
+        }
+
+        if (!mAnimRunning) {
+            if (DEBUG_STATE) Slog.v(TAG, "Step: starting start, finish, rotate");
+            if (TWO_PHASE_ANIMATION) {
+                if (mStartEnterAnimation != null) {
+                    mStartEnterAnimation.setStartTime(now);
+                }
+                if (mStartExitAnimation != null) {
+                    mStartExitAnimation.setStartTime(now);
+                }
+                if (mFinishEnterAnimation != null) {
+                    mFinishEnterAnimation.setStartTime(0);
+                }
+                if (mFinishExitAnimation != null) {
+                    mFinishExitAnimation.setStartTime(0);
+                }
+            }
+            if (USE_CUSTOM_BLACK_FRAME) {
+                if (mStartFrameAnimation != null) {
+                    mStartFrameAnimation.setStartTime(now);
+                }
+                if (mFinishFrameAnimation != null) {
+                    mFinishFrameAnimation.setStartTime(0);
+                }
+                if (mRotateFrameAnimation != null) {
+                    mRotateFrameAnimation.setStartTime(now);
+                }
+            }
+            if (mRotateEnterAnimation != null) {
+                mRotateEnterAnimation.setStartTime(now);
+            }
+            if (mRotateExitAnimation != null) {
+                mRotateExitAnimation.setStartTime(now);
+            }
+            mAnimRunning = true;
+            mHalfwayPoint = now + mRotateEnterAnimation.getDuration() / 2;
+        }
+
+        return stepAnimation(now);
+    }
+
+    public Transformation getEnterTransformation() {
+        return mEnterTransformation;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
new file mode 100644
index 0000000..87cabc9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.view.IWindowId;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.server.wm.WindowManagerService.H;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+
+import java.io.PrintWriter;
+
+/**
+ * This class represents an active client session.  There is generally one
+ * Session object per process that is interacting with the window manager.
+ */
+final class Session extends IWindowSession.Stub
+        implements IBinder.DeathRecipient {
+    final WindowManagerService mService;
+    final IInputMethodClient mClient;
+    final IInputContext mInputContext;
+    final int mUid;
+    final int mPid;
+    final String mStringName;
+    SurfaceSession mSurfaceSession;
+    int mNumWindow = 0;
+    boolean mClientDead = false;
+
+    public Session(WindowManagerService service, IInputMethodClient client,
+            IInputContext inputContext) {
+        mService = service;
+        mClient = client;
+        mInputContext = inputContext;
+        mUid = Binder.getCallingUid();
+        mPid = Binder.getCallingPid();
+        StringBuilder sb = new StringBuilder();
+        sb.append("Session{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(" ");
+        sb.append(mPid);
+        if (mUid < Process.FIRST_APPLICATION_UID) {
+            sb.append(":");
+            sb.append(mUid);
+        } else {
+            sb.append(":u");
+            sb.append(UserHandle.getUserId(mUid));
+            sb.append('a');
+            sb.append(UserHandle.getAppId(mUid));
+        }
+        sb.append("}");
+        mStringName = sb.toString();
+
+        synchronized (mService.mWindowMap) {
+            if (mService.mInputMethodManager == null && mService.mHaveInputMethods) {
+                IBinder b = ServiceManager.getService(
+                        Context.INPUT_METHOD_SERVICE);
+                mService.mInputMethodManager = IInputMethodManager.Stub.asInterface(b);
+            }
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            // Note: it is safe to call in to the input method manager
+            // here because we are not holding our lock.
+            if (mService.mInputMethodManager != null) {
+                mService.mInputMethodManager.addClient(client, inputContext,
+                        mUid, mPid);
+            } else {
+                client.setUsingInputMethod(false);
+            }
+            client.asBinder().linkToDeath(this, 0);
+        } catch (RemoteException e) {
+            // The caller has died, so we can just forget about this.
+            try {
+                if (mService.mInputMethodManager != null) {
+                    mService.mInputMethodManager.removeClient(client);
+                }
+            } catch (RemoteException ee) {
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // Log all 'real' exceptions thrown to the caller
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(WindowManagerService.TAG, "Window Session Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    public void binderDied() {
+        // Note: it is safe to call in to the input method manager
+        // here because we are not holding our lock.
+        try {
+            if (mService.mInputMethodManager != null) {
+                mService.mInputMethodManager.removeClient(mClient);
+            }
+        } catch (RemoteException e) {
+        }
+        synchronized(mService.mWindowMap) {
+            mClient.asBinder().unlinkToDeath(this, 0);
+            mClientDead = true;
+            killSessionLocked();
+        }
+    }
+
+    @Override
+    public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, Rect outContentInsets, InputChannel outInputChannel) {
+        return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,
+                outContentInsets, outInputChannel);
+    }
+
+    @Override
+    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, Rect outContentInsets,
+            InputChannel outInputChannel) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
+                outContentInsets, outInputChannel);
+    }
+
+    @Override
+    public int addWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, Rect outContentInsets) {
+        return addToDisplayWithoutInputChannel(window, seq, attrs, viewVisibility,
+                Display.DEFAULT_DISPLAY, outContentInsets);
+    }
+
+    @Override
+    public int addToDisplayWithoutInputChannel(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int viewVisibility, int displayId, Rect outContentInsets) {
+        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
+            outContentInsets, null);
+    }
+
+    public void remove(IWindow window) {
+        mService.removeWindow(this, window);
+    }
+
+    public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
+            int requestedWidth, int requestedHeight, int viewFlags,
+            int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+            Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
+        if (false) Slog.d(WindowManagerService.TAG, ">>>>>> ENTERED relayout from "
+                + Binder.getCallingPid());
+        int res = mService.relayoutWindow(this, window, seq, attrs,
+                requestedWidth, requestedHeight, viewFlags, flags,
+                outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
+                outConfig, outSurface);
+        if (false) Slog.d(WindowManagerService.TAG, "<<<<<< EXITING relayout to "
+                + Binder.getCallingPid());
+        return res;
+    }
+
+    public void performDeferredDestroy(IWindow window) {
+        mService.performDeferredDestroyWindow(this, window);
+    }
+
+    public boolean outOfMemory(IWindow window) {
+        return mService.outOfMemoryWindow(this, window);
+    }
+
+    public void setTransparentRegion(IWindow window, Region region) {
+        mService.setTransparentRegionWindow(this, window, region);
+    }
+
+    public void setInsets(IWindow window, int touchableInsets,
+            Rect contentInsets, Rect visibleInsets, Region touchableArea) {
+        mService.setInsetsWindow(this, window, touchableInsets, contentInsets,
+                visibleInsets, touchableArea);
+    }
+
+    public void getDisplayFrame(IWindow window, Rect outDisplayFrame) {
+        mService.getWindowDisplayFrame(this, window, outDisplayFrame);
+    }
+
+    public void finishDrawing(IWindow window) {
+        if (WindowManagerService.localLOGV) Slog.v(
+            WindowManagerService.TAG, "IWindow finishDrawing called for " + window);
+        mService.finishDrawingWindow(this, window);
+    }
+
+    public void setInTouchMode(boolean mode) {
+        synchronized(mService.mWindowMap) {
+            mService.mInTouchMode = mode;
+        }
+    }
+
+    public boolean getInTouchMode() {
+        synchronized(mService.mWindowMap) {
+            return mService.mInTouchMode;
+        }
+    }
+
+    public boolean performHapticFeedback(IWindow window, int effectId,
+            boolean always) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mService.mPolicy.performHapticFeedbackLw(
+                        mService.windowForClientLocked(this, window, true),
+                        effectId, always);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    /* Drag/drop */
+    public IBinder prepareDrag(IWindow window, int flags,
+            int width, int height, Surface outSurface) {
+        return mService.prepareDragSurface(window, mSurfaceSession, flags,
+                width, height, outSurface);
+    }
+
+    public boolean performDrag(IWindow window, IBinder dragToken,
+            float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+            ClipData data) {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "perform drag: win=" + window + " data=" + data);
+        }
+
+        synchronized (mService.mWindowMap) {
+            if (mService.mDragState == null) {
+                Slog.w(WindowManagerService.TAG, "No drag prepared");
+                throw new IllegalStateException("performDrag() without prepareDrag()");
+            }
+
+            if (dragToken != mService.mDragState.mToken) {
+                Slog.w(WindowManagerService.TAG, "Performing mismatched drag");
+                throw new IllegalStateException("performDrag() does not match prepareDrag()");
+            }
+
+            WindowState callingWin = mService.windowForClientLocked(null, window, false);
+            if (callingWin == null) {
+                Slog.w(WindowManagerService.TAG, "Bad requesting window " + window);
+                return false;  // !!! TODO: throw here?
+            }
+
+            // !!! TODO: if input is not still focused on the initiating window, fail
+            // the drag initiation (e.g. an alarm window popped up just as the application
+            // called performDrag()
+
+            mService.mH.removeMessages(H.DRAG_START_TIMEOUT, window.asBinder());
+
+            // !!! TODO: extract the current touch (x, y) in screen coordinates.  That
+            // will let us eliminate the (touchX,touchY) parameters from the API.
+
+            // !!! FIXME: put all this heavy stuff onto the mH looper, as well as
+            // the actual drag event dispatch stuff in the dragstate
+
+            Display display = callingWin.mDisplayContent.getDisplay();
+            mService.mDragState.register(display);
+            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+            if (!mService.mInputManager.transferTouchFocus(callingWin.mInputChannel,
+                    mService.mDragState.mServerChannel)) {
+                Slog.e(WindowManagerService.TAG, "Unable to transfer touch focus");
+                mService.mDragState.unregister();
+                mService.mDragState = null;
+                mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
+                return false;
+            }
+
+            mService.mDragState.mData = data;
+            mService.mDragState.mCurrentX = touchX;
+            mService.mDragState.mCurrentY = touchY;
+            mService.mDragState.broadcastDragStartedLw(touchX, touchY);
+
+            // remember the thumb offsets for later
+            mService.mDragState.mThumbOffsetX = thumbCenterX;
+            mService.mDragState.mThumbOffsetY = thumbCenterY;
+
+            // Make the surface visible at the proper location
+            final SurfaceControl surfaceControl = mService.mDragState.mSurfaceControl;
+            if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                    WindowManagerService.TAG, ">>> OPEN TRANSACTION performDrag");
+            SurfaceControl.openTransaction();
+            try {
+                surfaceControl.setPosition(touchX - thumbCenterX,
+                        touchY - thumbCenterY);
+                surfaceControl.setAlpha(.7071f);
+                surfaceControl.setLayer(mService.mDragState.getDragLayerLw());
+                surfaceControl.setLayerStack(display.getLayerStack());
+                surfaceControl.show();
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i(
+                        WindowManagerService.TAG, "<<< CLOSE TRANSACTION performDrag");
+            }
+        }
+
+        return true;    // success!
+    }
+
+    public void reportDropResult(IWindow window, boolean consumed) {
+        IBinder token = window.asBinder();
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "Drop result=" + consumed + " reported by " + token);
+        }
+
+        synchronized (mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                if (mService.mDragState == null) {
+                    // Most likely the drop recipient ANRed and we ended the drag
+                    // out from under it.  Log the issue and move on.
+                    Slog.w(WindowManagerService.TAG, "Drop result given but no drag in progress");
+                    return;
+                }
+
+                if (mService.mDragState.mToken != token) {
+                    // We're in a drag, but the wrong window has responded.
+                    Slog.w(WindowManagerService.TAG, "Invalid drop-result claim by " + window);
+                    throw new IllegalStateException("reportDropResult() by non-recipient");
+                }
+
+                // The right window has responded, even if it's no longer around,
+                // so be sure to halt the timeout even if the later WindowState
+                // lookup fails.
+                mService.mH.removeMessages(H.DRAG_END_TIMEOUT, window.asBinder());
+                WindowState callingWin = mService.windowForClientLocked(null, window, false);
+                if (callingWin == null) {
+                    Slog.w(WindowManagerService.TAG, "Bad result-reporting window " + window);
+                    return;  // !!! TODO: throw here?
+                }
+
+                mService.mDragState.mDragResult = consumed;
+                mService.mDragState.endDragLw();
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    public void dragRecipientEntered(IWindow window) {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "Drag into new candidate view @ " + window.asBinder());
+        }
+    }
+
+    public void dragRecipientExited(IWindow window) {
+        if (WindowManagerService.DEBUG_DRAG) {
+            Slog.d(WindowManagerService.TAG, "Drag from old candidate view @ " + window.asBinder());
+        }
+    }
+
+    public void setWallpaperPosition(IBinder window, float x, float y, float xStep, float yStep) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mService.setWindowWallpaperPositionLocked(
+                        mService.windowForClientLocked(this, window, true),
+                        x, y, xStep, yStep);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    public void wallpaperOffsetsComplete(IBinder window) {
+        mService.wallpaperOffsetsComplete(window);
+    }
+
+    public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
+            int z, Bundle extras, boolean sync) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mService.sendWindowWallpaperCommandLocked(
+                        mService.windowForClientLocked(this, window, true),
+                        action, x, y, z, extras, sync);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    public void wallpaperCommandComplete(IBinder window, Bundle result) {
+        mService.wallpaperCommandComplete(window, result);
+    }
+
+    public void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
+            float dsdx, float dtdx, float dsdy, float dtdy) {
+        synchronized(mService.mWindowMap) {
+            long ident = Binder.clearCallingIdentity();
+            try {
+                mService.setUniverseTransformLocked(
+                        mService.windowForClientLocked(this, window, true),
+                        alpha, offx, offy, dsdx, dtdx, dsdy, dtdy);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
+        synchronized(mService.mWindowMap) {
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                mService.onRectangleOnScreenRequested(token, rectangle, immediate);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+    }
+
+    public IWindowId getWindowId(IBinder window) {
+        return mService.getWindowId(window);
+    }
+
+    void windowAddedLocked() {
+        if (mSurfaceSession == null) {
+            if (WindowManagerService.localLOGV) Slog.v(
+                WindowManagerService.TAG, "First window added to " + this + ", creating SurfaceSession");
+            mSurfaceSession = new SurfaceSession();
+            if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+                    WindowManagerService.TAG, "  NEW SURFACE SESSION " + mSurfaceSession);
+            mService.mSessions.add(this);
+        }
+        mNumWindow++;
+    }
+
+    void windowRemovedLocked() {
+        mNumWindow--;
+        killSessionLocked();
+    }
+
+    void killSessionLocked() {
+        if (mNumWindow <= 0 && mClientDead) {
+            mService.mSessions.remove(this);
+            if (mSurfaceSession != null) {
+                if (WindowManagerService.localLOGV) Slog.v(
+                    WindowManagerService.TAG, "Last window removed from " + this
+                    + ", destroying " + mSurfaceSession);
+                if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+                        WindowManagerService.TAG, "  KILL SURFACE SESSION " + mSurfaceSession);
+                try {
+                    mSurfaceSession.kill();
+                } catch (Exception e) {
+                    Slog.w(WindowManagerService.TAG, "Exception thrown when killing surface session "
+                        + mSurfaceSession + " in session " + this
+                        + ": " + e.toString());
+                }
+                mSurfaceSession = null;
+            }
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow);
+                pw.print(" mClientDead="); pw.print(mClientDead);
+                pw.print(" mSurfaceSession="); pw.println(mSurfaceSession);
+    }
+
+    @Override
+    public String toString() {
+        return mStringName;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/StackTapPointerEventListener.java b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java
new file mode 100644
index 0000000..19d8ab3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StackTapPointerEventListener.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wm;
+
+import android.graphics.Region;
+import android.view.DisplayInfo;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicy.PointerEventListener;
+
+import com.android.server.wm.WindowManagerService.H;
+
+public class StackTapPointerEventListener implements PointerEventListener {
+    private static final int TAP_TIMEOUT_MSEC = 300;
+    private static final float TAP_MOTION_SLOP_INCHES = 0.125f;
+
+    private final int mMotionSlop;
+    private float mDownX;
+    private float mDownY;
+    private int mPointerId;
+    final private Region mTouchExcludeRegion;
+    private final WindowManagerService mService;
+    private final DisplayContent mDisplayContent;
+
+    public StackTapPointerEventListener(WindowManagerService service,
+            DisplayContent displayContent) {
+        mService = service;
+        mDisplayContent = displayContent;
+        mTouchExcludeRegion = displayContent.mTouchExcludeRegion;
+        DisplayInfo info = displayContent.getDisplayInfo();
+        mMotionSlop = (int)(info.logicalDensityDpi * TAP_MOTION_SLOP_INCHES);
+    }
+
+    @Override
+    public void onPointerEvent(MotionEvent motionEvent) {
+        final int action = motionEvent.getAction();
+        switch (action & MotionEvent.ACTION_MASK) {
+            case MotionEvent.ACTION_DOWN:
+                mPointerId = motionEvent.getPointerId(0);
+                mDownX = motionEvent.getX();
+                mDownY = motionEvent.getY();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mPointerId >= 0) {
+                    int index = motionEvent.findPointerIndex(mPointerId);
+                    if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC
+                            || (motionEvent.getX(index) - mDownX) > mMotionSlop
+                            || (motionEvent.getY(index) - mDownY) > mMotionSlop) {
+                        mPointerId = -1;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_POINTER_UP: {
+                int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
+                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
+                // Extract the index of the pointer that left the touch sensor
+                if (mPointerId == motionEvent.getPointerId(index)) {
+                    final int x = (int)motionEvent.getX(index);
+                    final int y = (int)motionEvent.getY(index);
+                    if ((motionEvent.getEventTime() - motionEvent.getDownTime())
+                            < TAP_TIMEOUT_MSEC
+                            && (x - mDownX) < mMotionSlop && (y - mDownY) < mMotionSlop
+                            && !mTouchExcludeRegion.contains(x, y)) {
+                        mService.mH.obtainMessage(H.TAP_OUTSIDE_STACK, x, y,
+                                mDisplayContent).sendToTarget();
+                    }
+                    mPointerId = -1;
+                }
+                break;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
new file mode 100644
index 0000000..7115b0f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.content.res.CompatibilityInfo;
+
+final class StartingData {
+    final String pkg;
+    final int theme;
+    final CompatibilityInfo compatInfo;
+    final CharSequence nonLocalizedLabel;
+    final int labelRes;
+    final int icon;
+    final int logo;
+    final int windowFlags;
+
+    StartingData(String _pkg, int _theme, CompatibilityInfo _compatInfo,
+            CharSequence _nonLocalizedLabel,
+            int _labelRes, int _icon, int _logo, int _windowFlags) {
+        pkg = _pkg;
+        theme = _theme;
+        compatInfo = _compatInfo;
+        nonLocalizedLabel = _nonLocalizedLabel;
+        labelRes = _labelRes;
+        icon = _icon;
+        logo = _logo;
+        windowFlags = _windowFlags;
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
new file mode 100644
index 0000000..fb5876b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 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.wm;
+
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.view.Display;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+class StrictModeFlash {
+    private static final String TAG = "StrictModeFlash";
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+    private final int mThickness = 20;
+
+    public StrictModeFlash(Display display, SurfaceSession session) {
+        SurfaceControl ctrl = null;
+        try {
+            ctrl = new SurfaceControl(session, "StrictModeFlash",
+                1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl.setLayerStack(display.getLayerStack());
+            ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+        mDrawNeeded = true;
+    }
+
+    private void drawIfNeeded() {
+        if (!mDrawNeeded) {
+            return;
+        }
+        mDrawNeeded = false;
+        final int dw = mLastDW;
+        final int dh = mLastDH;
+
+        Rect dirty = new Rect(0, 0, dw, dh);
+        Canvas c = null;
+        try {
+            c = mSurface.lockCanvas(dirty);
+        } catch (IllegalArgumentException e) {
+        } catch (Surface.OutOfResourcesException e) {
+        }
+        if (c == null) {
+            return;
+        }
+
+        // Top
+        c.clipRect(new Rect(0, 0, dw, mThickness), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Left
+        c.clipRect(new Rect(0, 0, mThickness, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Right
+        c.clipRect(new Rect(dw - mThickness, 0, dw, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+        // Bottom
+        c.clipRect(new Rect(0, dh - mThickness, dw, dh), Region.Op.REPLACE);
+        c.drawColor(Color.RED);
+
+        mSurface.unlockCanvasAndPost(c);
+    }
+
+    // Note: caller responsible for being inside
+    // Surface.openTransaction() / closeTransaction()
+    public void setVisibility(boolean on) {
+        if (mSurfaceControl == null) {
+            return;
+        }
+        drawIfNeeded();
+        if (on) {
+            mSurfaceControl.show();
+        } else {
+            mSurfaceControl.hide();
+        }
+    }
+
+    void positionSurface(int dw, int dh) {
+        if (mLastDW == dw && mLastDH == dh) {
+            return;
+        }
+        mLastDW = dw;
+        mLastDH = dh;
+        mSurfaceControl.setSize(dw, dh);
+        mDrawNeeded = true;
+    }
+
+}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
new file mode 100644
index 0000000..13fdbc8
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -0,0 +1,59 @@
+/*
+ * 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.wm;
+
+import android.util.EventLog;
+import com.android.server.EventLogTags;
+
+class Task {
+//    private final String TAG = "TaskGroup";
+    TaskStack mStack;
+    final AppTokenList mAppTokens = new AppTokenList();
+    final int taskId;
+    final int mUserId;
+
+    Task(AppWindowToken wtoken, TaskStack stack, int userId) {
+        taskId = wtoken.groupId;
+        mAppTokens.add(wtoken);
+        mStack = stack;
+        mUserId = userId;
+    }
+
+    DisplayContent getDisplayContent() {
+        return mStack.getDisplayContent();
+    }
+
+    void addAppToken(int addPos, AppWindowToken wtoken) {
+        mAppTokens.add(addPos, wtoken);
+    }
+
+    boolean removeAppToken(AppWindowToken wtoken) {
+        mAppTokens.remove(wtoken);
+        if (mAppTokens.size() == 0) {
+            EventLog.writeEvent(com.android.server.EventLogTags.WM_TASK_REMOVED, taskId,
+                    "removeAppToken: last token");
+            mStack.removeTask(this);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return "{taskId=" + taskId + " appTokens=" + mAppTokens + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskGroup.java b/services/core/java/com/android/server/wm/TaskGroup.java
new file mode 100644
index 0000000..1f1dd58
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskGroup.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.wm;
+
+import android.view.IApplicationToken;
+
+import java.util.ArrayList;
+
+public class TaskGroup {
+    public int taskId = -1;
+    public ArrayList<IApplicationToken> tokens = new ArrayList<IApplicationToken>();
+
+    @Override
+    public String toString() {
+        return "id=" + taskId + " tokens=" + tokens;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
new file mode 100644
index 0000000..ef245f9
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -0,0 +1,354 @@
+/*
+ * 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.wm;
+
+import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT;
+import static com.android.server.wm.WindowManagerService.TAG;
+
+import android.graphics.Rect;
+import android.os.Debug;
+import android.util.EventLog;
+import android.util.Slog;
+import android.util.TypedValue;
+import com.android.server.EventLogTags;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class TaskStack {
+    /** Amount of time in milliseconds to animate the dim surface from one value to another,
+     * when no window animation is driving it. */
+    private static final int DEFAULT_DIM_DURATION = 200;
+
+    /** Unique identifier */
+    final int mStackId;
+
+    /** The service */
+    private final WindowManagerService mService;
+
+    /** The display this stack sits under. */
+    private final DisplayContent mDisplayContent;
+
+    /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match
+     * mTaskHistory in the ActivityStack with the same mStackId */
+    private final ArrayList<Task> mTasks = new ArrayList<Task>();
+
+    /** Content limits relative to the DisplayContent this sits in. Empty indicates fullscreen,
+     * Nonempty is size of this TaskStack but is also used to scale if DisplayContent changes. */
+    Rect mBounds = new Rect();
+
+    /** Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} */
+    final DimLayer mDimLayer;
+
+    /** The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer. */
+    WindowStateAnimator mDimWinAnimator;
+
+    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
+    final DimLayer mAnimationBackgroundSurface;
+
+    /** The particular window with an Animation with non-zero background color. */
+    WindowStateAnimator mAnimationBackgroundAnimator;
+
+    /** Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the end
+     * then stop any dimming. */
+    boolean mDimmingTag;
+
+    TaskStack(WindowManagerService service, int stackId, DisplayContent displayContent) {
+        mService = service;
+        mStackId = stackId;
+        mDisplayContent = displayContent;
+        mDimLayer = new DimLayer(service, this);
+        mAnimationBackgroundSurface = new DimLayer(service, this);
+        // TODO: remove bounds from log, they are always 0.
+        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId, mBounds.left, mBounds.top,
+                mBounds.right, mBounds.bottom);
+    }
+
+    DisplayContent getDisplayContent() {
+        return mDisplayContent;
+    }
+
+    ArrayList<Task> getTasks() {
+        return mTasks;
+    }
+
+    private void resizeWindows() {
+        final boolean underStatusBar = mBounds.top == 0;
+
+        final ArrayList<WindowState> resizingWindows = mService.mResizingWindows;
+        for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens;
+            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+                final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows;
+                for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                    final WindowState win = windows.get(winNdx);
+                    if (!resizingWindows.contains(win)) {
+                        if (WindowManagerService.DEBUG_RESIZE) Slog.d(TAG,
+                                "setBounds: Resizing " + win);
+                        resizingWindows.add(win);
+                    }
+                    win.mUnderStatusBar = underStatusBar;
+                }
+            }
+        }
+    }
+
+    boolean setBounds(Rect bounds) {
+        if (mBounds.equals(bounds)) {
+            return false;
+        }
+
+        mDimLayer.setBounds(bounds);
+        mAnimationBackgroundSurface.setBounds(bounds);
+        mBounds.set(bounds);
+
+        resizeWindows();
+        return true;
+    }
+
+    void getBounds(Rect out) {
+        if (mBounds.isEmpty()) {
+            mDisplayContent.getLogicalDisplayRect(out);
+        } else {
+            out.set(mBounds);
+        }
+        out.intersect(mDisplayContent.mContentRect);
+    }
+
+    boolean isFullscreen() {
+        return mBounds.isEmpty();
+    }
+
+    void resizeBounds(float oldWidth, float oldHeight, float newWidth, float newHeight) {
+        if (oldWidth == newWidth && oldHeight == newHeight) {
+            return;
+        }
+        float widthScale = newWidth / oldWidth;
+        float heightScale = newHeight / oldHeight;
+        mBounds.left = (int)(mBounds.left * widthScale + 0.5);
+        mBounds.top = (int)(mBounds.top * heightScale + 0.5);
+        mBounds.right = (int)(mBounds.right * widthScale + 0.5);
+        mBounds.bottom = (int)(mBounds.bottom * heightScale + 0.5);
+        resizeWindows();
+    }
+
+    /**
+     * Put a Task in this stack. Used for adding and moving.
+     * @param task The task to add.
+     * @param toTop Whether to add it to the top or bottom.
+     */
+    void addTask(Task task, boolean toTop) {
+        int stackNdx;
+        if (!toTop) {
+            stackNdx = 0;
+        } else {
+            stackNdx = mTasks.size();
+            final int currentUserId = mService.mCurrentUserId;
+            if (task.mUserId != currentUserId) {
+                // Place the task below all current user tasks.
+                while (--stackNdx >= 0) {
+                    if (currentUserId != mTasks.get(stackNdx).mUserId) {
+                        break;
+                    }
+                }
+                ++stackNdx;
+            }
+        }
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "addTask: task=" + task + " toTop=" + toTop
+                + " pos=" + stackNdx);
+        mTasks.add(stackNdx, task);
+
+        task.mStack = this;
+        mDisplayContent.addTask(task, toTop);
+        mDisplayContent.moveStack(this, true);
+    }
+
+    void moveTaskToTop(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers="
+                + Debug.getCallers(6));
+        mTasks.remove(task);
+        addTask(task, true);
+    }
+
+    void moveTaskToBottom(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task);
+        mTasks.remove(task);
+        addTask(task, false);
+    }
+
+    /**
+     * Delete a Task from this stack. If it is the last Task in the stack, remove this stack from
+     * its parent StackBox and merge the parent.
+     * @param task The Task to delete.
+     */
+    void removeTask(Task task) {
+        if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task);
+        mTasks.remove(task);
+        mDisplayContent.removeTask(task);
+    }
+
+    int remove() {
+        mAnimationBackgroundSurface.destroySurface();
+        mDimLayer.destroySurface();
+        EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
+        TaskStack next = mDisplayContent.removeStack(this);
+        if (next != null) {
+            return next.mStackId;
+        }
+        return -1;
+    }
+
+    void resetAnimationBackgroundAnimator() {
+        mAnimationBackgroundAnimator = null;
+        mAnimationBackgroundSurface.hide();
+    }
+
+    private long getDimBehindFadeDuration(long duration) {
+        TypedValue tv = new TypedValue();
+        mService.mContext.getResources().getValue(
+                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
+        if (tv.type == TypedValue.TYPE_FRACTION) {
+            duration = (long)tv.getFraction(duration, duration);
+        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
+            duration = tv.data;
+        }
+        return duration;
+    }
+
+    boolean animateDimLayers() {
+        final int dimLayer;
+        final float dimAmount;
+        if (mDimWinAnimator == null) {
+            dimLayer = mDimLayer.getLayer();
+            dimAmount = 0;
+        } else {
+            dimLayer = mDimWinAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM;
+            dimAmount = mDimWinAnimator.mWin.mAttrs.dimAmount;
+        }
+        final float targetAlpha = mDimLayer.getTargetAlpha();
+        if (targetAlpha != dimAmount) {
+            if (mDimWinAnimator == null) {
+                mDimLayer.hide(DEFAULT_DIM_DURATION);
+            } else {
+                long duration = (mDimWinAnimator.mAnimating && mDimWinAnimator.mAnimation != null)
+                        ? mDimWinAnimator.mAnimation.computeDurationHint()
+                        : DEFAULT_DIM_DURATION;
+                if (targetAlpha > dimAmount) {
+                    duration = getDimBehindFadeDuration(duration);
+                }
+                mDimLayer.show(dimLayer, dimAmount, duration);
+            }
+        } else if (mDimLayer.getLayer() != dimLayer) {
+            mDimLayer.setLayer(dimLayer);
+        }
+        if (mDimLayer.isAnimating()) {
+            if (!mService.okToDisplay()) {
+                // Jump to the end of the animation.
+                mDimLayer.show();
+            } else {
+                return mDimLayer.stepAnimation();
+            }
+        }
+        return false;
+    }
+
+    void resetDimmingTag() {
+        mDimmingTag = false;
+    }
+
+    void setDimmingTag() {
+        mDimmingTag = true;
+    }
+
+    boolean testDimmingTag() {
+        return mDimmingTag;
+    }
+
+    boolean isDimming() {
+        return mDimLayer.isDimming();
+    }
+
+    boolean isDimming(WindowStateAnimator winAnimator) {
+        return mDimWinAnimator == winAnimator && mDimLayer.isDimming();
+    }
+
+    void startDimmingIfNeeded(WindowStateAnimator newWinAnimator) {
+        // Only set dim params on the highest dimmed layer.
+        final WindowStateAnimator existingDimWinAnimator = mDimWinAnimator;
+        // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
+        if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null
+                || !existingDimWinAnimator.mSurfaceShown
+                || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) {
+            mDimWinAnimator = newWinAnimator;
+        }
+    }
+
+    void stopDimmingIfNeeded() {
+        if (!mDimmingTag && isDimming()) {
+            mDimWinAnimator = null;
+        }
+    }
+
+    void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
+        int animLayer = winAnimator.mAnimLayer;
+        if (mAnimationBackgroundAnimator == null
+                || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
+            mAnimationBackgroundAnimator = winAnimator;
+            animLayer = mService.adjustAnimationBackground(winAnimator);
+            mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM,
+                    ((color >> 24) & 0xff) / 255f, 0);
+        }
+    }
+
+    void switchUser(int userId) {
+        int top = mTasks.size();
+        for (int taskNdx = 0; taskNdx < top; ++taskNdx) {
+            Task task = mTasks.get(taskNdx);
+            if (task.mUserId == userId) {
+                mTasks.remove(taskNdx);
+                mTasks.add(task);
+                --top;
+            }
+        }
+    }
+
+    void close() {
+        mDimLayer.mDimSurface.destroy();
+        mAnimationBackgroundSurface.mDimSurface.destroy();
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mStackId="); pw.println(mStackId);
+        for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) {
+            pw.print(prefix); pw.println(mTasks.get(taskNdx));
+        }
+        if (mAnimationBackgroundSurface.isDimming()) {
+            pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:");
+            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
+        }
+        if (mDimLayer.isDimming()) {
+            pw.print(prefix); pw.println("mDimLayer:");
+            mDimLayer.printTo(prefix, pw);
+            pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "{stackId=" + mStackId + " tasks=" + mTasks + "}";
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ViewServer.java b/services/core/java/com/android/server/wm/ViewServer.java
new file mode 100644
index 0000000..a763e2c
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ViewServer.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+
+import android.util.Slog;
+
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.InetAddress;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.io.IOException;
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.BufferedWriter;
+import java.io.OutputStreamWriter;
+
+/**
+ * The ViewServer is local socket server that can be used to communicate with the
+ * views of the opened windows. Communication with the views is ensured by the
+ * {@link com.android.server.wm.WindowManagerService} and is a cross-process operation.
+ *
+ * {@hide}
+ */
+class ViewServer implements Runnable {
+    /**
+     * The default port used to start view servers.
+     */
+    public static final int VIEW_SERVER_DEFAULT_PORT = 4939;
+
+    private static final int VIEW_SERVER_MAX_CONNECTIONS = 10;
+
+    // Debug facility
+    private static final String LOG_TAG = "ViewServer";
+
+    private static final String VALUE_PROTOCOL_VERSION = "4";
+    private static final String VALUE_SERVER_VERSION = "4";
+
+    // Protocol commands
+    // Returns the protocol version
+    private static final String COMMAND_PROTOCOL_VERSION = "PROTOCOL";
+    // Returns the server version
+    private static final String COMMAND_SERVER_VERSION = "SERVER";
+    // Lists all of the available windows in the system
+    private static final String COMMAND_WINDOW_MANAGER_LIST = "LIST";
+    // Keeps a connection open and notifies when the list of windows changes
+    private static final String COMMAND_WINDOW_MANAGER_AUTOLIST = "AUTOLIST";
+    // Returns the focused window
+    private static final String COMMAND_WINDOW_MANAGER_GET_FOCUS = "GET_FOCUS";
+
+    private ServerSocket mServer;
+    private Thread mThread;
+
+    private final WindowManagerService mWindowManager;
+    private final int mPort;
+
+    private ExecutorService mThreadPool;
+
+    /**
+     * Creates a new ViewServer associated with the specified window manager on the
+     * specified local port. The server is not started by default.
+     *
+     * @param windowManager The window manager used to communicate with the views.
+     * @param port The port for the server to listen to.
+     *
+     * @see #start()
+     */
+    ViewServer(WindowManagerService windowManager, int port) {
+        mWindowManager = windowManager;
+        mPort = port;
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @return True if the server was successfully created, or false if it already exists.
+     * @throws IOException If the server cannot be created.
+     *
+     * @see #stop()
+     * @see #isRunning()
+     * @see WindowManagerService#startViewServer(int)
+     */
+    boolean start() throws IOException {
+        if (mThread != null) {
+            return false;
+        }
+
+        mServer = new ServerSocket(mPort, VIEW_SERVER_MAX_CONNECTIONS, InetAddress.getLocalHost());
+        mThread = new Thread(this, "Remote View Server [port=" + mPort + "]");
+        mThreadPool = Executors.newFixedThreadPool(VIEW_SERVER_MAX_CONNECTIONS);
+        mThread.start();
+
+        return true;
+    }
+
+    /**
+     * Stops the server.
+     *
+     * @return True if the server was stopped, false if an error occured or if the
+     *         server wasn't started.
+     *
+     * @see #start()
+     * @see #isRunning()
+     * @see WindowManagerService#stopViewServer()
+     */
+    boolean stop() {
+        if (mThread != null) {
+
+            mThread.interrupt();
+            if (mThreadPool != null) {
+                try {
+                    mThreadPool.shutdownNow();
+                } catch (SecurityException e) {
+                    Slog.w(LOG_TAG, "Could not stop all view server threads");
+                }
+            }
+            mThreadPool = null;
+            mThread = null;
+            try {
+                mServer.close();
+                mServer = null;
+                return true;
+            } catch (IOException e) {
+                Slog.w(LOG_TAG, "Could not close the view server");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether the server is currently running.
+     *
+     * @return True if the server is running, false otherwise.
+     *
+     * @see #start()
+     * @see #stop()
+     * @see WindowManagerService#isViewServerRunning()  
+     */
+    boolean isRunning() {
+        return mThread != null && mThread.isAlive();
+    }
+
+    /**
+     * Main server loop.
+     */
+    public void run() {
+        while (Thread.currentThread() == mThread) {
+            // Any uncaught exception will crash the system process
+            try {
+                Socket client = mServer.accept();
+                if (mThreadPool != null) {
+                    mThreadPool.submit(new ViewServerWorker(client));
+                } else {
+                    try {
+                        client.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            } catch (Exception e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            }
+        }
+    }
+
+    private static boolean writeValue(Socket client, String value) {
+        boolean result;
+        BufferedWriter out = null;
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+            out.write(value);
+            out.write("\n");
+            out.flush();
+            result = true;
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+        return result;
+    }
+
+    class ViewServerWorker implements Runnable, WindowManagerService.WindowChangeListener {
+        private Socket mClient;
+        private boolean mNeedWindowListUpdate;
+        private boolean mNeedFocusedWindowUpdate;
+
+        public ViewServerWorker(Socket client) {
+            mClient = client;
+            mNeedWindowListUpdate = false;
+            mNeedFocusedWindowUpdate = false;
+        }
+
+        public void run() {
+
+            BufferedReader in = null;
+            try {
+                in = new BufferedReader(new InputStreamReader(mClient.getInputStream()), 1024);
+
+                final String request = in.readLine();
+
+                String command;
+                String parameters;
+
+                int index = request.indexOf(' ');
+                if (index == -1) {
+                    command = request;
+                    parameters = "";
+                } else {
+                    command = request.substring(0, index);
+                    parameters = request.substring(index + 1);
+                }
+
+                boolean result;
+                if (COMMAND_PROTOCOL_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_PROTOCOL_VERSION);
+                } else if (COMMAND_SERVER_VERSION.equalsIgnoreCase(command)) {
+                    result = writeValue(mClient, VALUE_SERVER_VERSION);
+                } else if (COMMAND_WINDOW_MANAGER_LIST.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerListWindows(mClient);
+                } else if (COMMAND_WINDOW_MANAGER_GET_FOCUS.equalsIgnoreCase(command)) {
+                    result = mWindowManager.viewServerGetFocusedWindow(mClient);
+                } else if (COMMAND_WINDOW_MANAGER_AUTOLIST.equalsIgnoreCase(command)) {
+                    result = windowManagerAutolistLoop();
+                } else {
+                    result = mWindowManager.viewServerWindowCommand(mClient,
+                            command, parameters);
+                }
+
+                if (!result) {
+                    Slog.w(LOG_TAG, "An error occurred with the command: " + command);
+                }
+            } catch(IOException e) {
+                Slog.w(LOG_TAG, "Connection error: ", e);
+            } finally {
+                if (in != null) {
+                    try {
+                        in.close();
+
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+                if (mClient != null) {
+                    try {
+                        mClient.close();
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+
+        public void windowsChanged() {
+            synchronized(this) {
+                mNeedWindowListUpdate = true;
+                notifyAll();
+            }
+        }
+
+        public void focusChanged() {
+            synchronized(this) {
+                mNeedFocusedWindowUpdate = true;
+                notifyAll();
+            }
+        }
+
+        private boolean windowManagerAutolistLoop() {
+            mWindowManager.addWindowChangeListener(this);
+            BufferedWriter out = null;
+            try {
+                out = new BufferedWriter(new OutputStreamWriter(mClient.getOutputStream()));
+                while (!Thread.interrupted()) {
+                    boolean needWindowListUpdate = false;
+                    boolean needFocusedWindowUpdate = false;
+                    synchronized (this) {
+                        while (!mNeedWindowListUpdate && !mNeedFocusedWindowUpdate) {
+                            wait();
+                        }
+                        if (mNeedWindowListUpdate) {
+                            mNeedWindowListUpdate = false;
+                            needWindowListUpdate = true;
+                        }
+                        if (mNeedFocusedWindowUpdate) {
+                            mNeedFocusedWindowUpdate = false;
+                            needFocusedWindowUpdate = true;
+                        }
+                    }
+                    if (needWindowListUpdate) {
+                        out.write("LIST UPDATE\n");
+                        out.flush();
+                    }
+                    if (needFocusedWindowUpdate) {
+                        out.write("FOCUS UPDATE\n");
+                        out.flush();
+                    }
+                }
+            } catch (Exception e) {
+                // Ignore
+            } finally {
+                if (out != null) {
+                    try {
+                        out.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                mWindowManager.removeWindowChangeListener(this);
+            }
+            return true;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
new file mode 100644
index 0000000..e226e3d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.Paint.FontMetricsInt;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+/**
+ * Displays a watermark on top of the window manager's windows.
+ */
+class Watermark {
+    private final Display mDisplay;
+    private final String[] mTokens;
+    private final String mText;
+    private final Paint mTextPaint;
+    private final int mTextWidth;
+    private final int mTextHeight;
+    private final int mDeltaX;
+    private final int mDeltaY;
+
+    private final SurfaceControl mSurfaceControl;
+    private final Surface mSurface = new Surface();
+    private int mLastDW;
+    private int mLastDH;
+    private boolean mDrawNeeded;
+
+    Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+        if (false) {
+            Log.i(WindowManagerService.TAG, "*********************** WATERMARK");
+            for (int i=0; i<tokens.length; i++) {
+                Log.i(WindowManagerService.TAG, "  TOKEN #" + i + ": " + tokens[i]);
+            }
+        }
+
+        mDisplay = display;
+        mTokens = tokens;
+
+        StringBuilder builder = new StringBuilder(32);
+        int len = mTokens[0].length();
+        len = len & ~1;
+        for (int i=0; i<len; i+=2) {
+            int c1 = mTokens[0].charAt(i);
+            int c2 = mTokens[0].charAt(i+1);
+            if (c1 >= 'a' && c1 <= 'f') c1 = c1 - 'a' + 10;
+            else if (c1 >= 'A' && c1 <= 'F') c1 = c1 - 'A' + 10;
+            else c1 -= '0';
+            if (c2 >= 'a' && c2 <= 'f') c2 = c2 - 'a' + 10;
+            else if (c2 >= 'A' && c2 <= 'F') c2 = c2 - 'A' + 10;
+            else c2 -= '0';
+            builder.append((char)(255-((c1*16)+c2)));
+        }
+        mText = builder.toString();
+        if (false) {
+            Log.i(WindowManagerService.TAG, "Final text: " + mText);
+        }
+
+        int fontSize = WindowManagerService.getPropertyInt(tokens, 1,
+                TypedValue.COMPLEX_UNIT_DIP, 20, dm);
+
+        mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        mTextPaint.setTextSize(fontSize);
+        mTextPaint.setTypeface(Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD));
+
+        FontMetricsInt fm = mTextPaint.getFontMetricsInt();
+        mTextWidth = (int)mTextPaint.measureText(mText);
+        mTextHeight = fm.descent - fm.ascent;
+
+        mDeltaX = WindowManagerService.getPropertyInt(tokens, 2,
+                TypedValue.COMPLEX_UNIT_PX, mTextWidth*2, dm);
+        mDeltaY = WindowManagerService.getPropertyInt(tokens, 3,
+                TypedValue.COMPLEX_UNIT_PX, mTextHeight*3, dm);
+        int shadowColor = WindowManagerService.getPropertyInt(tokens, 4,
+                TypedValue.COMPLEX_UNIT_PX, 0xb0000000, dm);
+        int color = WindowManagerService.getPropertyInt(tokens, 5,
+                TypedValue.COMPLEX_UNIT_PX, 0x60ffffff, dm);
+        int shadowRadius = WindowManagerService.getPropertyInt(tokens, 6,
+                TypedValue.COMPLEX_UNIT_PX, 7, dm);
+        int shadowDx = WindowManagerService.getPropertyInt(tokens, 8,
+                TypedValue.COMPLEX_UNIT_PX, 0, dm);
+        int shadowDy = WindowManagerService.getPropertyInt(tokens, 9,
+                TypedValue.COMPLEX_UNIT_PX, 0, dm);
+
+        mTextPaint.setColor(color);
+        mTextPaint.setShadowLayer(shadowRadius, shadowDx, shadowDy, shadowColor);
+
+        SurfaceControl ctrl = null;
+        try {
+            ctrl = new SurfaceControl(session, "WatermarkSurface",
+                    1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+            ctrl.setLayerStack(mDisplay.getLayerStack());
+            ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
+            ctrl.setPosition(0, 0);
+            ctrl.show();
+            mSurface.copyFrom(ctrl);
+        } catch (OutOfResourcesException e) {
+        }
+        mSurfaceControl = ctrl;
+    }
+
+    void positionSurface(int dw, int dh) {
+        if (mLastDW != dw || mLastDH != dh) {
+            mLastDW = dw;
+            mLastDH = dh;
+            mSurfaceControl.setSize(dw, dh);
+            mDrawNeeded = true;
+        }
+    }
+
+    void drawIfNeeded() {
+        if (mDrawNeeded) {
+            final int dw = mLastDW;
+            final int dh = mLastDH;
+
+            mDrawNeeded = false;
+            Rect dirty = new Rect(0, 0, dw, dh);
+            Canvas c = null;
+            try {
+                c = mSurface.lockCanvas(dirty);
+            } catch (IllegalArgumentException e) {
+            } catch (Surface.OutOfResourcesException e) {
+            }
+            if (c != null) {
+                c.drawColor(0, PorterDuff.Mode.CLEAR);
+
+                int deltaX = mDeltaX;
+                int deltaY = mDeltaY;
+
+                // deltaX shouldn't be close to a round fraction of our
+                // x step, or else things will line up too much.
+                int div = (dw+mTextWidth)/deltaX;
+                int rem = (dw+mTextWidth) - (div*deltaX);
+                int qdelta = deltaX/4;
+                if (rem < qdelta || rem > (deltaX-qdelta)) {
+                    deltaX += deltaX/3;
+                }
+
+                int y = -mTextHeight;
+                int x = -mTextWidth;
+                while (y < (dh+mTextHeight)) {
+                    c.drawText(mText, x, y, mTextPaint);
+                    x += deltaX;
+                    if (x >= dw) {
+                        x -= (dw+mTextWidth);
+                        y += deltaY;
+                    }
+                }
+                mSurface.unlockCanvasAndPost(c);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
new file mode 100644
index 0000000..91f15f3
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -0,0 +1,689 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_ACTION_PENDING;
+
+import android.content.Context;
+import android.os.Debug;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.WindowManagerPolicy;
+import android.view.animation.Animation;
+
+import com.android.server.wm.WindowManagerService.LayoutFields;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+/**
+ * Singleton class that carries out the animations and Surface operations in a separate task
+ * on behalf of WindowManagerService.
+ */
+public class WindowAnimator {
+    private static final String TAG = "WindowAnimator";
+
+    final WindowManagerService mService;
+    final Context mContext;
+    final WindowManagerPolicy mPolicy;
+
+    boolean mAnimating;
+
+    final Runnable mAnimationRunnable;
+
+    /** Time of current animation step. Reset on each iteration */
+    long mCurrentTime;
+
+    /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
+     * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
+    private int mAnimTransactionSequence;
+
+    /** Window currently running an animation that has requested it be detached
+     * from the wallpaper.  This means we need to ensure the wallpaper is
+     * visible behind it in case it animates in a way that would allow it to be
+     * seen. If multiple windows satisfy this, use the lowest window. */
+    WindowState mWindowDetachedWallpaper = null;
+
+    WindowStateAnimator mUniverseBackground = null;
+    int mAboveUniverseLayer = 0;
+
+    int mBulkUpdateParams = 0;
+    Object mLastWindowFreezeSource;
+
+    SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators =
+            new SparseArray<WindowAnimator.DisplayContentsAnimator>(2);
+
+    boolean mInitialized = false;
+
+    // forceHiding states.
+    static final int KEYGUARD_NOT_SHOWN     = 0;
+    static final int KEYGUARD_ANIMATING_IN  = 1;
+    static final int KEYGUARD_SHOWN         = 2;
+    static final int KEYGUARD_ANIMATING_OUT = 3;
+    int mForceHiding = KEYGUARD_NOT_SHOWN;
+
+    private String forceHidingToString() {
+        switch (mForceHiding) {
+            case KEYGUARD_NOT_SHOWN:    return "KEYGUARD_NOT_SHOWN";
+            case KEYGUARD_ANIMATING_IN: return "KEYGUARD_ANIMATING_IN";
+            case KEYGUARD_SHOWN:        return "KEYGUARD_SHOWN";
+            case KEYGUARD_ANIMATING_OUT:return "KEYGUARD_ANIMATING_OUT";
+            default: return "KEYGUARD STATE UNKNOWN " + mForceHiding;
+        }
+    }
+
+    WindowAnimator(final WindowManagerService service) {
+        mService = service;
+        mContext = service.mContext;
+        mPolicy = service.mPolicy;
+
+        mAnimationRunnable = new Runnable() {
+            @Override
+            public void run() {
+                synchronized (mService.mWindowMap) {
+                    mService.mAnimationScheduled = false;
+                    animateLocked();
+                }
+            }
+        };
+    }
+
+    void addDisplayLocked(final int displayId) {
+        // Create the DisplayContentsAnimator object by retrieving it.
+        getDisplayContentsAnimatorLocked(displayId);
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            mInitialized = true;
+        }
+    }
+
+    void removeDisplayLocked(final int displayId) {
+        final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
+        if (displayAnimator != null) {
+            if (displayAnimator.mScreenRotationAnimation != null) {
+                displayAnimator.mScreenRotationAnimation.kill();
+                displayAnimator.mScreenRotationAnimation = null;
+            }
+        }
+
+        mDisplayContentsAnimators.delete(displayId);
+    }
+
+    void hideWallpapersLocked(final WindowState w) {
+        final WindowState wallpaperTarget = mService.mWallpaperTarget;
+        final WindowState lowerWallpaperTarget = mService.mLowerWallpaperTarget;
+        final ArrayList<WindowToken> wallpaperTokens = mService.mWallpaperTokens;
+
+        if ((wallpaperTarget == w && lowerWallpaperTarget == null) || wallpaperTarget == null) {
+            final int numTokens = wallpaperTokens.size();
+            for (int i = numTokens - 1; i >= 0; i--) {
+                final WindowToken token = wallpaperTokens.get(i);
+                final int numWindows = token.windows.size();
+                for (int j = numWindows - 1; j >= 0; j--) {
+                    final WindowState wallpaper = token.windows.get(j);
+                    final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+                    if (!winAnimator.mLastHidden) {
+                        winAnimator.hide();
+                        mService.dispatchWallpaperVisibility(wallpaper, false);
+                        setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+                    }
+                }
+                if (WindowManagerService.DEBUG_WALLPAPER_LIGHT && !token.hidden) Slog.d(TAG,
+                        "Hiding wallpaper " + token + " from " + w
+                        + " target=" + wallpaperTarget + " lower=" + lowerWallpaperTarget
+                        + "\n" + Debug.getCallers(5, "  "));
+                token.hidden = true;
+            }
+        }
+    }
+
+    private void updateAppWindowsLocked(int displayId) {
+        final DisplayContent displayContent = mService.getDisplayContentLocked(displayId);
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowAnimator appAnimator = tokens.get(tokenNdx).mAppAnimator;
+                final boolean wasAnimating = appAnimator.animation != null
+                        && appAnimator.animation != AppWindowAnimator.sDummyAnimation;
+                if (appAnimator.stepAnimationLocked(mCurrentTime)) {
+                    mAnimating = true;
+                } else if (wasAnimating) {
+                    // stopped animating, do one more pass through the layout
+                    setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
+                            "appToken " + appAnimator.mAppToken + " done");
+                    if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                            "updateWindowsApps...: done animating " + appAnimator.mAppToken);
+                }
+            }
+        }
+
+        final AppTokenList exitingAppTokens = displayContent.mExitingAppTokens;
+        final int NEAT = exitingAppTokens.size();
+        for (int i = 0; i < NEAT; i++) {
+            final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator;
+            final boolean wasAnimating = appAnimator.animation != null
+                    && appAnimator.animation != AppWindowAnimator.sDummyAnimation;
+            if (appAnimator.stepAnimationLocked(mCurrentTime)) {
+                mAnimating = true;
+            } else if (wasAnimating) {
+                // stopped animating, do one more pass through the layout
+                setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
+                    "exiting appToken " + appAnimator.mAppToken + " done");
+                if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                        "updateWindowsApps...: done animating exiting " + appAnimator.mAppToken);
+            }
+        }
+    }
+
+    private void updateWindowsLocked(final int displayId) {
+        ++mAnimTransactionSequence;
+
+        final WindowList windows = mService.getWindowListLocked(displayId);
+        ArrayList<WindowStateAnimator> unForceHiding = null;
+        boolean wallpaperInUnForceHiding = false;
+        mForceHiding = KEYGUARD_NOT_SHOWN;
+
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            WindowState win = windows.get(i);
+            WindowStateAnimator winAnimator = win.mWinAnimator;
+            final int flags = winAnimator.mAttrFlags;
+
+            if (winAnimator.mSurfaceControl != null) {
+                final boolean wasAnimating = winAnimator.mWasAnimating;
+                final boolean nowAnimating = winAnimator.stepAnimationLocked(mCurrentTime);
+
+                if (WindowManagerService.DEBUG_WALLPAPER) {
+                    Slog.v(TAG, win + ": wasAnimating=" + wasAnimating +
+                            ", nowAnimating=" + nowAnimating);
+                }
+
+                if (wasAnimating && !winAnimator.mAnimating && mService.mWallpaperTarget == win) {
+                    mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+                    setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
+                            WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+                    if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                        mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2",
+                                getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
+                    }
+                }
+
+                if (mPolicy.doesForceHide(win, win.mAttrs)) {
+                    if (!wasAnimating && nowAnimating) {
+                        if (WindowManagerService.DEBUG_ANIM ||
+                                WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                                "Animation started that could impact force hide: " + win);
+                        mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED;
+                        setPendingLayoutChanges(displayId,
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3",
+                                    getPendingLayoutChanges(displayId));
+                        }
+                        mService.mFocusMayChange = true;
+                    }
+                    if (win.isReadyForDisplay()) {
+                        if (nowAnimating) {
+                            if (winAnimator.mAnimationIsEntrance) {
+                                mForceHiding = KEYGUARD_ANIMATING_IN;
+                            } else {
+                                mForceHiding = KEYGUARD_ANIMATING_OUT;
+                            }
+                        } else {
+                            mForceHiding = win.isDrawnLw() ? KEYGUARD_SHOWN : KEYGUARD_NOT_SHOWN;
+                        }
+                    }
+                    if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG,
+                            "Force hide " + mForceHiding
+                            + " hasSurface=" + win.mHasSurface
+                            + " policyVis=" + win.mPolicyVisibility
+                            + " destroying=" + win.mDestroying
+                            + " attHidden=" + win.mAttachedHidden
+                            + " vis=" + win.mViewVisibility
+                            + " hidden=" + win.mRootToken.hidden
+                            + " anim=" + win.mWinAnimator.mAnimation);
+                } else if (mPolicy.canBeForceHidden(win, win.mAttrs)) {
+                    final boolean hideWhenLocked =
+                            (winAnimator.mAttrFlags & FLAG_SHOW_WHEN_LOCKED) == 0;
+                    final boolean changed;
+                    if (((mForceHiding == KEYGUARD_ANIMATING_IN)
+                                && (!winAnimator.isAnimating() || hideWhenLocked))
+                            || ((mForceHiding == KEYGUARD_SHOWN) && hideWhenLocked)) {
+                        changed = win.hideLw(false, false);
+                        if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG,
+                                "Now policy hidden: " + win);
+                    } else {
+                        changed = win.showLw(false, false);
+                        if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG,
+                                "Now policy shown: " + win);
+                        if (changed) {
+                            if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0
+                                    && win.isVisibleNow() /*w.isReadyForDisplay()*/) {
+                                if (unForceHiding == null) {
+                                    unForceHiding = new ArrayList<WindowStateAnimator>();
+                                }
+                                unForceHiding.add(winAnimator);
+                                if ((flags & FLAG_SHOW_WALLPAPER) != 0) {
+                                    wallpaperInUnForceHiding = true;
+                                }
+                            }
+                            final WindowState currentFocus = mService.mCurrentFocus;
+                            if (currentFocus == null || currentFocus.mLayer < win.mLayer) {
+                                // We are showing on to of the current
+                                // focus, so re-evaluate focus to make
+                                // sure it is correct.
+                                if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.v(TAG,
+                                        "updateWindowsLocked: setting mFocusMayChange true");
+                                mService.mFocusMayChange = true;
+                            }
+                        }
+                    }
+                    if (changed && (flags & FLAG_SHOW_WALLPAPER) != 0) {
+                        mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+                        setPendingLayoutChanges(Display.DEFAULT_DISPLAY,
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4",
+                                    getPendingLayoutChanges(Display.DEFAULT_DISPLAY));
+                        }
+                    }
+                }
+            }
+
+            final AppWindowToken atoken = win.mAppToken;
+            if (winAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW) {
+                if (atoken == null || atoken.allDrawn) {
+                    if (winAnimator.performShowLocked()) {
+                        setPendingLayoutChanges(displayId,
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
+                        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                            mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5",
+                                    getPendingLayoutChanges(displayId));
+                        }
+                    }
+                }
+            }
+            final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+            if (appAnimator != null && appAnimator.thumbnail != null) {
+                if (appAnimator.thumbnailTransactionSeq != mAnimTransactionSequence) {
+                    appAnimator.thumbnailTransactionSeq = mAnimTransactionSequence;
+                    appAnimator.thumbnailLayer = 0;
+                }
+                if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
+                    appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
+                }
+            }
+        } // end forall windows
+
+        // If we have windows that are being show due to them no longer
+        // being force-hidden, apply the appropriate animation to them.
+        if (unForceHiding != null) {
+            for (int i=unForceHiding.size()-1; i>=0; i--) {
+                Animation a = mPolicy.createForceHideEnterAnimation(wallpaperInUnForceHiding);
+                if (a != null) {
+                    final WindowStateAnimator winAnimator = unForceHiding.get(i);
+                    winAnimator.setAnimation(a);
+                    winAnimator.mAnimationIsEntrance = true;
+                }
+            }
+        }
+    }
+
+    private void updateWallpaperLocked(int displayId) {
+        mService.getDisplayContentLocked(displayId).resetAnimationBackgroundAnimator();
+
+        final WindowList windows = mService.getWindowListLocked(displayId);
+        WindowState detachedWallpaper = null;
+
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            final WindowState win = windows.get(i);
+            WindowStateAnimator winAnimator = win.mWinAnimator;
+            if (winAnimator.mSurfaceControl == null) {
+                continue;
+            }
+
+            final int flags = winAnimator.mAttrFlags;
+
+            // If this window is animating, make a note that we have
+            // an animating window and take care of a request to run
+            // a detached wallpaper animation.
+            if (winAnimator.mAnimating) {
+                if (winAnimator.mAnimation != null) {
+                    if ((flags & FLAG_SHOW_WALLPAPER) != 0
+                            && winAnimator.mAnimation.getDetachWallpaper()) {
+                        detachedWallpaper = win;
+                    }
+                    final int color = winAnimator.mAnimation.getBackgroundColor();
+                    if (color != 0) {
+                        win.getStack().setAnimationBackground(winAnimator, color);
+                    }
+                }
+                mAnimating = true;
+            }
+
+            // If this window's app token is running a detached wallpaper
+            // animation, make a note so we can ensure the wallpaper is
+            // displayed behind it.
+            final AppWindowAnimator appAnimator = winAnimator.mAppAnimator;
+            if (appAnimator != null && appAnimator.animation != null
+                    && appAnimator.animating) {
+                if ((flags & FLAG_SHOW_WALLPAPER) != 0
+                        && appAnimator.animation.getDetachWallpaper()) {
+                    detachedWallpaper = win;
+                }
+
+                final int color = appAnimator.animation.getBackgroundColor();
+                if (color != 0) {
+                    win.getStack().setAnimationBackground(winAnimator, color);
+                }
+            }
+        } // end forall windows
+
+        if (mWindowDetachedWallpaper != detachedWallpaper) {
+            if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG,
+                    "Detached wallpaper changed from " + mWindowDetachedWallpaper
+                    + " to " + detachedWallpaper);
+            mWindowDetachedWallpaper = detachedWallpaper;
+            mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE;
+        }
+    }
+
+    /** See if any windows have been drawn, so they (and others associated with them) can now be
+     *  shown. */
+    private void testTokenMayBeDrawnLocked(int displayId) {
+        // See if any windows have been drawn, so they (and others
+        // associated with them) can now be shown.
+        final ArrayList<Task> tasks = mService.getDisplayContentLocked(displayId).getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                final boolean allDrawn = wtoken.allDrawn;
+                if (allDrawn != appAnimator.allDrawn) {
+                    appAnimator.allDrawn = allDrawn;
+                    if (allDrawn) {
+                        // The token has now changed state to having all
+                        // windows shown...  what to do, what to do?
+                        if (appAnimator.freezingScreen) {
+                            appAnimator.showAllWindowsLocked();
+                            mService.unsetAppFreezingScreenLocked(wtoken, false, true);
+                            if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG,
+                                    "Setting mOrientationChangeComplete=true because wtoken "
+                                    + wtoken + " numInteresting=" + wtoken.numInterestingWindows
+                                    + " numDrawn=" + wtoken.numDrawnWindows);
+                            // This will set mOrientationChangeComplete and cause a pass through layout.
+                            setAppLayoutChanges(appAnimator,
+                                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER,
+                                    "testTokenMayBeDrawnLocked: freezingScreen");
+                        } else {
+                            setAppLayoutChanges(appAnimator,
+                                    WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM,
+                                    "testTokenMayBeDrawnLocked");
+
+                            // We can now show all of the drawn windows!
+                            if (!mService.mOpeningApps.contains(wtoken)) {
+                                mAnimating |= appAnimator.showAllWindowsLocked();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void performAnimationsLocked(final int displayId) {
+        updateWindowsLocked(displayId);
+        updateWallpaperLocked(displayId);
+    }
+
+
+    /** Locked on mService.mWindowMap. */
+    private void animateLocked() {
+        if (!mInitialized) {
+            return;
+        }
+
+        mCurrentTime = SystemClock.uptimeMillis();
+        mBulkUpdateParams = SET_ORIENTATION_CHANGE_COMPLETE;
+        boolean wasAnimating = mAnimating;
+        mAnimating = false;
+        if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+            Slog.i(TAG, "!!! animate: entry time=" + mCurrentTime);
+        }
+
+        if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+                TAG, ">>> OPEN TRANSACTION animateLocked");
+        SurfaceControl.openTransaction();
+        SurfaceControl.setAnimationTransaction();
+        try {
+            final int numDisplays = mDisplayContentsAnimators.size();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                updateAppWindowsLocked(displayId);
+                DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
+
+                final ScreenRotationAnimation screenRotationAnimation =
+                        displayAnimator.mScreenRotationAnimation;
+                if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+                    if (screenRotationAnimation.stepAnimationLocked(mCurrentTime)) {
+                        mAnimating = true;
+                    } else {
+                        mBulkUpdateParams |= SET_UPDATE_ROTATION;
+                        screenRotationAnimation.kill();
+                        displayAnimator.mScreenRotationAnimation = null;
+                    }
+                }
+
+                // Update animations of all applications, including those
+                // associated with exiting/removed apps
+                performAnimationsLocked(displayId);
+
+                final WindowList windows = mService.getWindowListLocked(displayId);
+                final int N = windows.size();
+                for (int j = 0; j < N; j++) {
+                    windows.get(j).mWinAnimator.prepareSurfaceLocked(true);
+                }
+            }
+
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+
+                testTokenMayBeDrawnLocked(displayId);
+
+                final ScreenRotationAnimation screenRotationAnimation =
+                        mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation;
+                if (screenRotationAnimation != null) {
+                    screenRotationAnimation.updateSurfacesInTransaction();
+                }
+
+                mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers();
+
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) {
+                    mService.mDisplayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+                }
+            }
+
+            if (mAnimating) {
+                mService.scheduleAnimationLocked();
+            }
+
+            mService.setFocusedStackLayer();
+
+            if (mService.mWatermark != null) {
+                mService.mWatermark.drawIfNeeded();
+            }
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Unhandled exception in Window Manager", e);
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(
+                    TAG, "<<< CLOSE TRANSACTION animateLocked");
+        }
+
+        boolean hasPendingLayoutChanges = false;
+        final int numDisplays = mService.mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mService.mDisplayContents.valueAt(displayNdx);
+            final int pendingChanges = getPendingLayoutChanges(displayContent.getDisplayId());
+            if ((pendingChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) {
+                mBulkUpdateParams |= SET_WALLPAPER_ACTION_PENDING;
+            }
+            if (pendingChanges != 0) {
+                hasPendingLayoutChanges = true;
+            }
+        }
+
+        boolean doRequest = false;
+        if (mBulkUpdateParams != 0) {
+            doRequest = mService.copyAnimToLayoutParamsLocked();
+        }
+
+        if (hasPendingLayoutChanges || doRequest) {
+            mService.requestTraversalLocked();
+        }
+
+        if (!mAnimating && wasAnimating) {
+            mService.requestTraversalLocked();
+        }
+        if (WindowManagerService.DEBUG_WINDOW_TRACE) {
+            Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
+                + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
+                + " mPendingLayoutChanges(DEFAULT_DISPLAY)="
+                + Integer.toHexString(getPendingLayoutChanges(Display.DEFAULT_DISPLAY)));
+        }
+    }
+
+    static String bulkUpdateParamsToString(int bulkUpdateParams) {
+        StringBuilder builder = new StringBuilder(128);
+        if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) {
+            builder.append(" UPDATE_ROTATION");
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) {
+            builder.append(" WALLPAPER_MAY_CHANGE");
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) {
+            builder.append(" FORCE_HIDING_CHANGED");
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) != 0) {
+            builder.append(" ORIENTATION_CHANGE_COMPLETE");
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) {
+            builder.append(" TURN_ON_SCREEN");
+        }
+        return builder.toString();
+    }
+
+    public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) {
+        final String subPrefix = "  " + prefix;
+        final String subSubPrefix = "  " + subPrefix;
+
+        for (int i = 0; i < mDisplayContentsAnimators.size(); i++) {
+            pw.print(prefix); pw.print("DisplayContentsAnimator #");
+                    pw.print(mDisplayContentsAnimators.keyAt(i));
+                    pw.println(":");
+            DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i);
+            final WindowList windows =
+                    mService.getWindowListLocked(mDisplayContentsAnimators.keyAt(i));
+            final int N = windows.size();
+            for (int j = 0; j < N; j++) {
+                WindowStateAnimator wanim = windows.get(j).mWinAnimator;
+                pw.print(subPrefix); pw.print("Window #"); pw.print(j);
+                        pw.print(": "); pw.println(wanim);
+            }
+            if (displayAnimator.mScreenRotationAnimation != null) {
+                pw.print(subPrefix); pw.println("mScreenRotationAnimation:");
+                displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw);
+            } else if (dumpAll) {
+                pw.print(subPrefix); pw.println("no ScreenRotationAnimation ");
+            }
+        }
+
+        pw.println();
+
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mAnimTransactionSequence=");
+                    pw.print(mAnimTransactionSequence);
+                    pw.print(" mForceHiding="); pw.println(forceHidingToString());
+            pw.print(prefix); pw.print("mCurrentTime=");
+                    pw.println(TimeUtils.formatUptime(mCurrentTime));
+        }
+        if (mBulkUpdateParams != 0) {
+            pw.print(prefix); pw.print("mBulkUpdateParams=0x");
+                    pw.print(Integer.toHexString(mBulkUpdateParams));
+                    pw.println(bulkUpdateParamsToString(mBulkUpdateParams));
+        }
+        if (mWindowDetachedWallpaper != null) {
+            pw.print(prefix); pw.print("mWindowDetachedWallpaper=");
+                pw.println(mWindowDetachedWallpaper);
+        }
+        if (mUniverseBackground != null) {
+            pw.print(prefix); pw.print("mUniverseBackground="); pw.print(mUniverseBackground);
+                    pw.print(" mAboveUniverseLayer="); pw.println(mAboveUniverseLayer);
+        }
+    }
+
+    int getPendingLayoutChanges(final int displayId) {
+        return mService.getDisplayContentLocked(displayId).pendingLayoutChanges;
+    }
+
+    void setPendingLayoutChanges(final int displayId, final int changes) {
+        mService.getDisplayContentLocked(displayId).pendingLayoutChanges |= changes;
+    }
+
+    void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) {
+        // Used to track which displays layout changes have been done.
+        SparseIntArray displays = new SparseIntArray(2);
+        WindowList windows = appAnimator.mAppToken.allAppWindows;
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            final int displayId = windows.get(i).getDisplayId();
+            if (displays.indexOfKey(displayId) < 0) {
+                setPendingLayoutChanges(displayId, changes);
+                if (WindowManagerService.DEBUG_LAYOUT_REPEATS) {
+                    mService.debugLayoutRepeats(s, getPendingLayoutChanges(displayId));
+                }
+                // Keep from processing this display again.
+                displays.put(displayId, changes);
+            }
+        }
+    }
+
+    private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) {
+        DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId);
+        if (displayAnimator == null) {
+            displayAnimator = new DisplayContentsAnimator();
+            mDisplayContentsAnimators.put(displayId, displayAnimator);
+        }
+        return displayAnimator;
+    }
+
+    void setScreenRotationAnimationLocked(int displayId, ScreenRotationAnimation animation) {
+        getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation = animation;
+    }
+
+    ScreenRotationAnimation getScreenRotationAnimationLocked(int displayId) {
+        return getDisplayContentsAnimatorLocked(displayId).mScreenRotationAnimation;
+    }
+
+    private class DisplayContentsAnimator {
+        ScreenRotationAnimation mScreenRotationAnimation = null;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
new file mode 100644
index 0000000..04129e2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -0,0 +1,10827 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.*;
+
+import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
+
+import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+
+import android.app.AppOpsManager;
+import android.util.TimeUtils;
+import android.view.IWindowId;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.policy.PolicyManager;
+import com.android.internal.policy.impl.PhoneWindowManager;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.view.IInputContext;
+import com.android.internal.view.IInputMethodClient;
+import com.android.internal.view.IInputMethodManager;
+import com.android.internal.view.WindowManagerPolicyThread;
+import com.android.server.AttributeCache;
+import com.android.server.EventLogTags;
+import com.android.server.UiThread;
+import com.android.server.Watchdog;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.display.DisplayManagerService;
+import com.android.server.input.InputManagerService;
+import com.android.server.power.PowerManagerService;
+import com.android.server.power.ShutdownThread;
+
+import android.Manifest;
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
+import android.animation.ValueAnimator;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.WorkSource;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.FloatMath;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TypedValue;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IInputFilter;
+import android.view.IMagnificationCallbacks;
+import android.view.IOnKeyguardExitResult;
+import android.view.IRotationWatcher;
+import android.view.IWindow;
+import android.view.IWindowManager;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.MagnificationSpec;
+import android.view.MotionEvent;
+import android.view.Surface.OutOfResourcesException;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicy;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerPolicy.FakeWindow;
+import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import java.io.BufferedWriter;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.Socket;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+/** {@hide} */
+public class WindowManagerService extends IWindowManager.Stub
+        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
+                DisplayManagerService.WindowManagerFuncs, DisplayManager.DisplayListener {
+    static final String TAG = "WindowManager";
+    static final boolean DEBUG = false;
+    static final boolean DEBUG_ADD_REMOVE = false;
+    static final boolean DEBUG_FOCUS = false;
+    static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false;
+    static final boolean DEBUG_ANIM = false;
+    static final boolean DEBUG_LAYOUT = false;
+    static final boolean DEBUG_RESIZE = false;
+    static final boolean DEBUG_LAYERS = false;
+    static final boolean DEBUG_INPUT = false;
+    static final boolean DEBUG_INPUT_METHOD = false;
+    static final boolean DEBUG_VISIBILITY = false;
+    static final boolean DEBUG_WINDOW_MOVEMENT = false;
+    static final boolean DEBUG_TOKEN_MOVEMENT = false;
+    static final boolean DEBUG_ORIENTATION = false;
+    static final boolean DEBUG_APP_ORIENTATION = false;
+    static final boolean DEBUG_CONFIGURATION = false;
+    static final boolean DEBUG_APP_TRANSITIONS = false;
+    static final boolean DEBUG_STARTING_WINDOW = false;
+    static final boolean DEBUG_REORDER = false;
+    static final boolean DEBUG_WALLPAPER = false;
+    static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
+    static final boolean DEBUG_DRAG = false;
+    static final boolean DEBUG_SCREEN_ON = false;
+    static final boolean DEBUG_SCREENSHOT = false;
+    static final boolean DEBUG_BOOT = false;
+    static final boolean DEBUG_LAYOUT_REPEATS = true;
+    static final boolean DEBUG_SURFACE_TRACE = false;
+    static final boolean DEBUG_WINDOW_TRACE = false;
+    static final boolean DEBUG_TASK_MOVEMENT = false;
+    static final boolean DEBUG_STACK = false;
+    static final boolean SHOW_SURFACE_ALLOC = false;
+    static final boolean SHOW_TRANSACTIONS = false;
+    static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS;
+    static final boolean HIDE_STACK_CRAWLS = true;
+    static final int LAYOUT_REPEAT_THRESHOLD = 4;
+
+    static final boolean PROFILE_ORIENTATION = false;
+    static final boolean localLOGV = DEBUG;
+
+    /** How much to multiply the policy's type layer, to reserve room
+     * for multiple windows of the same type and Z-ordering adjustment
+     * with TYPE_LAYER_OFFSET. */
+    static final int TYPE_LAYER_MULTIPLIER = 10000;
+
+    /** Offset from TYPE_LAYER_MULTIPLIER for moving a group of windows above
+     * or below others in the same layer. */
+    static final int TYPE_LAYER_OFFSET = 1000;
+
+    /** How much to increment the layer for each window, to reserve room
+     * for effect surfaces between them.
+     */
+    static final int WINDOW_LAYER_MULTIPLIER = 5;
+
+    /**
+     * Dim surface layer is immediately below target window.
+     */
+    static final int LAYER_OFFSET_DIM = 1;
+
+    /**
+     * Blur surface layer is immediately below dim layer.
+     */
+    static final int LAYER_OFFSET_BLUR = 2;
+
+    /**
+     * FocusedStackFrame layer is immediately above focused window.
+     */
+    static final int LAYER_OFFSET_FOCUSED_STACK = 1;
+
+    /**
+     * Animation thumbnail is as far as possible below the window above
+     * the thumbnail (or in other words as far as possible above the window
+     * below it).
+     */
+    static final int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER-1;
+
+    /**
+     * Layer at which to put the rotation freeze snapshot.
+     */
+    static final int FREEZE_LAYER = (TYPE_LAYER_MULTIPLIER * 200) + 1;
+
+    /**
+     * Layer at which to put the mask for emulated screen sizes.
+     */
+    static final int MASK_LAYER = TYPE_LAYER_MULTIPLIER * 200;
+
+    /** The maximum length we will accept for a loaded animation duration:
+     * this is 10 seconds.
+     */
+    static final int MAX_ANIMATION_DURATION = 10*1000;
+
+    /** Amount of time (in milliseconds) to animate the fade-in-out transition for
+     * compatible windows.
+     */
+    static final int DEFAULT_FADE_IN_OUT_DURATION = 400;
+
+    /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
+    static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
+
+    /** Amount of time (in milliseconds) to delay before declaring a starting window leaked. */
+    static final int STARTING_WINDOW_TIMEOUT_DURATION = 10000;
+
+    /**
+     * If true, the window manager will do its own custom freezing and general
+     * management of the screen during rotation.
+     */
+    static final boolean CUSTOM_SCREEN_ROTATION = true;
+
+    // Maximum number of milliseconds to wait for input devices to be enumerated before
+    // proceding with safe mode detection.
+    private static final int INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS = 1000;
+
+    // Default input dispatching timeout in nanoseconds.
+    static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L;
+
+    /** Minimum value for createStack and resizeStack weight value */
+    public static final float STACK_WEIGHT_MIN = 0.2f;
+
+    /** Maximum value for createStack and resizeStack weight value */
+    public static final float STACK_WEIGHT_MAX = 0.8f;
+
+    static final int UPDATE_FOCUS_NORMAL = 0;
+    static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1;
+    static final int UPDATE_FOCUS_PLACING_SURFACES = 2;
+    static final int UPDATE_FOCUS_WILL_PLACE_SURFACES = 3;
+
+    private static final String SYSTEM_SECURE = "ro.secure";
+    private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
+
+    private static final String DENSITY_OVERRIDE = "ro.config.density_override";
+    private static final String SIZE_OVERRIDE = "ro.config.size_override";
+
+    private static final int MAX_SCREENSHOT_RETRIES = 3;
+
+    final private KeyguardDisableHandler mKeyguardDisableHandler;
+
+    final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) {
+                mKeyguardDisableHandler.sendEmptyMessage(
+                    KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED);
+            }
+        }
+    };
+
+    // Current user when multi-user is enabled. Don't show windows of non-current user.
+    int mCurrentUserId;
+
+    final Context mContext;
+
+    final boolean mHaveInputMethods;
+
+    final boolean mAllowBootMessages;
+
+    final boolean mLimitedAlphaCompositing;
+
+    final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager();
+
+    final IActivityManager mActivityManager;
+
+    final IBatteryStats mBatteryStats;
+
+    final AppOpsManager mAppOps;
+
+    final DisplaySettings mDisplaySettings;
+
+    /**
+     * All currently active sessions with clients.
+     */
+    final HashSet<Session> mSessions = new HashSet<Session>();
+
+    /**
+     * Mapping from an IWindow IBinder to the server's Window object.
+     * This is also used as the lock for all of our state.
+     * NOTE: Never call into methods that lock ActivityManagerService while holding this object.
+     */
+    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<IBinder, WindowState>();
+
+    /**
+     * Mapping from a token IBinder to a WindowToken object.
+     */
+    final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<IBinder, WindowToken>();
+
+    /**
+     * List of window tokens that have finished starting their application,
+     * and now need to have the policy remove their windows.
+     */
+    final ArrayList<AppWindowToken> mFinishedStarting = new ArrayList<AppWindowToken>();
+
+    /**
+     * Fake windows added to the window manager.  Note: ordered from top to
+     * bottom, opposite of mWindows.
+     */
+    final ArrayList<FakeWindowImpl> mFakeWindows = new ArrayList<FakeWindowImpl>();
+
+    /**
+     * Windows that are being resized.  Used so we can tell the client about
+     * the resize after closing the transaction in which we resized the
+     * underlying surface.
+     */
+    final ArrayList<WindowState> mResizingWindows = new ArrayList<WindowState>();
+
+    /**
+     * Windows whose animations have ended and now must be removed.
+     */
+    final ArrayList<WindowState> mPendingRemove = new ArrayList<WindowState>();
+
+    /**
+     * Used when processing mPendingRemove to avoid working on the original array.
+     */
+    WindowState[] mPendingRemoveTmp = new WindowState[20];
+
+    /**
+     * Windows whose surface should be destroyed.
+     */
+    final ArrayList<WindowState> mDestroySurface = new ArrayList<WindowState>();
+
+    /**
+     * Windows that have lost input focus and are waiting for the new
+     * focus window to be displayed before they are told about this.
+     */
+    ArrayList<WindowState> mLosingFocus = new ArrayList<WindowState>();
+
+    /**
+     * This is set when we have run out of memory, and will either be an empty
+     * list or contain windows that need to be force removed.
+     */
+    ArrayList<WindowState> mForceRemoves;
+
+    /**
+     * Windows that clients are waiting to have drawn.
+     */
+    ArrayList<Pair<WindowState, IRemoteCallback>> mWaitingForDrawn
+            = new ArrayList<Pair<WindowState, IRemoteCallback>>();
+
+    /**
+     * Windows that have called relayout() while we were running animations,
+     * so we need to tell when the animation is done.
+     */
+    final ArrayList<WindowState> mRelayoutWhileAnimating = new ArrayList<WindowState>();
+
+    /**
+     * Used when rebuilding window list to keep track of windows that have
+     * been removed.
+     */
+    WindowState[] mRebuildTmp = new WindowState[20];
+
+    IInputMethodManager mInputMethodManager;
+
+    DisplayMagnifier mDisplayMagnifier;
+
+    final SurfaceSession mFxSession;
+    Watermark mWatermark;
+    StrictModeFlash mStrictModeFlash;
+    FocusedStackFrame mFocusedStackFrame;
+
+    int mFocusedStackLayer;
+
+    final float[] mTmpFloats = new float[9];
+    final Rect mTmpContentRect = new Rect();
+
+    boolean mDisplayReady;
+    boolean mSafeMode;
+    boolean mDisplayEnabled = false;
+    boolean mSystemBooted = false;
+    boolean mForceDisplayEnabled = false;
+    boolean mShowingBootMessages = false;
+
+    String mLastANRState;
+
+    /** All DisplayContents in the world, kept here */
+    SparseArray<DisplayContent> mDisplayContents = new SparseArray<DisplayContent>(2);
+
+    int mRotation = 0;
+    int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    boolean mAltOrientation = false;
+    ArrayList<IRotationWatcher> mRotationWatchers
+            = new ArrayList<IRotationWatcher>();
+    int mDeferredRotationPauseCount;
+
+    int mSystemDecorLayer = 0;
+    final Rect mScreenRect = new Rect();
+
+    boolean mTraversalScheduled = false;
+    boolean mDisplayFrozen = false;
+    long mDisplayFreezeTime = 0;
+    int mLastDisplayFreezeDuration = 0;
+    Object mLastFinishedFreezeSource = null;
+    boolean mWaitingForConfig = false;
+    boolean mWindowsFreezingScreen = false;
+    boolean mClientFreezingScreen = false;
+    int mAppsFreezingScreen = 0;
+    int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+
+    int mLayoutSeq = 0;
+
+    int mLastStatusBarVisibility = 0;
+
+    // State while inside of layoutAndPlaceSurfacesLocked().
+    boolean mFocusMayChange;
+
+    Configuration mCurConfiguration = new Configuration();
+
+    // This is held as long as we have the screen frozen, to give us time to
+    // perform a rotation animation when turning off shows the lock screen which
+    // changes the orientation.
+    private final PowerManager.WakeLock mScreenFrozenLock;
+
+    final AppTransition mAppTransition;
+    boolean mStartingIconInTransition = false;
+    boolean mSkipAppTransitionAnimation = false;
+
+    final ArrayList<AppWindowToken> mOpeningApps = new ArrayList<AppWindowToken>();
+    final ArrayList<AppWindowToken> mClosingApps = new ArrayList<AppWindowToken>();
+
+    boolean mIsTouchDevice;
+
+    final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
+    final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
+    final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics();
+
+    final H mH = new H();
+
+    final Choreographer mChoreographer = Choreographer.getInstance();
+
+    WindowState mCurrentFocus = null;
+    WindowState mLastFocus = null;
+
+    /** This just indicates the window the input method is on top of, not
+     * necessarily the window its input is going to. */
+    WindowState mInputMethodTarget = null;
+
+    /** If true hold off on modifying the animation layer of mInputMethodTarget */
+    boolean mInputMethodTargetWaitingAnim;
+    int mInputMethodAnimLayerAdjustment;
+
+    WindowState mInputMethodWindow = null;
+    final ArrayList<WindowState> mInputMethodDialogs = new ArrayList<WindowState>();
+
+    boolean mHardKeyboardAvailable;
+    boolean mHardKeyboardEnabled;
+    OnHardKeyboardStatusChangeListener mHardKeyboardStatusChangeListener;
+
+    final ArrayList<WindowToken> mWallpaperTokens = new ArrayList<WindowToken>();
+
+    // If non-null, this is the currently visible window that is associated
+    // with the wallpaper.
+    WindowState mWallpaperTarget = null;
+    // If non-null, we are in the middle of animating from one wallpaper target
+    // to another, and this is the lower one in Z-order.
+    WindowState mLowerWallpaperTarget = null;
+    // If non-null, we are in the middle of animating from one wallpaper target
+    // to another, and this is the higher one in Z-order.
+    WindowState mUpperWallpaperTarget = null;
+    int mWallpaperAnimLayerAdjustment;
+    float mLastWallpaperX = -1;
+    float mLastWallpaperY = -1;
+    float mLastWallpaperXStep = -1;
+    float mLastWallpaperYStep = -1;
+    // This is set when we are waiting for a wallpaper to tell us it is done
+    // changing its scroll position.
+    WindowState mWaitingOnWallpaper;
+    // The last time we had a timeout when waiting for a wallpaper.
+    long mLastWallpaperTimeoutTime;
+    // We give a wallpaper up to 150ms to finish scrolling.
+    static final long WALLPAPER_TIMEOUT = 150;
+    // Time we wait after a timeout before trying to wait again.
+    static final long WALLPAPER_TIMEOUT_RECOVERY = 10000;
+    boolean mAnimateWallpaperWithTarget;
+
+    AppWindowToken mFocusedApp = null;
+
+    PowerManagerService mPowerManager;
+
+    float mWindowAnimationScale = 1.0f;
+    float mTransitionAnimationScale = 1.0f;
+    float mAnimatorDurationScale = 1.0f;
+
+    final InputManagerService mInputManager;
+    final DisplayManagerService mDisplayManagerService;
+    final DisplayManager mDisplayManager;
+
+    // Who is holding the screen on.
+    Session mHoldingScreenOn;
+    PowerManager.WakeLock mHoldingScreenWakeLock;
+
+    boolean mTurnOnScreen;
+
+    DragState mDragState = null;
+
+    // For frozen screen animations.
+    int mExitAnimId, mEnterAnimId;
+
+    /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple
+     * methods. */
+    class LayoutFields {
+        static final int SET_UPDATE_ROTATION                = 1 << 0;
+        static final int SET_WALLPAPER_MAY_CHANGE           = 1 << 1;
+        static final int SET_FORCE_HIDING_CHANGED           = 1 << 2;
+        static final int SET_ORIENTATION_CHANGE_COMPLETE    = 1 << 3;
+        static final int SET_TURN_ON_SCREEN                 = 1 << 4;
+        static final int SET_WALLPAPER_ACTION_PENDING       = 1 << 5;
+
+        boolean mWallpaperForceHidingChanged = false;
+        boolean mWallpaperMayChange = false;
+        boolean mOrientationChangeComplete = true;
+        Object mLastWindowFreezeSource = null;
+        private Session mHoldScreen = null;
+        private boolean mObscured = false;
+        private boolean mSyswin = false;
+        private float mScreenBrightness = -1;
+        private float mButtonBrightness = -1;
+        private long mUserActivityTimeout = -1;
+        private boolean mUpdateRotation = false;
+        boolean mWallpaperActionPending = false;
+
+        // Set to true when the display contains content to show the user.
+        // When false, the display manager may choose to mirror or blank the display.
+        boolean mDisplayHasContent = false;
+
+        // Only set while traversing the default display based on its content.
+        // Affects the behavior of mirroring on secondary displays.
+        boolean mObscureApplicationContentOnSecondaryDisplays = false;
+    }
+    final LayoutFields mInnerFields = new LayoutFields();
+
+    boolean mAnimationScheduled;
+
+    /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this
+     * is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
+    private int mTransactionSequence;
+
+    /** Only do a maximum of 6 repeated layouts. After that quit */
+    private int mLayoutRepeatCount;
+
+    final WindowAnimator mAnimator;
+
+    SparseArray<Task> mTaskIdToTask = new SparseArray<Task>();
+    SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>();
+
+    private final PointerEventDispatcher mPointerEventDispatcher;
+
+    final class DragInputEventReceiver extends InputEventReceiver {
+        public DragInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            boolean handled = false;
+            try {
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0
+                        && mDragState != null) {
+                    final MotionEvent motionEvent = (MotionEvent)event;
+                    boolean endDrag = false;
+                    final float newX = motionEvent.getRawX();
+                    final float newY = motionEvent.getRawY();
+
+                    switch (motionEvent.getAction()) {
+                    case MotionEvent.ACTION_DOWN: {
+                        if (DEBUG_DRAG) {
+                            Slog.w(TAG, "Unexpected ACTION_DOWN in drag layer");
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_MOVE: {
+                        synchronized (mWindowMap) {
+                            // move the surface and tell the involved window(s) where we are
+                            mDragState.notifyMoveLw(newX, newY);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_UP: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Got UP on move channel; dropping at "
+                                + newX + "," + newY);
+                        synchronized (mWindowMap) {
+                            endDrag = mDragState.notifyDropLw(newX, newY);
+                        }
+                    } break;
+
+                    case MotionEvent.ACTION_CANCEL: {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag cancelled!");
+                        endDrag = true;
+                    } break;
+                    }
+
+                    if (endDrag) {
+                        if (DEBUG_DRAG) Slog.d(TAG, "Drag ended; tearing down state");
+                        // tell all the windows that the drag has ended
+                        synchronized (mWindowMap) {
+                            mDragState.endDragLw();
+                        }
+                    }
+
+                    handled = true;
+                }
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception caught by drag handleMotion", e);
+            } finally {
+                finishInputEvent(event, handled);
+            }
+        }
+    }
+
+    /**
+     * Whether the UI is currently running in touch mode (not showing
+     * navigational focus because the user is directly pressing the screen).
+     */
+    boolean mInTouchMode = true;
+
+    private ViewServer mViewServer;
+    private final ArrayList<WindowChangeListener> mWindowChangeListeners =
+        new ArrayList<WindowChangeListener>();
+    private boolean mWindowsChanged = false;
+
+    public interface WindowChangeListener {
+        public void windowsChanged();
+        public void focusChanged();
+    }
+
+    final Configuration mTempConfiguration = new Configuration();
+
+    // The desired scaling factor for compatible apps.
+    float mCompatibleScreenScale;
+
+    // If true, only the core apps and services are being launched because the device
+    // is in a special boot mode, such as being encrypted or waiting for a decryption password.
+    // For example, when this flag is true, there will be no wallpaper service.
+    final boolean mOnlyCore;
+
+    public static WindowManagerService main(final Context context,
+            final PowerManagerService pm, final DisplayManagerService dm,
+            final InputManagerService im, final Handler wmHandler,
+            final boolean haveInputMethods, final boolean showBootMsgs,
+            final boolean onlyCore) {
+        final WindowManagerService[] holder = new WindowManagerService[1];
+        wmHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                holder[0] = new WindowManagerService(context, pm, dm, im,
+                        haveInputMethods, showBootMsgs, onlyCore);
+            }
+        }, 0);
+        return holder[0];
+    }
+
+    private void initPolicy(Handler uiHandler) {
+        uiHandler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
+
+                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+                mAnimator.mAboveUniverseLayer = mPolicy.getAboveUniverseLayer()
+                        * TYPE_LAYER_MULTIPLIER
+                        + TYPE_LAYER_OFFSET;
+            }
+        }, 0);
+    }
+
+    private WindowManagerService(Context context, PowerManagerService pm,
+            DisplayManagerService displayManager, InputManagerService inputManager,
+            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
+        mContext = context;
+        mHaveInputMethods = haveInputMethods;
+        mAllowBootMessages = showBootMsgs;
+        mOnlyCore = onlyCore;
+        mLimitedAlphaCompositing = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_sf_limitedAlpha);
+        mInputManager = inputManager; // Must be before createDisplayContentLocked.
+        mDisplayManagerService = displayManager;
+        mDisplaySettings = new DisplaySettings(context);
+        mDisplaySettings.readSettingsLocked();
+
+        mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG));
+
+        mFxSession = new SurfaceSession();
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        mDisplayManager.registerDisplayListener(this, null);
+        Display[] displays = mDisplayManager.getDisplays();
+        for (Display display : displays) {
+            createDisplayContentLocked(display);
+        }
+
+        mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
+
+        mPowerManager = pm;
+        mPowerManager.setPolicy(mPolicy);
+        PowerManager pmc = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+        mScreenFrozenLock = pmc.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
+        mScreenFrozenLock.setReferenceCounted(false);
+
+        mAppTransition = new AppTransition(context, mH);
+
+        mActivityManager = ActivityManagerNative.getDefault();
+        mBatteryStats = BatteryStatsService.getService();
+        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+        mAppOps.startWatchingMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, null,
+                new AppOpsManager.OnOpChangedInternalListener() {
+                    @Override
+                    public void onOpChanged(int op, String packageName) {
+                        updateAppOpsState();
+                    }
+                }
+        );
+
+        // Get persisted window scale setting
+        mWindowAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
+        mTransitionAnimationScale = Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+        setAnimatorDurationScale(Settings.Global.getFloat(context.getContentResolver(),
+                Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale));
+
+        // Track changes to DevicePolicyManager state so we can enable/disable keyguard.
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        mHoldingScreenWakeLock = pmc.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+                | PowerManager.ON_AFTER_RELEASE, TAG);
+        mHoldingScreenWakeLock.setReferenceCounted(false);
+
+        mAnimator = new WindowAnimator(this);
+
+        initPolicy(UiThread.getHandler());
+
+        // Add ourself to the Watchdog monitors.
+        Watchdog.getInstance().addMonitor(this);
+
+        SurfaceControl.openTransaction();
+        try {
+            createWatermarkInTransaction();
+            mFocusedStackFrame = new FocusedStackFrame(
+                    getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+        } finally {
+            SurfaceControl.closeTransaction();
+        }
+    }
+
+    public InputMonitor getInputMonitor() {
+        return mInputMonitor;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            // The window manager only throws security exceptions, so let's
+            // log all others.
+            if (!(e instanceof SecurityException)) {
+                Slog.wtf(TAG, "Window Manager Crash", e);
+            }
+            throw e;
+        }
+    }
+
+    private void placeWindowAfter(WindowState pos, WindowState window) {
+        final WindowList windows = pos.getWindowList();
+        final int i = windows.indexOf(pos);
+        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
+            TAG, "Adding window " + window + " at "
+            + (i+1) + " of " + windows.size() + " (after " + pos + ")");
+        windows.add(i+1, window);
+        mWindowsChanged = true;
+    }
+
+    private void placeWindowBefore(WindowState pos, WindowState window) {
+        final WindowList windows = pos.getWindowList();
+        int i = windows.indexOf(pos);
+        if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
+            TAG, "Adding window " + window + " at "
+            + i + " of " + windows.size() + " (before " + pos + ")");
+        if (i < 0) {
+            Slog.w(TAG, "placeWindowBefore: Unable to find " + pos + " in " + windows);
+            i = 0;
+        }
+        windows.add(i, window);
+        mWindowsChanged = true;
+    }
+
+    //This method finds out the index of a window that has the same app token as
+    //win. used for z ordering the windows in mWindows
+    private int findIdxBasedOnAppTokens(WindowState win) {
+        WindowList windows = win.getWindowList();
+        for(int j = windows.size() - 1; j >= 0; j--) {
+            WindowState wentry = windows.get(j);
+            if(wentry.mAppToken == win.mAppToken) {
+                return j;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Return the list of Windows from the passed token on the given Display.
+     * @param token The token with all the windows.
+     * @param displayContent The display we are interested in.
+     * @return List of windows from token that are on displayContent.
+     */
+    WindowList getTokenWindowsOnDisplay(WindowToken token, DisplayContent displayContent) {
+        final WindowList windowList = new WindowList();
+        final int count = token.windows.size();
+        for (int i = 0; i < count; i++) {
+            final WindowState win = token.windows.get(i);
+            if (win.mDisplayContent == displayContent) {
+                windowList.add(win);
+            }
+        }
+        return windowList;
+    }
+
+    /**
+     * Recursive search through a WindowList and all of its windows' children.
+     * @param targetWin The window to search for.
+     * @param windows The list to search.
+     * @return The index of win in windows or of the window that is an ancestor of win.
+     */
+    private int indexOfWinInWindowList(WindowState targetWin, WindowList windows) {
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            final WindowState w = windows.get(i);
+            if (w == targetWin) {
+                return i;
+            }
+            if (!w.mChildWindows.isEmpty()) {
+                if (indexOfWinInWindowList(targetWin, w.mChildWindows) >= 0) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    private int addAppWindowToListLocked(final WindowState win) {
+        final IWindow client = win.mClient;
+        final WindowToken token = win.mToken;
+        final DisplayContent displayContent = win.mDisplayContent;
+
+        final WindowList windows = win.getWindowList();
+        final int N = windows.size();
+        WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
+        int tokenWindowsPos = 0;
+        int windowListPos = tokenWindowList.size();
+        if (!tokenWindowList.isEmpty()) {
+            // If this application has existing windows, we
+            // simply place the new window on top of them... but
+            // keep the starting window on top.
+            if (win.mAttrs.type == TYPE_BASE_APPLICATION) {
+                // Base windows go behind everything else.
+                WindowState lowestWindow = tokenWindowList.get(0);
+                placeWindowBefore(lowestWindow, win);
+                tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows);
+            } else {
+                AppWindowToken atoken = win.mAppToken;
+                WindowState lastWindow = tokenWindowList.get(windowListPos - 1);
+                if (atoken != null && lastWindow == atoken.startingWindow) {
+                    placeWindowBefore(lastWindow, win);
+                    tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows);
+                } else {
+                    int newIdx = findIdxBasedOnAppTokens(win);
+                    //there is a window above this one associated with the same
+                    //apptoken note that the window could be a floating window
+                    //that was created later or a window at the top of the list of
+                    //windows associated with this token.
+                    if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                            "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " +
+                            N);
+                    windows.add(newIdx + 1, win);
+                    if (newIdx < 0) {
+                        // No window from token found on win's display.
+                        tokenWindowsPos = 0;
+                    } else {
+                        tokenWindowsPos = indexOfWinInWindowList(
+                                windows.get(newIdx), token.windows) + 1;
+                    }
+                    mWindowsChanged = true;
+                }
+            }
+            return tokenWindowsPos;
+        }
+
+        // No windows from this token on this display
+        if (localLOGV) Slog.v(TAG, "Figuring out where to add app window " + client.asBinder()
+                + " (token=" + token + ")");
+        // Figure out where the window should go, based on the
+        // order of applications.
+        WindowState pos = null;
+
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        int taskNdx;
+        int tokenNdx = -1;
+        for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
+                final AppWindowToken t = tokens.get(tokenNdx);
+                if (t == token) {
+                    --tokenNdx;
+                    if (tokenNdx < 0) {
+                        --taskNdx;
+                        if (taskNdx >= 0) {
+                            tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1;
+                        }
+                    }
+                    break;
+                }
+
+                // We haven't reached the token yet; if this token
+                // is not going to the bottom and has windows on this display, we can
+                // use it as an anchor for when we do reach the token.
+                tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
+                if (!t.sendingToBottom && tokenWindowList.size() > 0) {
+                    pos = tokenWindowList.get(0);
+                }
+            }
+            if (tokenNdx >= 0) {
+                // early exit
+                break;
+            }
+        }
+
+        // We now know the index into the apps.  If we found
+        // an app window above, that gives us the position; else
+        // we need to look some more.
+        if (pos != null) {
+            // Move behind any windows attached to this one.
+            WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
+            if (atoken != null) {
+                tokenWindowList =
+                        getTokenWindowsOnDisplay(atoken, displayContent);
+                final int NC = tokenWindowList.size();
+                if (NC > 0) {
+                    WindowState bottom = tokenWindowList.get(0);
+                    if (bottom.mSubLayer < 0) {
+                        pos = bottom;
+                    }
+                }
+            }
+            placeWindowBefore(pos, win);
+            return tokenWindowsPos;
+        }
+
+        // Continue looking down until we find the first
+        // token that has windows on this display.
+        for ( ; taskNdx >= 0; --taskNdx) {
+            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            for ( ; tokenNdx >= 0; --tokenNdx) {
+                final AppWindowToken t = tokens.get(tokenNdx);
+                tokenWindowList = getTokenWindowsOnDisplay(t, displayContent);
+                final int NW = tokenWindowList.size();
+                if (NW > 0) {
+                    pos = tokenWindowList.get(NW-1);
+                    break;
+                }
+            }
+            if (tokenNdx >= 0) {
+                // found
+                break;
+            }
+        }
+
+        if (pos != null) {
+            // Move in front of any windows attached to this
+            // one.
+            WindowToken atoken = mTokenMap.get(pos.mClient.asBinder());
+            if (atoken != null) {
+                final int NC = atoken.windows.size();
+                if (NC > 0) {
+                    WindowState top = atoken.windows.get(NC-1);
+                    if (top.mSubLayer >= 0) {
+                        pos = top;
+                    }
+                }
+            }
+            placeWindowAfter(pos, win);
+            return tokenWindowsPos;
+        }
+
+        // Just search for the start of this layer.
+        final int myLayer = win.mBaseLayer;
+        int i;
+        for (i = 0; i < N; i++) {
+            WindowState w = windows.get(i);
+            if (w.mBaseLayer > myLayer) {
+                break;
+            }
+        }
+        if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                "Based on layer: Adding window " + win + " at " + i + " of " + N);
+        windows.add(i, win);
+        mWindowsChanged = true;
+        return tokenWindowsPos;
+    }
+
+    private void addFreeWindowToListLocked(final WindowState win) {
+        final WindowList windows = win.getWindowList();
+
+        // Figure out where window should go, based on layer.
+        final int myLayer = win.mBaseLayer;
+        int i;
+        for (i = windows.size() - 1; i >= 0; i--) {
+            if (windows.get(i).mBaseLayer <= myLayer) {
+                break;
+            }
+        }
+        i++;
+        if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                "Free window: Adding window " + win + " at " + i + " of " + windows.size());
+        windows.add(i, win);
+        mWindowsChanged = true;
+    }
+
+    private void addAttachedWindowToListLocked(final WindowState win, boolean addToToken) {
+        final WindowToken token = win.mToken;
+        final DisplayContent displayContent = win.mDisplayContent;
+        final WindowState attached = win.mAttachedWindow;
+
+        WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent);
+
+        // Figure out this window's ordering relative to the window
+        // it is attached to.
+        final int NA = tokenWindowList.size();
+        final int sublayer = win.mSubLayer;
+        int largestSublayer = Integer.MIN_VALUE;
+        WindowState windowWithLargestSublayer = null;
+        int i;
+        for (i = 0; i < NA; i++) {
+            WindowState w = tokenWindowList.get(i);
+            final int wSublayer = w.mSubLayer;
+            if (wSublayer >= largestSublayer) {
+                largestSublayer = wSublayer;
+                windowWithLargestSublayer = w;
+            }
+            if (sublayer < 0) {
+                // For negative sublayers, we go below all windows
+                // in the same sublayer.
+                if (wSublayer >= sublayer) {
+                    if (addToToken) {
+                        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
+                        token.windows.add(i, win);
+                    }
+                    placeWindowBefore(wSublayer >= 0 ? attached : w, win);
+                    break;
+                }
+            } else {
+                // For positive sublayers, we go above all windows
+                // in the same sublayer.
+                if (wSublayer > sublayer) {
+                    if (addToToken) {
+                        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
+                        token.windows.add(i, win);
+                    }
+                    placeWindowBefore(w, win);
+                    break;
+                }
+            }
+        }
+        if (i >= NA) {
+            if (addToToken) {
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
+                token.windows.add(win);
+            }
+            if (sublayer < 0) {
+                placeWindowBefore(attached, win);
+            } else {
+                placeWindowAfter(largestSublayer >= 0
+                                 ? windowWithLargestSublayer
+                                 : attached,
+                                 win);
+            }
+        }
+    }
+
+    private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) {
+        if (DEBUG_FOCUS_LIGHT) Slog.d(TAG, "addWindowToListInOrderLocked: win=" + win +
+                " Callers=" + Debug.getCallers(4));
+        if (win.mAttachedWindow == null) {
+            final WindowToken token = win.mToken;
+            int tokenWindowsPos = 0;
+            if (token.appWindowToken != null) {
+                tokenWindowsPos = addAppWindowToListLocked(win);
+            } else {
+                addFreeWindowToListLocked(win);
+            }
+            if (addToToken) {
+                if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token);
+                token.windows.add(tokenWindowsPos, win);
+            }
+        } else {
+            addAttachedWindowToListLocked(win, addToToken);
+        }
+
+        if (win.mAppToken != null && addToToken) {
+            win.mAppToken.allAppWindows.add(win);
+        }
+    }
+
+    static boolean canBeImeTarget(WindowState w) {
+        final int fl = w.mAttrs.flags
+                & (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM);
+        if (fl == 0 || fl == (FLAG_NOT_FOCUSABLE|FLAG_ALT_FOCUSABLE_IM)
+                || w.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            if (DEBUG_INPUT_METHOD) {
+                Slog.i(TAG, "isVisibleOrAdding " + w + ": " + w.isVisibleOrAdding());
+                if (!w.isVisibleOrAdding()) {
+                    Slog.i(TAG, "  mSurface=" + w.mWinAnimator.mSurfaceControl
+                            + " relayoutCalled=" + w.mRelayoutCalled + " viewVis=" + w.mViewVisibility
+                            + " policyVis=" + w.mPolicyVisibility
+                            + " policyVisAfterAnim=" + w.mPolicyVisibilityAfterAnim
+                            + " attachHid=" + w.mAttachedHidden
+                            + " exiting=" + w.mExiting + " destroying=" + w.mDestroying);
+                    if (w.mAppToken != null) {
+                        Slog.i(TAG, "  mAppToken.hiddenRequested=" + w.mAppToken.hiddenRequested);
+                    }
+                }
+            }
+            return w.isVisibleOrAdding();
+        }
+        return false;
+    }
+
+    /**
+     * Dig through the WindowStates and find the one that the Input Method will target.
+     * @param willMove
+     * @return The index+1 in mWindows of the discovered target.
+     */
+    int findDesiredInputMethodWindowIndexLocked(boolean willMove) {
+        // TODO(multidisplay): Needs some serious rethought when the target and IME are not on the
+        // same display. Or even when the current IME/target are not on the same screen as the next
+        // IME/target. For now only look for input windows on the main screen.
+        WindowList windows = getDefaultWindowListLocked();
+        WindowState w = null;
+        int i;
+        for (i = windows.size() - 1; i >= 0; --i) {
+            WindowState win = windows.get(i);
+
+            if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG, "Checking window @" + i
+                    + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags));
+            if (canBeImeTarget(win)) {
+                w = win;
+                //Slog.i(TAG, "Putting input method here!");
+
+                // Yet more tricksyness!  If this window is a "starting"
+                // window, we do actually want to be on top of it, but
+                // it is not -really- where input will go.  So if the caller
+                // is not actually looking to move the IME, look down below
+                // for a real window to target...
+                if (!willMove
+                        && w.mAttrs.type == TYPE_APPLICATION_STARTING
+                        && i > 0) {
+                    WindowState wb = windows.get(i-1);
+                    if (wb.mAppToken == w.mAppToken && canBeImeTarget(wb)) {
+                        i--;
+                        w = wb;
+                    }
+                }
+                break;
+            }
+        }
+
+        // Now w is either mWindows[0] or an IME (or null if mWindows is empty).
+
+        if (DEBUG_INPUT_METHOD && willMove) Slog.v(TAG, "Proposed new IME target: " + w);
+
+        // Now, a special case -- if the last target's window is in the
+        // process of exiting, and is above the new target, keep on the
+        // last target to avoid flicker.  Consider for example a Dialog with
+        // the IME shown: when the Dialog is dismissed, we want to keep
+        // the IME above it until it is completely gone so it doesn't drop
+        // behind the dialog or its full-screen scrim.
+        final WindowState curTarget = mInputMethodTarget;
+        if (curTarget != null
+                && curTarget.isDisplayedLw()
+                && curTarget.isClosing()
+                && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) {
+            if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, not changing");
+            return windows.indexOf(curTarget) + 1;
+        }
+
+        if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Desired input method target="
+                + w + " willMove=" + willMove);
+
+        if (willMove && w != null) {
+            AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
+            if (token != null) {
+
+                // Now some fun for dealing with window animations that
+                // modify the Z order.  We need to look at all windows below
+                // the current target that are in this app, finding the highest
+                // visible one in layering.
+                WindowState highestTarget = null;
+                int highestPos = 0;
+                if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) {
+                    WindowList curWindows = curTarget.getWindowList();
+                    int pos = curWindows.indexOf(curTarget);
+                    while (pos >= 0) {
+                        WindowState win = curWindows.get(pos);
+                        if (win.mAppToken != token) {
+                            break;
+                        }
+                        if (!win.mRemoved) {
+                            if (highestTarget == null || win.mWinAnimator.mAnimLayer >
+                                    highestTarget.mWinAnimator.mAnimLayer) {
+                                highestTarget = win;
+                                highestPos = pos;
+                            }
+                        }
+                        pos--;
+                    }
+                }
+
+                if (highestTarget != null) {
+                    if (DEBUG_INPUT_METHOD) Slog.v(TAG, mAppTransition + " " + highestTarget
+                            + " animating=" + highestTarget.mWinAnimator.isAnimating()
+                            + " layer=" + highestTarget.mWinAnimator.mAnimLayer
+                            + " new layer=" + w.mWinAnimator.mAnimLayer);
+
+                    if (mAppTransition.isTransitionSet()) {
+                        // If we are currently setting up for an animation,
+                        // hold everything until we can find out what will happen.
+                        mInputMethodTargetWaitingAnim = true;
+                        mInputMethodTarget = highestTarget;
+                        return highestPos + 1;
+                    } else if (highestTarget.mWinAnimator.isAnimating() &&
+                            highestTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer) {
+                        // If the window we are currently targeting is involved
+                        // with an animation, and it is on top of the next target
+                        // we will be over, then hold off on moving until
+                        // that is done.
+                        mInputMethodTargetWaitingAnim = true;
+                        mInputMethodTarget = highestTarget;
+                        return highestPos + 1;
+                    }
+                }
+            }
+        }
+
+        //Slog.i(TAG, "Placing input method @" + (i+1));
+        if (w != null) {
+            if (willMove) {
+                if (DEBUG_INPUT_METHOD) Slog.w(TAG, "Moving IM target from " + curTarget + " to "
+                        + w + (HIDE_STACK_CRAWLS ? "" : " Callers=" + Debug.getCallers(4)));
+                mInputMethodTarget = w;
+                mInputMethodTargetWaitingAnim = false;
+                if (w.mAppToken != null) {
+                    setInputMethodAnimLayerAdjustment(w.mAppToken.mAppAnimator.animLayerAdjustment);
+                } else {
+                    setInputMethodAnimLayerAdjustment(0);
+                }
+            }
+            return i+1;
+        }
+        if (willMove) {
+            if (DEBUG_INPUT_METHOD) Slog.w(TAG, "Moving IM target from " + curTarget + " to null."
+                    + (HIDE_STACK_CRAWLS ? "" : " Callers=" + Debug.getCallers(4)));
+            mInputMethodTarget = null;
+            setInputMethodAnimLayerAdjustment(0);
+        }
+        return -1;
+    }
+
+    void addInputMethodWindowToListLocked(WindowState win) {
+        int pos = findDesiredInputMethodWindowIndexLocked(true);
+        if (pos >= 0) {
+            win.mTargetAppToken = mInputMethodTarget.mAppToken;
+            if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(
+                    TAG, "Adding input method window " + win + " at " + pos);
+            // TODO(multidisplay): IMEs are only supported on the default display.
+            getDefaultWindowListLocked().add(pos, win);
+            mWindowsChanged = true;
+            moveInputMethodDialogsLocked(pos+1);
+            return;
+        }
+        win.mTargetAppToken = null;
+        addWindowToListInOrderLocked(win, true);
+        moveInputMethodDialogsLocked(pos);
+    }
+
+    void setInputMethodAnimLayerAdjustment(int adj) {
+        if (DEBUG_LAYERS) Slog.v(TAG, "Setting im layer adj to " + adj);
+        mInputMethodAnimLayerAdjustment = adj;
+        WindowState imw = mInputMethodWindow;
+        if (imw != null) {
+            imw.mWinAnimator.mAnimLayer = imw.mLayer + adj;
+            if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + imw
+                    + " anim layer: " + imw.mWinAnimator.mAnimLayer);
+            int wi = imw.mChildWindows.size();
+            while (wi > 0) {
+                wi--;
+                WindowState cw = imw.mChildWindows.get(wi);
+                cw.mWinAnimator.mAnimLayer = cw.mLayer + adj;
+                if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + cw
+                        + " anim layer: " + cw.mWinAnimator.mAnimLayer);
+            }
+        }
+        int di = mInputMethodDialogs.size();
+        while (di > 0) {
+            di --;
+            imw = mInputMethodDialogs.get(di);
+            imw.mWinAnimator.mAnimLayer = imw.mLayer + adj;
+            if (DEBUG_LAYERS) Slog.v(TAG, "IM win " + imw
+                    + " anim layer: " + imw.mWinAnimator.mAnimLayer);
+        }
+    }
+
+    private int tmpRemoveWindowLocked(int interestingPos, WindowState win) {
+        WindowList windows = win.getWindowList();
+        int wpos = windows.indexOf(win);
+        if (wpos >= 0) {
+            if (wpos < interestingPos) interestingPos--;
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing at " + wpos + ": " + win);
+            windows.remove(wpos);
+            mWindowsChanged = true;
+            int NC = win.mChildWindows.size();
+            while (NC > 0) {
+                NC--;
+                WindowState cw = win.mChildWindows.get(NC);
+                int cpos = windows.indexOf(cw);
+                if (cpos >= 0) {
+                    if (cpos < interestingPos) interestingPos--;
+                    if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Temp removing child at "
+                            + cpos + ": " + cw);
+                    windows.remove(cpos);
+                }
+            }
+        }
+        return interestingPos;
+    }
+
+    private void reAddWindowToListInOrderLocked(WindowState win) {
+        addWindowToListInOrderLocked(win, false);
+        // This is a hack to get all of the child windows added as well
+        // at the right position.  Child windows should be rare and
+        // this case should be rare, so it shouldn't be that big a deal.
+        WindowList windows = win.getWindowList();
+        int wpos = windows.indexOf(win);
+        if (wpos >= 0) {
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "ReAdd removing from " + wpos + ": " + win);
+            windows.remove(wpos);
+            mWindowsChanged = true;
+            reAddWindowLocked(wpos, win);
+        }
+    }
+
+    void logWindowList(final WindowList windows, String prefix) {
+        int N = windows.size();
+        while (N > 0) {
+            N--;
+            Slog.v(TAG, prefix + "#" + N + ": " + windows.get(N));
+        }
+    }
+
+    void moveInputMethodDialogsLocked(int pos) {
+        ArrayList<WindowState> dialogs = mInputMethodDialogs;
+
+        // TODO(multidisplay): IMEs are only supported on the default display.
+        WindowList windows = getDefaultWindowListLocked();
+        final int N = dialogs.size();
+        if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Removing " + N + " dialogs w/pos=" + pos);
+        for (int i=0; i<N; i++) {
+            pos = tmpRemoveWindowLocked(pos, dialogs.get(i));
+        }
+        if (DEBUG_INPUT_METHOD) {
+            Slog.v(TAG, "Window list w/pos=" + pos);
+            logWindowList(windows, "  ");
+        }
+
+        if (pos >= 0) {
+            final AppWindowToken targetAppToken = mInputMethodTarget.mAppToken;
+            if (pos < windows.size()) {
+                WindowState wp = windows.get(pos);
+                if (wp == mInputMethodWindow) {
+                    pos++;
+                }
+            }
+            if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Adding " + N + " dialogs at pos=" + pos);
+            for (int i=0; i<N; i++) {
+                WindowState win = dialogs.get(i);
+                win.mTargetAppToken = targetAppToken;
+                pos = reAddWindowLocked(pos, win);
+            }
+            if (DEBUG_INPUT_METHOD) {
+                Slog.v(TAG, "Final window list:");
+                logWindowList(windows, "  ");
+            }
+            return;
+        }
+        for (int i=0; i<N; i++) {
+            WindowState win = dialogs.get(i);
+            win.mTargetAppToken = null;
+            reAddWindowToListInOrderLocked(win);
+            if (DEBUG_INPUT_METHOD) {
+                Slog.v(TAG, "No IM target, final list:");
+                logWindowList(windows, "  ");
+            }
+        }
+    }
+
+    boolean moveInputMethodWindowsIfNeededLocked(boolean needAssignLayers) {
+        final WindowState imWin = mInputMethodWindow;
+        final int DN = mInputMethodDialogs.size();
+        if (imWin == null && DN == 0) {
+            return false;
+        }
+
+        // TODO(multidisplay): IMEs are only supported on the default display.
+        WindowList windows = getDefaultWindowListLocked();
+
+        int imPos = findDesiredInputMethodWindowIndexLocked(true);
+        if (imPos >= 0) {
+            // In this case, the input method windows are to be placed
+            // immediately above the window they are targeting.
+
+            // First check to see if the input method windows are already
+            // located here, and contiguous.
+            final int N = windows.size();
+            WindowState firstImWin = imPos < N
+                    ? windows.get(imPos) : null;
+
+            // Figure out the actual input method window that should be
+            // at the bottom of their stack.
+            WindowState baseImWin = imWin != null
+                    ? imWin : mInputMethodDialogs.get(0);
+            if (baseImWin.mChildWindows.size() > 0) {
+                WindowState cw = baseImWin.mChildWindows.get(0);
+                if (cw.mSubLayer < 0) baseImWin = cw;
+            }
+
+            if (firstImWin == baseImWin) {
+                // The windows haven't moved...  but are they still contiguous?
+                // First find the top IM window.
+                int pos = imPos+1;
+                while (pos < N) {
+                    if (!(windows.get(pos)).mIsImWindow) {
+                        break;
+                    }
+                    pos++;
+                }
+                pos++;
+                // Now there should be no more input method windows above.
+                while (pos < N) {
+                    if ((windows.get(pos)).mIsImWindow) {
+                        break;
+                    }
+                    pos++;
+                }
+                if (pos >= N) {
+                    // Z order is good.
+                    // The IM target window may be changed, so update the mTargetAppToken.
+                    if (imWin != null) {
+                        imWin.mTargetAppToken = mInputMethodTarget.mAppToken;
+                    }
+                    return false;
+                }
+            }
+
+            if (imWin != null) {
+                if (DEBUG_INPUT_METHOD) {
+                    Slog.v(TAG, "Moving IM from " + imPos);
+                    logWindowList(windows, "  ");
+                }
+                imPos = tmpRemoveWindowLocked(imPos, imWin);
+                if (DEBUG_INPUT_METHOD) {
+                    Slog.v(TAG, "List after removing with new pos " + imPos + ":");
+                    logWindowList(windows, "  ");
+                }
+                imWin.mTargetAppToken = mInputMethodTarget.mAppToken;
+                reAddWindowLocked(imPos, imWin);
+                if (DEBUG_INPUT_METHOD) {
+                    Slog.v(TAG, "List after moving IM to " + imPos + ":");
+                    logWindowList(windows, "  ");
+                }
+                if (DN > 0) moveInputMethodDialogsLocked(imPos+1);
+            } else {
+                moveInputMethodDialogsLocked(imPos);
+            }
+
+        } else {
+            // In this case, the input method windows go in a fixed layer,
+            // because they aren't currently associated with a focus window.
+
+            if (imWin != null) {
+                if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Moving IM from " + imPos);
+                tmpRemoveWindowLocked(0, imWin);
+                imWin.mTargetAppToken = null;
+                reAddWindowToListInOrderLocked(imWin);
+                if (DEBUG_INPUT_METHOD) {
+                    Slog.v(TAG, "List with no IM target:");
+                    logWindowList(windows, "  ");
+                }
+                if (DN > 0) moveInputMethodDialogsLocked(-1);
+            } else {
+                moveInputMethodDialogsLocked(-1);
+            }
+
+        }
+
+        if (needAssignLayers) {
+            assignLayersLocked(windows);
+        }
+
+        return true;
+    }
+
+    final boolean isWallpaperVisible(WindowState wallpaperTarget) {
+        if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+                + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
+                + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null)
+                        ? wallpaperTarget.mAppToken.mAppAnimator.animation : null)
+                + " upper=" + mUpperWallpaperTarget
+                + " lower=" + mLowerWallpaperTarget);
+        return (wallpaperTarget != null
+                        && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null
+                                && wallpaperTarget.mAppToken.mAppAnimator.animation != null)))
+                || mUpperWallpaperTarget != null
+                || mLowerWallpaperTarget != null;
+    }
+
+    static final int ADJUST_WALLPAPER_LAYERS_CHANGED = 1<<1;
+    static final int ADJUST_WALLPAPER_VISIBILITY_CHANGED = 1<<2;
+
+    int adjustWallpaperWindowsLocked() {
+        mInnerFields.mWallpaperMayChange = false;
+        boolean targetChanged = false;
+
+        // TODO(multidisplay): Wallpapers on main screen only.
+        final DisplayInfo displayInfo = getDefaultDisplayContentLocked().getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        // First find top-most window that has asked to be on top of the
+        // wallpaper; all wallpapers go behind it.
+        final WindowList windows = getDefaultWindowListLocked();
+        int N = windows.size();
+        WindowState w = null;
+        WindowState foundW = null;
+        int foundI = 0;
+        WindowState topCurW = null;
+        int topCurI = 0;
+        int windowDetachedI = -1;
+        int i = N;
+        while (i > 0) {
+            i--;
+            w = windows.get(i);
+            if ((w.mAttrs.type == TYPE_WALLPAPER)) {
+                if (topCurW == null) {
+                    topCurW = w;
+                    topCurI = i;
+                }
+                continue;
+            }
+            topCurW = null;
+            if (w != mAnimator.mWindowDetachedWallpaper && w.mAppToken != null) {
+                // If this window's app token is hidden and not animating,
+                // it is of no interest to us.
+                if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) {
+                    if (DEBUG_WALLPAPER) Slog.v(TAG,
+                            "Skipping hidden and not animating token: " + w);
+                    continue;
+                }
+            }
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Win #" + i + " " + w + ": isOnScreen="
+                    + w.isOnScreen() + " mDrawState=" + w.mWinAnimator.mDrawState);
+            if ((w.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0 && w.isOnScreen()
+                    && (mWallpaperTarget == w || w.isDrawFinishedLw())) {
+                if (DEBUG_WALLPAPER) Slog.v(TAG,
+                        "Found wallpaper target: #" + i + "=" + w);
+                foundW = w;
+                foundI = i;
+                if (w == mWallpaperTarget && w.mWinAnimator.isAnimating()) {
+                    // The current wallpaper target is animating, so we'll
+                    // look behind it for another possible target and figure
+                    // out what is going on below.
+                    if (DEBUG_WALLPAPER) Slog.v(TAG, "Win " + w
+                            + ": token animating, looking behind.");
+                    continue;
+                }
+                break;
+            } else if (w == mAnimator.mWindowDetachedWallpaper) {
+                windowDetachedI = i;
+            }
+        }
+
+        if (foundW == null && windowDetachedI >= 0) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                    "Found animating detached wallpaper activity: #" + i + "=" + w);
+            foundW = w;
+            foundI = windowDetachedI;
+        }
+
+        if (mWallpaperTarget != foundW
+                && (mLowerWallpaperTarget == null || mLowerWallpaperTarget != foundW)) {
+            if (DEBUG_WALLPAPER_LIGHT) {
+                Slog.v(TAG, "New wallpaper target: " + foundW
+                        + " oldTarget: " + mWallpaperTarget);
+            }
+
+            mLowerWallpaperTarget = null;
+            mUpperWallpaperTarget = null;
+
+            WindowState oldW = mWallpaperTarget;
+            mWallpaperTarget = foundW;
+            targetChanged = true;
+
+            // Now what is happening...  if the current and new targets are
+            // animating, then we are in our super special mode!
+            if (foundW != null && oldW != null) {
+                boolean oldAnim = oldW.isAnimatingLw();
+                boolean foundAnim = foundW.isAnimatingLw();
+                if (DEBUG_WALLPAPER_LIGHT) {
+                    Slog.v(TAG, "New animation: " + foundAnim
+                            + " old animation: " + oldAnim);
+                }
+                if (foundAnim && oldAnim) {
+                    int oldI = windows.indexOf(oldW);
+                    if (DEBUG_WALLPAPER_LIGHT) {
+                        Slog.v(TAG, "New i: " + foundI + " old i: " + oldI);
+                    }
+                    if (oldI >= 0) {
+                        if (DEBUG_WALLPAPER_LIGHT) {
+                            Slog.v(TAG, "Animating wallpapers: old#" + oldI
+                                    + "=" + oldW + "; new#" + foundI
+                                    + "=" + foundW);
+                        }
+
+                        // Set the new target correctly.
+                        if (foundW.mAppToken != null && foundW.mAppToken.hiddenRequested) {
+                            if (DEBUG_WALLPAPER_LIGHT) {
+                                Slog.v(TAG, "Old wallpaper still the target.");
+                            }
+                            mWallpaperTarget = oldW;
+                            foundW = oldW;
+                            foundI = oldI;
+                        }
+                        // Now set the upper and lower wallpaper targets
+                        // correctly, and make sure that we are positioning
+                        // the wallpaper below the lower.
+                        else if (foundI > oldI) {
+                            // The new target is on top of the old one.
+                            if (DEBUG_WALLPAPER_LIGHT) {
+                                Slog.v(TAG, "Found target above old target.");
+                            }
+                            mUpperWallpaperTarget = foundW;
+                            mLowerWallpaperTarget = oldW;
+                            foundW = oldW;
+                            foundI = oldI;
+                        } else {
+                            // The new target is below the old one.
+                            if (DEBUG_WALLPAPER_LIGHT) {
+                                Slog.v(TAG, "Found target below old target.");
+                            }
+                            mUpperWallpaperTarget = oldW;
+                            mLowerWallpaperTarget = foundW;
+                        }
+                    }
+                }
+            }
+
+        } else if (mLowerWallpaperTarget != null) {
+            // Is it time to stop animating?
+            if (!mLowerWallpaperTarget.isAnimatingLw() || !mUpperWallpaperTarget.isAnimatingLw()) {
+                if (DEBUG_WALLPAPER_LIGHT) {
+                    Slog.v(TAG, "No longer animating wallpaper targets!");
+                }
+                mLowerWallpaperTarget = null;
+                mUpperWallpaperTarget = null;
+                mWallpaperTarget = foundW;
+                targetChanged = true;
+            }
+        }
+
+        boolean visible = foundW != null;
+        if (visible) {
+            // The window is visible to the compositor...  but is it visible
+            // to the user?  That is what the wallpaper cares about.
+            visible = isWallpaperVisible(foundW);
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper visibility: " + visible);
+
+            // If the wallpaper target is animating, we may need to copy
+            // its layer adjustment.  Only do this if we are not transfering
+            // between two wallpaper targets.
+            mWallpaperAnimLayerAdjustment =
+                    (mLowerWallpaperTarget == null && foundW.mAppToken != null)
+                    ? foundW.mAppToken.mAppAnimator.animLayerAdjustment : 0;
+
+            final int maxLayer = mPolicy.getMaxWallpaperLayer()
+                    * TYPE_LAYER_MULTIPLIER
+                    + TYPE_LAYER_OFFSET;
+
+            // Now w is the window we are supposed to be behind...  but we
+            // need to be sure to also be behind any of its attached windows,
+            // AND any starting window associated with it, AND below the
+            // maximum layer the policy allows for wallpapers.
+            while (foundI > 0) {
+                WindowState wb = windows.get(foundI-1);
+                if (wb.mBaseLayer < maxLayer &&
+                        wb.mAttachedWindow != foundW &&
+                        (foundW.mAttachedWindow == null ||
+                                wb.mAttachedWindow != foundW.mAttachedWindow) &&
+                        (wb.mAttrs.type != TYPE_APPLICATION_STARTING ||
+                                foundW.mToken == null || wb.mToken != foundW.mToken)) {
+                    // This window is not related to the previous one in any
+                    // interesting way, so stop here.
+                    break;
+                }
+                foundW = wb;
+                foundI--;
+            }
+        } else {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "No wallpaper target");
+        }
+
+        if (foundW == null && topCurW != null) {
+            // There is no wallpaper target, so it goes at the bottom.
+            // We will assume it is the same place as last time, if known.
+            foundW = topCurW;
+            foundI = topCurI+1;
+        } else {
+            // Okay i is the position immediately above the wallpaper.  Look at
+            // what is below it for later.
+            foundW = foundI > 0 ? windows.get(foundI-1) : null;
+        }
+
+        if (visible) {
+            if (mWallpaperTarget.mWallpaperX >= 0) {
+                mLastWallpaperX = mWallpaperTarget.mWallpaperX;
+                mLastWallpaperXStep = mWallpaperTarget.mWallpaperXStep;
+            }
+            if (mWallpaperTarget.mWallpaperY >= 0) {
+                mLastWallpaperY = mWallpaperTarget.mWallpaperY;
+                mLastWallpaperYStep = mWallpaperTarget.mWallpaperYStep;
+            }
+        }
+
+        // Start stepping backwards from here, ensuring that our wallpaper windows
+        // are correctly placed.
+        int changed = 0;
+        int curTokenIndex = mWallpaperTokens.size();
+        while (curTokenIndex > 0) {
+            curTokenIndex--;
+            WindowToken token = mWallpaperTokens.get(curTokenIndex);
+            if (token.hidden == visible) {
+                if (DEBUG_WALLPAPER_LIGHT) Slog.d(TAG,
+                        "Wallpaper token " + token + " hidden=" + !visible);
+                changed |= ADJUST_WALLPAPER_VISIBILITY_CHANGED;
+                token.hidden = !visible;
+                // Need to do a layout to ensure the wallpaper now has the
+                // correct size.
+                getDefaultDisplayContentLocked().layoutNeeded = true;
+            }
+
+            int curWallpaperIndex = token.windows.size();
+            while (curWallpaperIndex > 0) {
+                curWallpaperIndex--;
+                WindowState wallpaper = token.windows.get(curWallpaperIndex);
+
+                if (visible) {
+                    updateWallpaperOffsetLocked(wallpaper, dw, dh, false);
+                }
+
+                // First, make sure the client has the current visibility
+                // state.
+                dispatchWallpaperVisibility(wallpaper, visible);
+
+                wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + mWallpaperAnimLayerAdjustment;
+                if (DEBUG_LAYERS || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "adjustWallpaper win "
+                        + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer);
+
+                // First, if this window is at the current index, then all
+                // is well.
+                if (wallpaper == foundW) {
+                    foundI--;
+                    foundW = foundI > 0
+                            ? windows.get(foundI-1) : null;
+                    continue;
+                }
+
+                // The window didn't match...  the current wallpaper window,
+                // wherever it is, is in the wrong place, so make sure it is
+                // not in the list.
+                int oldIndex = windows.indexOf(wallpaper);
+                if (oldIndex >= 0) {
+                    if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Wallpaper removing at "
+                            + oldIndex + ": " + wallpaper);
+                    windows.remove(oldIndex);
+                    mWindowsChanged = true;
+                    if (oldIndex < foundI) {
+                        foundI--;
+                    }
+                }
+
+                // Now stick it in. For apps over wallpaper keep the wallpaper at the bottommost
+                // layer. For keyguard over wallpaper put the wallpaper under the keyguard.
+                int insertionIndex = 0;
+                if (visible && foundW != null) {
+                    final int type = foundW.mAttrs.type;
+                    if (type == TYPE_KEYGUARD || type == TYPE_KEYGUARD_SCRIM) {
+                        insertionIndex = windows.indexOf(foundW);
+                    }
+                }
+                if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
+                    Slog.v(TAG, "Moving wallpaper " + wallpaper
+                            + " from " + oldIndex + " to " + insertionIndex);
+                }
+
+                windows.add(insertionIndex, wallpaper);
+                mWindowsChanged = true;
+                changed |= ADJUST_WALLPAPER_LAYERS_CHANGED;
+            }
+        }
+
+        /*
+        final TaskStack targetStack =
+                mWallpaperTarget == null ? null : mWallpaperTarget.getStack();
+        if ((changed & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0 &&
+                targetStack != null && !targetStack.isHomeStack()) {
+            // If the wallpaper target is not on the home stack then make sure that all windows
+            // from other non-home stacks are above the wallpaper.
+            for (i = foundI - 1; i >= 0; --i) {
+                WindowState win = windows.get(i);
+                if (!win.isVisibleLw()) {
+                    continue;
+                }
+                final TaskStack winStack = win.getStack();
+                if (winStack != null && !winStack.isHomeStack() && winStack != targetStack) {
+                    windows.remove(i);
+                    windows.add(foundI + 1, win);
+                }
+            }
+        }
+        */
+
+        if (targetChanged && DEBUG_WALLPAPER_LIGHT) {
+            Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget
+                    + " lower=" + mLowerWallpaperTarget + " upper="
+                    + mUpperWallpaperTarget);
+        }
+
+        return changed;
+    }
+
+    void setWallpaperAnimLayerAdjustmentLocked(int adj) {
+        if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG,
+                "Setting wallpaper layer adj to " + adj);
+        mWallpaperAnimLayerAdjustment = adj;
+        int curTokenIndex = mWallpaperTokens.size();
+        while (curTokenIndex > 0) {
+            curTokenIndex--;
+            WindowToken token = mWallpaperTokens.get(curTokenIndex);
+            int curWallpaperIndex = token.windows.size();
+            while (curWallpaperIndex > 0) {
+                curWallpaperIndex--;
+                WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                wallpaper.mWinAnimator.mAnimLayer = wallpaper.mLayer + adj;
+                if (DEBUG_LAYERS || DEBUG_WALLPAPER) Slog.v(TAG, "setWallpaper win "
+                        + wallpaper + " anim layer: " + wallpaper.mWinAnimator.mAnimLayer);
+            }
+        }
+    }
+
+    boolean updateWallpaperOffsetLocked(WindowState wallpaperWin, int dw, int dh,
+            boolean sync) {
+        boolean changed = false;
+        boolean rawChanged = false;
+        float wpx = mLastWallpaperX >= 0 ? mLastWallpaperX : 0.5f;
+        float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
+        int availw = wallpaperWin.mFrame.right-wallpaperWin.mFrame.left-dw;
+        int offset = availw > 0 ? -(int)(availw*wpx+.5f) : 0;
+        changed = wallpaperWin.mXOffset != offset;
+        if (changed) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper "
+                    + wallpaperWin + " x: " + offset);
+            wallpaperWin.mXOffset = offset;
+        }
+        if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
+            wallpaperWin.mWallpaperX = wpx;
+            wallpaperWin.mWallpaperXStep = wpxs;
+            rawChanged = true;
+        }
+
+        float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
+        float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
+        int availh = wallpaperWin.mFrame.bottom-wallpaperWin.mFrame.top-dh;
+        offset = availh > 0 ? -(int)(availh*wpy+.5f) : 0;
+        if (wallpaperWin.mYOffset != offset) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper "
+                    + wallpaperWin + " y: " + offset);
+            changed = true;
+            wallpaperWin.mYOffset = offset;
+        }
+        if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
+            wallpaperWin.mWallpaperY = wpy;
+            wallpaperWin.mWallpaperYStep = wpys;
+            rawChanged = true;
+        }
+
+        if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
+                    WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
+            try {
+                if (DEBUG_WALLPAPER) Slog.v(TAG, "Report new wp offset "
+                        + wallpaperWin + " x=" + wallpaperWin.mWallpaperX
+                        + " y=" + wallpaperWin.mWallpaperY);
+                if (sync) {
+                    mWaitingOnWallpaper = wallpaperWin;
+                }
+                wallpaperWin.mClient.dispatchWallpaperOffsets(
+                        wallpaperWin.mWallpaperX, wallpaperWin.mWallpaperY,
+                        wallpaperWin.mWallpaperXStep, wallpaperWin.mWallpaperYStep, sync);
+                if (sync) {
+                    if (mWaitingOnWallpaper != null) {
+                        long start = SystemClock.uptimeMillis();
+                        if ((mLastWallpaperTimeoutTime+WALLPAPER_TIMEOUT_RECOVERY)
+                                < start) {
+                            try {
+                                if (DEBUG_WALLPAPER) Slog.v(TAG,
+                                        "Waiting for offset complete...");
+                                mWindowMap.wait(WALLPAPER_TIMEOUT);
+                            } catch (InterruptedException e) {
+                            }
+                            if (DEBUG_WALLPAPER) Slog.v(TAG, "Offset complete!");
+                            if ((start+WALLPAPER_TIMEOUT)
+                                    < SystemClock.uptimeMillis()) {
+                                Slog.i(TAG, "Timeout waiting for wallpaper to offset: "
+                                        + wallpaperWin);
+                                mLastWallpaperTimeoutTime = start;
+                            }
+                        }
+                        mWaitingOnWallpaper = null;
+                    }
+                }
+            } catch (RemoteException e) {
+            }
+        }
+
+        return changed;
+    }
+
+    void wallpaperOffsetsComplete(IBinder window) {
+        synchronized (mWindowMap) {
+            if (mWaitingOnWallpaper != null &&
+                    mWaitingOnWallpaper.mClient.asBinder() == window) {
+                mWaitingOnWallpaper = null;
+                mWindowMap.notifyAll();
+            }
+        }
+    }
+
+    void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
+        final DisplayContent displayContent = changingTarget.mDisplayContent;
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        WindowState target = mWallpaperTarget;
+        if (target != null) {
+            if (target.mWallpaperX >= 0) {
+                mLastWallpaperX = target.mWallpaperX;
+            } else if (changingTarget.mWallpaperX >= 0) {
+                mLastWallpaperX = changingTarget.mWallpaperX;
+            }
+            if (target.mWallpaperY >= 0) {
+                mLastWallpaperY = target.mWallpaperY;
+            } else if (changingTarget.mWallpaperY >= 0) {
+                mLastWallpaperY = changingTarget.mWallpaperY;
+            }
+        }
+
+        int curTokenIndex = mWallpaperTokens.size();
+        while (curTokenIndex > 0) {
+            curTokenIndex--;
+            WindowToken token = mWallpaperTokens.get(curTokenIndex);
+            int curWallpaperIndex = token.windows.size();
+            while (curWallpaperIndex > 0) {
+                curWallpaperIndex--;
+                WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                if (updateWallpaperOffsetLocked(wallpaper, dw, dh, sync)) {
+                    WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+                    winAnimator.computeShownFrameLocked();
+                    // No need to lay out the windows - we can just set the wallpaper position
+                    // directly.
+                    winAnimator.setWallpaperOffset(wallpaper.mShownFrame);
+                    // We only want to be synchronous with one wallpaper.
+                    sync = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Check wallpaper for visiblity change and notify window if so.
+     * @param wallpaper The wallpaper to test and notify.
+     * @param visible Current visibility.
+     */
+    void dispatchWallpaperVisibility(final WindowState wallpaper, final boolean visible) {
+        if (wallpaper.mWallpaperVisible != visible) {
+            wallpaper.mWallpaperVisible = visible;
+            try {
+                if (DEBUG_VISIBILITY || DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                        "Updating vis of wallpaper " + wallpaper
+                        + ": " + visible + " from:\n" + Debug.getCallers(4, "  "));
+                wallpaper.mClient.dispatchAppVisibility(visible);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    void updateWallpaperVisibilityLocked() {
+        final boolean visible = isWallpaperVisible(mWallpaperTarget);
+        final DisplayContent displayContent = mWallpaperTarget.mDisplayContent;
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        int curTokenIndex = mWallpaperTokens.size();
+        while (curTokenIndex > 0) {
+            curTokenIndex--;
+            WindowToken token = mWallpaperTokens.get(curTokenIndex);
+            if (token.hidden == visible) {
+                token.hidden = !visible;
+                // Need to do a layout to ensure the wallpaper now has the
+                // correct size.
+                getDefaultDisplayContentLocked().layoutNeeded = true;
+            }
+
+            int curWallpaperIndex = token.windows.size();
+            while (curWallpaperIndex > 0) {
+                curWallpaperIndex--;
+                WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                if (visible) {
+                    updateWallpaperOffsetLocked(wallpaper, dw, dh, false);
+                }
+
+                dispatchWallpaperVisibility(wallpaper, visible);
+            }
+        }
+    }
+
+    public int addWindow(Session session, IWindow client, int seq,
+            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
+            Rect outContentInsets, InputChannel outInputChannel) {
+        int[] appOp = new int[1];
+        int res = mPolicy.checkAddPermission(attrs, appOp);
+        if (res != WindowManagerGlobal.ADD_OKAY) {
+            return res;
+        }
+
+        boolean reportNewConfig = false;
+        WindowState attachedWindow = null;
+        WindowState win = null;
+        long origId;
+        final int type = attrs.type;
+
+        synchronized(mWindowMap) {
+            if (!mDisplayReady) {
+                throw new IllegalStateException("Display has not been initialialized");
+            }
+
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent == null) {
+                Slog.w(TAG, "Attempted to add window to a display that does not exist: "
+                        + displayId + ".  Aborting.");
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+            }
+            if (!displayContent.hasAccess(session.mUid)) {
+                Slog.w(TAG, "Attempted to add window to a display for which the application "
+                        + "does not have access: " + displayId + ".  Aborting.");
+                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
+            }
+
+            if (mWindowMap.containsKey(client.asBinder())) {
+                Slog.w(TAG, "Window " + client + " is already added");
+                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
+            }
+
+            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
+                attachedWindow = windowForClientLocked(null, attrs.token, false);
+                if (attachedWindow == null) {
+                    Slog.w(TAG, "Attempted to add window with token that is not a window: "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
+                }
+                if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
+                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
+                    Slog.w(TAG, "Attempted to add window with token that is a sub-window: "
+                            + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
+                }
+            }
+
+            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
+                Slog.w(TAG, "Attempted to add private presentation window to a non-private display.  Aborting.");
+                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
+            }
+
+            boolean addToken = false;
+            WindowToken token = mTokenMap.get(attrs.token);
+            if (token == null) {
+                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+                    Slog.w(TAG, "Attempted to add application window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (type == TYPE_INPUT_METHOD) {
+                    Slog.w(TAG, "Attempted to add input method window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (type == TYPE_WALLPAPER) {
+                    Slog.w(TAG, "Attempted to add wallpaper window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                if (type == TYPE_DREAM) {
+                    Slog.w(TAG, "Attempted to add Dream window with unknown token "
+                          + attrs.token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+                token = new WindowToken(this, attrs.token, -1, false);
+                addToken = true;
+            } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+                AppWindowToken atoken = token.appWindowToken;
+                if (atoken == null) {
+                    Slog.w(TAG, "Attempted to add window with non-application token "
+                          + token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
+                } else if (atoken.removed) {
+                    Slog.w(TAG, "Attempted to add window with exiting application token "
+                          + token + ".  Aborting.");
+                    return WindowManagerGlobal.ADD_APP_EXITING;
+                }
+                if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
+                    // No need for this guy!
+                    if (localLOGV) Slog.v(
+                            TAG, "**** NO NEED TO START: " + attrs.getTitle());
+                    return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
+                }
+            } else if (type == TYPE_INPUT_METHOD) {
+                if (token.windowType != TYPE_INPUT_METHOD) {
+                    Slog.w(TAG, "Attempted to add input method window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (type == TYPE_WALLPAPER) {
+                if (token.windowType != TYPE_WALLPAPER) {
+                    Slog.w(TAG, "Attempted to add wallpaper window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            } else if (type == TYPE_DREAM) {
+                if (token.windowType != TYPE_DREAM) {
+                    Slog.w(TAG, "Attempted to add Dream window with bad token "
+                            + attrs.token + ".  Aborting.");
+                      return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
+                }
+            }
+
+            win = new WindowState(this, session, client, token,
+                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
+            if (win.mDeathRecipient == null) {
+                // Client has apparently died, so there is no reason to
+                // continue.
+                Slog.w(TAG, "Adding window client " + client.asBinder()
+                        + " that is dead, aborting.");
+                return WindowManagerGlobal.ADD_APP_EXITING;
+            }
+
+            mPolicy.adjustWindowParamsLw(win.mAttrs);
+            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
+
+            res = mPolicy.prepareAddWindowLw(win, attrs);
+            if (res != WindowManagerGlobal.ADD_OKAY) {
+                return res;
+            }
+
+            if (outInputChannel != null && (attrs.inputFeatures
+                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
+                String name = win.makeInputChannelName();
+                InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
+                win.setInputChannel(inputChannels[0]);
+                inputChannels[1].transferTo(outInputChannel);
+
+                mInputManager.registerInputChannel(win.mInputChannel, win.mInputWindowHandle);
+            }
+
+            // From now on, no exceptions or errors allowed!
+
+            res = WindowManagerGlobal.ADD_OKAY;
+
+            origId = Binder.clearCallingIdentity();
+
+            if (addToken) {
+                mTokenMap.put(attrs.token, token);
+            }
+            win.attach();
+            mWindowMap.put(client.asBinder(), win);
+            if (win.mAppOp != AppOpsManager.OP_NONE) {
+                if (mAppOps.startOpNoThrow(win.mAppOp, win.getOwningUid(), win.getOwningPackage())
+                        != AppOpsManager.MODE_ALLOWED) {
+                    win.setAppOpVisibilityLw(false);
+                }
+            }
+
+            if (type == TYPE_APPLICATION_STARTING && token.appWindowToken != null) {
+                token.appWindowToken.startingWindow = win;
+                if (DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken
+                        + " startingWindow=" + win);
+                Message m = mH.obtainMessage(H.REMOVE_STARTING_TIMEOUT, token.appWindowToken);
+                mH.sendMessageDelayed(m, STARTING_WINDOW_TIMEOUT_DURATION);
+            }
+
+            boolean imMayMove = true;
+
+            if (type == TYPE_INPUT_METHOD) {
+                win.mGivenInsetsPending = true;
+                mInputMethodWindow = win;
+                addInputMethodWindowToListLocked(win);
+                imMayMove = false;
+            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
+                mInputMethodDialogs.add(win);
+                addWindowToListInOrderLocked(win, true);
+                moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));
+                imMayMove = false;
+            } else {
+                addWindowToListInOrderLocked(win, true);
+                if (type == TYPE_WALLPAPER) {
+                    mLastWallpaperTimeoutTime = 0;
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                } else if (mWallpaperTarget != null
+                        && mWallpaperTarget.mLayer >= win.mBaseLayer) {
+                    // If there is currently a wallpaper being shown, and
+                    // the base layer of the new window is below the current
+                    // layer of the target window, then adjust the wallpaper.
+                    // This is to avoid a new window being placed between the
+                    // wallpaper and its target.
+                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+                }
+            }
+
+            win.mWinAnimator.mEnterAnimationPending = true;
+
+            if (displayContent.isDefaultDisplay) {
+                mPolicy.getContentInsetHintLw(attrs, outContentInsets);
+            } else {
+                outContentInsets.setEmpty();
+            }
+
+            if (mInTouchMode) {
+                res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
+            }
+            if (win.mAppToken == null || !win.mAppToken.clientHidden) {
+                res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
+            }
+
+            mInputMonitor.setUpdateInputWindowsNeededLw();
+
+            boolean focusChanged = false;
+            if (win.canReceiveKeys()) {
+                focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
+                        false /*updateInputWindows*/);
+                if (focusChanged) {
+                    imMayMove = false;
+                }
+            }
+
+            if (imMayMove) {
+                moveInputMethodWindowsIfNeededLocked(false);
+            }
+
+            assignLayersLocked(displayContent.getWindowList());
+            // Don't do layout here, the window must call
+            // relayout to be displayed, so we'll do it there.
+
+            if (focusChanged) {
+                finishUpdateFocusedWindowAfterAssignLayersLocked(false /*updateInputWindows*/);
+            }
+            mInputMonitor.updateInputWindowsLw(false /*force*/);
+
+            if (localLOGV) Slog.v(
+                TAG, "New client " + client.asBinder()
+                + ": window=" + win);
+
+            if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) {
+                reportNewConfig = true;
+            }
+        }
+
+        if (reportNewConfig) {
+            sendNewConfiguration();
+        }
+
+        Binder.restoreCallingIdentity(origId);
+
+        return res;
+    }
+
+    public void removeWindow(Session session, IWindow client) {
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                return;
+            }
+            removeWindowLocked(session, win);
+        }
+    }
+
+    public void removeWindowLocked(Session session, WindowState win) {
+        if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+            if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win);
+            removeStartingWindowTimeout(win.mAppToken);
+        }
+
+        if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && win==mCurrentFocus) Slog.v(
+                TAG, "Remove " + win + " client="
+                + Integer.toHexString(System.identityHashCode(win.mClient.asBinder()))
+                + ", surface=" + win.mWinAnimator.mSurfaceControl + " Callers="
+                + Debug.getCallers(4));
+
+        final long origId = Binder.clearCallingIdentity();
+
+        win.disposeInputChannel();
+
+        if (DEBUG_APP_TRANSITIONS) Slog.v(
+                TAG, "Remove " + win + ": mSurface=" + win.mWinAnimator.mSurfaceControl
+                + " mExiting=" + win.mExiting
+                + " isAnimating=" + win.mWinAnimator.isAnimating()
+                + " app-animation="
+                + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+                + " inPendingTransaction="
+                + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
+                + " mDisplayFrozen=" + mDisplayFrozen);
+        // Visibility of the removed window. Will be used later to update orientation later on.
+        boolean wasVisible = false;
+        // First, see if we need to run an animation.  If we do, we have
+        // to hold off on removing the window until the animation is done.
+        // If the display is frozen, just remove immediately, since the
+        // animation wouldn't be seen.
+        if (win.mHasSurface && okToDisplay()) {
+            // If we are not currently running the exit animation, we
+            // need to see about starting one.
+            wasVisible = win.isWinVisibleLw();
+            if (wasVisible) {
+
+                int transit = WindowManagerPolicy.TRANSIT_EXIT;
+                if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+                    transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+                }
+                // Try starting an animation.
+                if (win.mWinAnimator.applyAnimationLocked(transit, false)) {
+                    win.mExiting = true;
+                }
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (mDisplayMagnifier != null
+                        && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    mDisplayMagnifier.onWindowTransitionLocked(win, transit);
+                }
+            }
+            if (win.mExiting || win.mWinAnimator.isAnimating()) {
+                // The exit animation is running... wait for it!
+                //Slog.i(TAG, "*** Running exit animation...");
+                win.mExiting = true;
+                win.mRemoveOnExit = true;
+                win.mDisplayContent.layoutNeeded = true;
+                updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                        false /*updateInputWindows*/);
+                performLayoutAndPlaceSurfacesLocked();
+                if (win.mAppToken != null) {
+                    win.mAppToken.updateReportedVisibilityLocked();
+                }
+                //dump();
+                Binder.restoreCallingIdentity(origId);
+                return;
+            }
+        }
+
+        removeWindowInnerLocked(session, win);
+        // Removing a visible window will effect the computed orientation
+        // So just update orientation if needed.
+        if (wasVisible && updateOrientationFromAppTokensLocked(false)) {
+            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+        }
+        updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    private void removeWindowInnerLocked(Session session, WindowState win) {
+        if (win.mRemoved) {
+            // Nothing to do.
+            return;
+        }
+
+        for (int i=win.mChildWindows.size()-1; i>=0; i--) {
+            WindowState cwin = win.mChildWindows.get(i);
+            Slog.w(TAG, "Force-removing child win " + cwin + " from container "
+                    + win);
+            removeWindowInnerLocked(cwin.mSession, cwin);
+        }
+
+        win.mRemoved = true;
+
+        if (mInputMethodTarget == win) {
+            moveInputMethodWindowsIfNeededLocked(false);
+        }
+
+        if (false) {
+            RuntimeException e = new RuntimeException("here");
+            e.fillInStackTrace();
+            Slog.w(TAG, "Removing window " + win, e);
+        }
+
+        mPolicy.removeWindowLw(win);
+        win.removeLocked();
+
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "removeWindowInnerLocked: " + win);
+        mWindowMap.remove(win.mClient.asBinder());
+        if (win.mAppOp != AppOpsManager.OP_NONE) {
+            mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
+        }
+
+        final WindowList windows = win.getWindowList();
+        windows.remove(win);
+        mPendingRemove.remove(win);
+        mResizingWindows.remove(win);
+        mWindowsChanged = true;
+        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
+
+        if (mInputMethodWindow == win) {
+            mInputMethodWindow = null;
+        } else if (win.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
+            mInputMethodDialogs.remove(win);
+        }
+
+        final WindowToken token = win.mToken;
+        final AppWindowToken atoken = win.mAppToken;
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + win + " from " + token);
+        token.windows.remove(win);
+        if (atoken != null) {
+            atoken.allAppWindows.remove(win);
+        }
+        if (localLOGV) Slog.v(
+                TAG, "**** Removing window " + win + ": count="
+                + token.windows.size());
+        if (token.windows.size() == 0) {
+            if (!token.explicit) {
+                mTokenMap.remove(token.token);
+            } else if (atoken != null) {
+                atoken.firstWindowDrawn = false;
+            }
+        }
+
+        if (atoken != null) {
+            if (atoken.startingWindow == win) {
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling startingWindow " + win);
+                removeStartingWindowTimeout(atoken);
+                atoken.startingWindow = null;
+            } else if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) {
+                // If this is the last window and we had requested a starting
+                // transition window, well there is no point now.
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling last startingWindow");
+                atoken.startingData = null;
+            } else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) {
+                // If this is the last window except for a starting transition
+                // window, we need to get rid of the starting transition.
+                scheduleRemoveStartingWindow(atoken);
+            }
+        }
+
+        if (win.mAttrs.type == TYPE_WALLPAPER) {
+            mLastWallpaperTimeoutTime = 0;
+            getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+        } else if ((win.mAttrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
+            getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+        }
+
+        if (!mInLayout) {
+            assignLayersLocked(windows);
+            win.mDisplayContent.layoutNeeded = true;
+            performLayoutAndPlaceSurfacesLocked();
+            if (win.mAppToken != null) {
+                win.mAppToken.updateReportedVisibilityLocked();
+            }
+        }
+
+        mInputMonitor.updateInputWindowsLw(true /*force*/);
+    }
+
+    public void updateAppOpsState() {
+        synchronized(mWindowMap) {
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                final int numWindows = windows.size();
+                for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                    final WindowState win = windows.get(winNdx);
+                    if (win.mAppOp != AppOpsManager.OP_NONE) {
+                        final int mode = mAppOps.checkOpNoThrow(win.mAppOp, win.getOwningUid(),
+                                win.getOwningPackage());
+                        win.setAppOpVisibilityLw(mode == AppOpsManager.MODE_ALLOWED);
+                    }
+                }
+            }
+        }
+    }
+
+    static void logSurface(WindowState w, String msg, RuntimeException where) {
+        String str = "  SURFACE " + msg + ": " + w;
+        if (where != null) {
+            Slog.i(TAG, str, where);
+        } else {
+            Slog.i(TAG, str);
+        }
+    }
+
+    static void logSurface(SurfaceControl s, String title, String msg, RuntimeException where) {
+        String str = "  SURFACE " + s + ": " + msg + " / " + title;
+        if (where != null) {
+            Slog.i(TAG, str, where);
+        } else {
+            Slog.i(TAG, str);
+        }
+    }
+
+    void setTransparentRegionWindow(Session session, IWindow client, Region region) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState w = windowForClientLocked(session, client, false);
+                if ((w != null) && w.mHasSurface) {
+                    w.mWinAnimator.setTransparentRegionHintLocked(region);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void setInsetsWindow(Session session, IWindow client,
+            int touchableInsets, Rect contentInsets,
+            Rect visibleInsets, Region touchableRegion) {
+        long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState w = windowForClientLocked(session, client, false);
+                if (w != null) {
+                    w.mGivenInsetsPending = false;
+                    w.mGivenContentInsets.set(contentInsets);
+                    w.mGivenVisibleInsets.set(visibleInsets);
+                    w.mGivenTouchableRegion.set(touchableRegion);
+                    w.mTouchableInsets = touchableInsets;
+                    if (w.mGlobalScale != 1) {
+                        w.mGivenContentInsets.scale(w.mGlobalScale);
+                        w.mGivenVisibleInsets.scale(w.mGlobalScale);
+                        w.mGivenTouchableRegion.scale(w.mGlobalScale);
+                    }
+                    w.mDisplayContent.layoutNeeded = true;
+                    performLayoutAndPlaceSurfacesLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public void getWindowDisplayFrame(Session session, IWindow client,
+            Rect outDisplayFrame) {
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                outDisplayFrame.setEmpty();
+                return;
+            }
+            outDisplayFrame.set(win.mDisplayFrame);
+        }
+    }
+
+    public void setWindowWallpaperPositionLocked(WindowState window, float x, float y,
+            float xStep, float yStep) {
+        if (window.mWallpaperX != x || window.mWallpaperY != y)  {
+            window.mWallpaperX = x;
+            window.mWallpaperY = y;
+            window.mWallpaperXStep = xStep;
+            window.mWallpaperYStep = yStep;
+            updateWallpaperOffsetLocked(window, true);
+        }
+    }
+
+    void wallpaperCommandComplete(IBinder window, Bundle result) {
+        synchronized (mWindowMap) {
+            if (mWaitingOnWallpaper != null &&
+                    mWaitingOnWallpaper.mClient.asBinder() == window) {
+                mWaitingOnWallpaper = null;
+                mWindowMap.notifyAll();
+            }
+        }
+    }
+
+    public Bundle sendWindowWallpaperCommandLocked(WindowState window,
+            String action, int x, int y, int z, Bundle extras, boolean sync) {
+        if (window == mWallpaperTarget || window == mLowerWallpaperTarget
+                || window == mUpperWallpaperTarget) {
+            boolean doWait = sync;
+            int curTokenIndex = mWallpaperTokens.size();
+            while (curTokenIndex > 0) {
+                curTokenIndex--;
+                WindowToken token = mWallpaperTokens.get(curTokenIndex);
+                int curWallpaperIndex = token.windows.size();
+                while (curWallpaperIndex > 0) {
+                    curWallpaperIndex--;
+                    WindowState wallpaper = token.windows.get(curWallpaperIndex);
+                    try {
+                        wallpaper.mClient.dispatchWallpaperCommand(action,
+                                x, y, z, extras, sync);
+                        // We only want to be synchronous with one wallpaper.
+                        sync = false;
+                    } catch (RemoteException e) {
+                    }
+                }
+            }
+
+            if (doWait) {
+                // XXX Need to wait for result.
+            }
+        }
+
+        return null;
+    }
+
+    public void setUniverseTransformLocked(WindowState window, float alpha,
+            float offx, float offy, float dsdx, float dtdx, float dsdy, float dtdy) {
+        Transformation transform = window.mWinAnimator.mUniverseTransform;
+        transform.setAlpha(alpha);
+        Matrix matrix = transform.getMatrix();
+        matrix.getValues(mTmpFloats);
+        mTmpFloats[Matrix.MTRANS_X] = offx;
+        mTmpFloats[Matrix.MTRANS_Y] = offy;
+        mTmpFloats[Matrix.MSCALE_X] = dsdx;
+        mTmpFloats[Matrix.MSKEW_Y] = dtdx;
+        mTmpFloats[Matrix.MSKEW_X] = dsdy;
+        mTmpFloats[Matrix.MSCALE_Y] = dtdy;
+        matrix.setValues(mTmpFloats);
+        final DisplayInfo displayInfo = window.mDisplayContent.getDisplayInfo();
+        final RectF dispRect = new RectF(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        matrix.mapRect(dispRect);
+        window.mGivenTouchableRegion.set(0, 0,
+                displayInfo.logicalWidth, displayInfo.logicalHeight);
+        window.mGivenTouchableRegion.op((int)dispRect.left, (int)dispRect.top,
+                (int)dispRect.right, (int)dispRect.bottom, Region.Op.DIFFERENCE);
+        window.mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
+        window.mDisplayContent.layoutNeeded = true;
+        performLayoutAndPlaceSurfacesLocked();
+    }
+
+    public void onRectangleOnScreenRequested(IBinder token, Rect rectangle, boolean immediate) {
+        synchronized (mWindowMap) {
+            if (mDisplayMagnifier != null) {
+                WindowState window = mWindowMap.get(token);
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (window != null && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    mDisplayMagnifier.onRectangleOnScreenRequestedLocked(rectangle, immediate);
+                }
+            }
+        }
+    }
+
+    public IWindowId getWindowId(IBinder token) {
+        synchronized (mWindowMap) {
+            WindowState window = mWindowMap.get(token);
+            return window != null ? window.mWindowId : null;
+        }
+    }
+
+    public int relayoutWindow(Session session, IWindow client, int seq,
+            WindowManager.LayoutParams attrs, int requestedWidth,
+            int requestedHeight, int viewVisibility, int flags,
+            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
+            Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
+        boolean toBeDisplayed = false;
+        boolean inTouchMode;
+        boolean configChanged;
+        boolean surfaceChanged = false;
+        boolean animating;
+
+        // if they don't have this permission, mask out the status bar bits
+        int systemUiVisibility = 0;
+        if (attrs != null) {
+            systemUiVisibility = (attrs.systemUiVisibility|attrs.subtreeSystemUiVisibility);
+            if ((systemUiVisibility & StatusBarManager.DISABLE_MASK) != 0) {
+                if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                        != PackageManager.PERMISSION_GRANTED) {
+                    systemUiVisibility &= ~StatusBarManager.DISABLE_MASK;
+                }
+            }
+        }
+        long origId = Binder.clearCallingIdentity();
+
+        synchronized(mWindowMap) {
+            WindowState win = windowForClientLocked(session, client, false);
+            if (win == null) {
+                return 0;
+            }
+            WindowStateAnimator winAnimator = win.mWinAnimator;
+            if (win.mRequestedWidth != requestedWidth
+                    || win.mRequestedHeight != requestedHeight) {
+                win.mLayoutNeeded = true;
+                win.mRequestedWidth = requestedWidth;
+                win.mRequestedHeight = requestedHeight;
+            }
+            if (attrs != null && seq == win.mSeq) {
+                win.mSystemUiVisibility = systemUiVisibility;
+            }
+
+            if (attrs != null) {
+                mPolicy.adjustWindowParamsLw(attrs);
+            }
+
+            winAnimator.mSurfaceDestroyDeferred =
+                    (flags&WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY) != 0;
+
+            int attrChanges = 0;
+            int flagChanges = 0;
+            if (attrs != null) {
+                if (win.mAttrs.type != attrs.type) {
+                    throw new IllegalArgumentException(
+                            "Window type can not be changed after the window is added.");
+                }
+                flagChanges = win.mAttrs.flags ^= attrs.flags;
+                attrChanges = win.mAttrs.copyFrom(attrs);
+                if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
+                        | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
+                    win.mLayoutNeeded = true;
+                }
+            }
+
+            if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": viewVisibility=" + viewVisibility
+                    + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs);
+
+            win.mEnforceSizeCompat =
+                    (win.mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
+
+            if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) {
+                winAnimator.mAlpha = attrs.alpha;
+            }
+
+            final boolean scaledWindow =
+                ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0);
+
+            if (scaledWindow) {
+                // requested{Width|Height} Surface's physical size
+                // attrs.{width|height} Size on screen
+                win.mHScale = (attrs.width  != requestedWidth)  ?
+                        (attrs.width  / (float)requestedWidth) : 1.0f;
+                win.mVScale = (attrs.height != requestedHeight) ?
+                        (attrs.height / (float)requestedHeight) : 1.0f;
+            } else {
+                win.mHScale = win.mVScale = 1;
+            }
+
+            boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0;
+
+            final boolean isDefaultDisplay = win.isDefaultDisplay();
+            boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility
+                    || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)
+                    || (!win.mRelayoutCalled));
+
+            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
+                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
+            wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
+
+            win.mRelayoutCalled = true;
+            final int oldVisibility = win.mViewVisibility;
+            win.mViewVisibility = viewVisibility;
+            if (DEBUG_SCREEN_ON) {
+                RuntimeException stack = new RuntimeException();
+                stack.fillInStackTrace();
+                Slog.i(TAG, "Relayout " + win + ": oldVis=" + oldVisibility
+                        + " newVis=" + viewVisibility, stack);
+            }
+            if (viewVisibility == View.VISIBLE &&
+                    (win.mAppToken == null || !win.mAppToken.clientHidden)) {
+                toBeDisplayed = !win.isVisibleLw();
+                if (win.mExiting) {
+                    winAnimator.cancelExitAnimationForNextAnimationLocked();
+                    win.mExiting = false;
+                }
+                if (win.mDestroying) {
+                    win.mDestroying = false;
+                    mDestroySurface.remove(win);
+                }
+                if (oldVisibility == View.GONE) {
+                    winAnimator.mEnterAnimationPending = true;
+                }
+                if (toBeDisplayed) {
+                    if (win.isDrawnLw() && okToDisplay()) {
+                        winAnimator.applyEnterAnimationLocked();
+                    }
+                    if ((win.mAttrs.flags
+                            & WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) != 0) {
+                        if (DEBUG_VISIBILITY) Slog.v(TAG,
+                                "Relayout window turning screen on: " + win);
+                        win.mTurnOnScreen = true;
+                    }
+                    if (win.isConfigChanged()) {
+                        if (DEBUG_CONFIGURATION) Slog.i(TAG, "Window " + win
+                                + " visible with new config: " + mCurConfiguration);
+                        outConfig.setTo(mCurConfiguration);
+                    }
+                }
+                if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) {
+                    // To change the format, we need to re-build the surface.
+                    winAnimator.destroySurfaceLocked();
+                    toBeDisplayed = true;
+                    surfaceChanged = true;
+                }
+                try {
+                    if (!win.mHasSurface) {
+                        surfaceChanged = true;
+                    }
+                    SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
+                    if (surfaceControl != null) {
+                        outSurface.copyFrom(surfaceControl);
+                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
+                                "  OUT SURFACE " + outSurface + ": copied");
+                    } else {
+                        // For some reason there isn't a surface.  Clear the
+                        // caller's object so they see the same state.
+                        outSurface.release();
+                    }
+                } catch (Exception e) {
+                    mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+                    Slog.w(TAG, "Exception thrown when creating surface for client "
+                             + client + " (" + win.mAttrs.getTitle() + ")",
+                             e);
+                    Binder.restoreCallingIdentity(origId);
+                    return 0;
+                }
+                if (toBeDisplayed) {
+                    focusMayChange = isDefaultDisplay;
+                }
+                if (win.mAttrs.type == TYPE_INPUT_METHOD
+                        && mInputMethodWindow == null) {
+                    mInputMethodWindow = win;
+                    imMayMove = true;
+                }
+                if (win.mAttrs.type == TYPE_BASE_APPLICATION
+                        && win.mAppToken != null
+                        && win.mAppToken.startingWindow != null) {
+                    // Special handling of starting window over the base
+                    // window of the app: propagate lock screen flags to it,
+                    // to provide the correct semantics while starting.
+                    final int mask =
+                        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                        | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+                        | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+                    WindowManager.LayoutParams sa = win.mAppToken.startingWindow.mAttrs;
+                    sa.flags = (sa.flags&~mask) | (win.mAttrs.flags&mask);
+                }
+            } else {
+                winAnimator.mEnterAnimationPending = false;
+                if (winAnimator.mSurfaceControl != null) {
+                    if (DEBUG_VISIBILITY) Slog.i(TAG, "Relayout invis " + win
+                            + ": mExiting=" + win.mExiting);
+                    // If we are not currently running the exit animation, we
+                    // need to see about starting one.
+                    if (!win.mExiting) {
+                        surfaceChanged = true;
+                        // Try starting an animation; if there isn't one, we
+                        // can destroy the surface right away.
+                        int transit = WindowManagerPolicy.TRANSIT_EXIT;
+                        if (win.mAttrs.type == TYPE_APPLICATION_STARTING) {
+                            transit = WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+                        }
+                        if (win.isWinVisibleLw() &&
+                                winAnimator.applyAnimationLocked(transit, false)) {
+                            focusMayChange = isDefaultDisplay;
+                            win.mExiting = true;
+                        } else if (win.mWinAnimator.isAnimating()) {
+                            // Currently in a hide animation... turn this into
+                            // an exit.
+                            win.mExiting = true;
+                        } else if (win == mWallpaperTarget) {
+                            // If the wallpaper is currently behind this
+                            // window, we need to change both of them inside
+                            // of a transaction to avoid artifacts.
+                            win.mExiting = true;
+                            win.mWinAnimator.mAnimating = true;
+                        } else {
+                            if (mInputMethodWindow == win) {
+                                mInputMethodWindow = null;
+                            }
+                            winAnimator.destroySurfaceLocked();
+                        }
+                        //TODO (multidisplay): Magnification is supported only for the default
+                        if (mDisplayMagnifier != null
+                                && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                            mDisplayMagnifier.onWindowTransitionLocked(win, transit);
+                        }
+                    }
+                }
+
+                outSurface.release();
+                if (DEBUG_VISIBILITY) Slog.i(TAG, "Releasing surface in: " + win);
+            }
+
+            if (focusMayChange) {
+                //System.out.println("Focus may change: " + win.mAttrs.getTitle());
+                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                        false /*updateInputWindows*/)) {
+                    imMayMove = false;
+                }
+                //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
+            }
+
+            // updateFocusedWindowLocked() already assigned layers so we only need to
+            // reassign them at this point if the IM window state gets shuffled
+            if (imMayMove && (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed)) {
+                // Little hack here -- we -should- be able to rely on the
+                // function to return true if the IME has moved and needs
+                // its layer recomputed.  However, if the IME was hidden
+                // and isn't actually moved in the list, its layer may be
+                // out of data so we make sure to recompute it.
+                assignLayersLocked(win.getWindowList());
+            }
+
+            if (wallpaperMayMove) {
+                getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+            }
+
+            win.mDisplayContent.layoutNeeded = true;
+            win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
+            configChanged = updateOrientationFromAppTokensLocked(false);
+            performLayoutAndPlaceSurfacesLocked();
+            if (toBeDisplayed && win.mIsWallpaper) {
+                DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+                updateWallpaperOffsetLocked(win,
+                        displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+            }
+            if (win.mAppToken != null) {
+                win.mAppToken.updateReportedVisibilityLocked();
+            }
+            outFrame.set(win.mCompatFrame);
+            outOverscanInsets.set(win.mOverscanInsets);
+            outContentInsets.set(win.mContentInsets);
+            outVisibleInsets.set(win.mVisibleInsets);
+            if (localLOGV) Slog.v(
+                TAG, "Relayout given client " + client.asBinder()
+                + ", requestedWidth=" + requestedWidth
+                + ", requestedHeight=" + requestedHeight
+                + ", viewVisibility=" + viewVisibility
+                + "\nRelayout returning frame=" + outFrame
+                + ", surface=" + outSurface);
+
+            if (localLOGV || DEBUG_FOCUS) Slog.v(
+                TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
+
+            inTouchMode = mInTouchMode;
+            animating = mAnimator.mAnimating && win.mWinAnimator.isAnimating();
+            if (animating && !mRelayoutWhileAnimating.contains(win)) {
+                mRelayoutWhileAnimating.add(win);
+            }
+
+            mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+            if (DEBUG_LAYOUT) {
+                Slog.v(TAG, "Relayout complete " + win + ": outFrame=" + outFrame.toShortString());
+            }
+        }
+
+        if (configChanged) {
+            sendNewConfiguration();
+        }
+
+        Binder.restoreCallingIdentity(origId);
+
+        return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
+                | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
+                | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0)
+                | (animating ? WindowManagerGlobal.RELAYOUT_RES_ANIMATING : 0);
+    }
+
+    public void performDeferredDestroyWindow(Session session, IWindow client) {
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return;
+                }
+                win.mWinAnimator.destroyDeferredSurfaceLocked();
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public boolean outOfMemoryWindow(Session session, IWindow client) {
+        long origId = Binder.clearCallingIdentity();
+
+        try {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win == null) {
+                    return false;
+                }
+                return reclaimSomeSurfaceMemoryLocked(win.mWinAnimator, "from-client", false);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public void finishDrawingWindow(Session session, IWindow client) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(session, client, false);
+                if (win != null && win.mWinAnimator.finishDrawingLocked()) {
+                    if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+                        getDefaultDisplayContentLocked().pendingLayoutChanges |=
+                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                    }
+                    win.mDisplayContent.layoutNeeded = true;
+                    requestTraversalLocked();
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void getWindowFrame(IBinder token, Rect outBounds) {
+        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
+                "getWindowInfo()")) {
+            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
+        }
+        synchronized (mWindowMap) {
+            WindowState windowState = mWindowMap.get(token);
+            if (windowState != null) {
+                outBounds.set(windowState.mFrame);
+            } else {
+                outBounds.setEmpty();
+            }
+        }
+    }
+
+    @Override
+    public void setMagnificationSpec(MagnificationSpec spec) {
+        if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+                "setMagnificationSpec()")) {
+            throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
+        }
+        synchronized (mWindowMap) {
+            if (mDisplayMagnifier != null) {
+                mDisplayMagnifier.setMagnificationSpecLocked(spec);
+            } else {
+                throw new IllegalStateException("Magnification callbacks not set!");
+            }
+        }
+        if (Binder.getCallingPid() != android.os.Process.myPid()) {
+            spec.recycle();
+        }
+    }
+
+    @Override
+    public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) {
+        if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+                "getCompatibleMagnificationSpecForWindow()")) {
+            throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
+        }
+        synchronized (mWindowMap) {
+            WindowState windowState = mWindowMap.get(windowToken);
+            if (windowState == null) {
+                return null;
+            }
+            MagnificationSpec spec = null;
+            if (mDisplayMagnifier != null) {
+                spec = mDisplayMagnifier.getMagnificationSpecForWindowLocked(windowState);
+            }
+            if ((spec == null || spec.isNop()) && windowState.mGlobalScale == 1.0f) {
+                return null;
+            }
+            spec = (spec == null) ? MagnificationSpec.obtain() : MagnificationSpec.obtain(spec);
+            spec.scale *= windowState.mGlobalScale;
+            return spec;
+        }
+    }
+
+    @Override
+    public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) {
+        if (!checkCallingPermission(android.Manifest.permission.MAGNIFY_DISPLAY,
+                "setMagnificationCallbacks()")) {
+            throw new SecurityException("Requires MAGNIFY_DISPLAY permission.");
+        }
+        synchronized (mWindowMap) {
+            if (mDisplayMagnifier == null) {
+                mDisplayMagnifier = new DisplayMagnifier(this, callbacks);
+            } else {
+                if (callbacks == null) {
+                    if (mDisplayMagnifier != null) {
+                        mDisplayMagnifier.destroyLocked();
+                        mDisplayMagnifier = null;
+                    }
+                } else {
+                    throw new IllegalStateException("Magnification callbacks already set!");
+                }
+            }
+        }
+    }
+
+    private boolean applyAnimationLocked(AppWindowToken atoken,
+            WindowManager.LayoutParams lp, int transit, boolean enter) {
+        // Only apply an animation if the display isn't frozen.  If it is
+        // frozen, there is no reason to animate and it can cause strange
+        // artifacts when we unfreeze the display if some different animation
+        // is running.
+        if (okToDisplay()) {
+            DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+            final int width = displayInfo.appWidth;
+            final int height = displayInfo.appHeight;
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) Slog.v(TAG, "applyAnimation: atoken="
+                    + atoken);
+            Animation a = mAppTransition.loadAnimation(lp, transit, enter, width, height);
+            if (a != null) {
+                if (DEBUG_ANIM) {
+                    RuntimeException e = null;
+                    if (!HIDE_STACK_CRAWLS) {
+                        e = new RuntimeException();
+                        e.fillInStackTrace();
+                    }
+                    Slog.v(TAG, "Loaded animation " + a + " for " + atoken, e);
+                }
+                atoken.mAppAnimator.setAnimation(a, width, height);
+            }
+        } else {
+            atoken.mAppAnimator.clearAnimation();
+        }
+
+        return atoken.mAppAnimator.animation != null;
+    }
+
+    // -------------------------------------------------------------
+    // Application Window Tokens
+    // -------------------------------------------------------------
+
+    public void validateAppTokens(int stackId, List<TaskGroup> tasks) {
+        synchronized (mWindowMap) {
+            int t = tasks.size() - 1;
+            if (t < 0) {
+                Slog.w(TAG, "validateAppTokens: empty task list");
+                return;
+            }
+
+            TaskGroup task = tasks.get(0);
+            int taskId = task.taskId;
+            Task targetTask = mTaskIdToTask.get(taskId);
+            DisplayContent displayContent = targetTask.getDisplayContent();
+            if (displayContent == null) {
+                Slog.w(TAG, "validateAppTokens: no Display for taskId=" + taskId);
+                return;
+            }
+
+            final ArrayList<Task> localTasks = mStackIdToStack.get(stackId).getTasks();
+            int taskNdx;
+            for (taskNdx = localTasks.size() - 1; taskNdx >= 0 && t >= 0; --taskNdx, --t) {
+                AppTokenList localTokens = localTasks.get(taskNdx).mAppTokens;
+                task = tasks.get(t);
+                List<IApplicationToken> tokens = task.tokens;
+
+                DisplayContent lastDisplayContent = displayContent;
+                displayContent = mTaskIdToTask.get(taskId).getDisplayContent();
+                if (displayContent != lastDisplayContent) {
+                    Slog.w(TAG, "validateAppTokens: displayContent changed in TaskGroup list!");
+                    return;
+                }
+
+                int tokenNdx;
+                int v;
+                for (tokenNdx = localTokens.size() - 1, v = task.tokens.size() - 1;
+                        tokenNdx >= 0 && v >= 0; ) {
+                    final AppWindowToken atoken = localTokens.get(tokenNdx);
+                    if (atoken.removed) {
+                        --tokenNdx;
+                        continue;
+                    }
+                    if (tokens.get(v) != atoken.token) {
+                        break;
+                    }
+                    --tokenNdx;
+                    v--;
+                }
+
+                if (tokenNdx >= 0 || v >= 0) {
+                    break;
+                }
+            }
+
+            if (taskNdx >= 0 || t >= 0) {
+                Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks);
+                Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + localTasks);
+                Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4));
+            }
+        }
+    }
+
+    public void validateStackOrder(Integer[] remoteStackIds) {
+        // TODO:
+    }
+
+    boolean checkCallingPermission(String permission, String func) {
+        // Quick check: if the calling permission is me, it's all okay.
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return true;
+        }
+
+        if (mContext.checkCallingPermission(permission)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+        String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid()
+                + ", uid=" + Binder.getCallingUid()
+                + " requires " + permission;
+        Slog.w(TAG, msg);
+        return false;
+    }
+
+    boolean okToDisplay() {
+        return !mDisplayFrozen && mDisplayEnabled && mPolicy.isScreenOnFully();
+    }
+
+    AppWindowToken findAppWindowToken(IBinder token) {
+        WindowToken wtoken = mTokenMap.get(token);
+        if (wtoken == null) {
+            return null;
+        }
+        return wtoken.appWindowToken;
+    }
+
+    @Override
+    public void addWindowToken(IBinder token, int type) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "addWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            WindowToken wtoken = mTokenMap.get(token);
+            if (wtoken != null) {
+                Slog.w(TAG, "Attempted to add existing input method token: " + token);
+                return;
+            }
+            wtoken = new WindowToken(this, token, type, true);
+            mTokenMap.put(token, wtoken);
+            if (type == TYPE_WALLPAPER) {
+                mWallpaperTokens.add(wtoken);
+            }
+        }
+    }
+
+    @Override
+    public void removeWindowToken(IBinder token) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "removeWindowToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        final long origId = Binder.clearCallingIdentity();
+        synchronized(mWindowMap) {
+            DisplayContent displayContent = null;
+            WindowToken wtoken = mTokenMap.remove(token);
+            if (wtoken != null) {
+                boolean delayed = false;
+                if (!wtoken.hidden) {
+                    final int N = wtoken.windows.size();
+                    boolean changed = false;
+
+                    for (int i=0; i<N; i++) {
+                        WindowState win = wtoken.windows.get(i);
+                        displayContent = win.mDisplayContent;
+
+                        if (win.mWinAnimator.isAnimating()) {
+                            delayed = true;
+                        }
+
+                        if (win.isVisibleNow()) {
+                            win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT,
+                                    false);
+                            //TODO (multidisplay): Magnification is supported only for the default
+                            if (mDisplayMagnifier != null && win.isDefaultDisplay()) {
+                                mDisplayMagnifier.onWindowTransitionLocked(win,
+                                        WindowManagerPolicy.TRANSIT_EXIT);
+                            }
+                            changed = true;
+                            displayContent.layoutNeeded = true;
+                        }
+                    }
+
+                    wtoken.hidden = true;
+
+                    if (changed) {
+                        performLayoutAndPlaceSurfacesLocked();
+                        updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+                                false /*updateInputWindows*/);
+                    }
+
+                    if (delayed) {
+                        displayContent.mExitingTokens.add(wtoken);
+                    } else if (wtoken.windowType == TYPE_WALLPAPER) {
+                        mWallpaperTokens.remove(wtoken);
+                    }
+                }
+
+                mInputMonitor.updateInputWindowsLw(true /*force*/);
+            } else {
+                Slog.w(TAG, "Attempted to remove non-existing token: " + token);
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    private Task createTask(int taskId, int stackId, int userId, AppWindowToken atoken) {
+        final TaskStack stack = mStackIdToStack.get(stackId);
+        if (stack == null) {
+            throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId);
+        }
+        EventLog.writeEvent(EventLogTags.WM_TASK_CREATED, taskId, stackId);
+        Task task = new Task(atoken, stack, userId);
+        mTaskIdToTask.put(taskId, task);
+        stack.addTask(task, true);
+        return task;
+    }
+
+    @Override
+    public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId,
+            int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
+            int configChanges) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "addAppToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        // Get the dispatching timeout here while we are not holding any locks so that it
+        // can be cached by the AppWindowToken.  The timeout value is used later by the
+        // input dispatcher in code that does hold locks.  If we did not cache the value
+        // here we would run the chance of introducing a deadlock between the window manager
+        // (which holds locks while updating the input dispatcher state) and the activity manager
+        // (which holds locks while querying the application token).
+        long inputDispatchingTimeoutNanos;
+        try {
+            inputDispatchingTimeoutNanos = token.getKeyDispatchingTimeout() * 1000000L;
+        } catch (RemoteException ex) {
+            Slog.w(TAG, "Could not get dispatching timeout.", ex);
+            inputDispatchingTimeoutNanos = DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+        }
+
+        synchronized(mWindowMap) {
+            AppWindowToken atoken = findAppWindowToken(token.asBinder());
+            if (atoken != null) {
+                Slog.w(TAG, "Attempted to add existing app token: " + token);
+                return;
+            }
+            atoken = new AppWindowToken(this, token);
+            atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
+            atoken.groupId = taskId;
+            atoken.appFullscreen = fullscreen;
+            atoken.showWhenLocked = showWhenLocked;
+            atoken.requestedOrientation = requestedOrientation;
+            atoken.layoutConfigChanges = (configChanges &
+                    (ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_ORIENTATION)) != 0;
+            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken
+                    + " to stack=" + stackId + " task=" + taskId + " at " + addPos);
+
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                task = createTask(taskId, stackId, userId, atoken);
+            } else {
+                task.addAppToken(addPos, atoken);
+            }
+
+            mTokenMap.put(token.asBinder(), atoken);
+
+            // Application tokens start out hidden.
+            atoken.hidden = true;
+            atoken.hiddenRequested = true;
+
+            //dump();
+        }
+    }
+
+    @Override
+    public void setAppGroupId(IBinder token, int groupId) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppGroupId()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            final AppWindowToken atoken = findAppWindowToken(token);
+            if (atoken == null) {
+                Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token);
+                return;
+            }
+            Task oldTask = mTaskIdToTask.get(atoken.groupId);
+            oldTask.removeAppToken(atoken);
+
+            atoken.groupId = groupId;
+            Task newTask = mTaskIdToTask.get(groupId);
+            if (newTask == null) {
+                newTask = createTask(groupId, oldTask.mStack.mStackId, oldTask.mUserId, atoken);
+            }
+            newTask.mAppTokens.add(atoken);
+        }
+    }
+
+    public int getOrientationFromWindowsLocked() {
+        if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
+            // If the display is frozen, some activities may be in the middle
+            // of restarting, and thus have removed their old window.  If the
+            // window has the flag to hide the lock screen, then the lock screen
+            // can re-appear and inflict its own orientation on us.  Keep the
+            // orientation stable until this all settles down.
+            return mLastWindowForcedOrientation;
+        }
+
+        // TODO(multidisplay): Change to the correct display.
+        final WindowList windows = getDefaultWindowListLocked();
+        int pos = windows.size() - 1;
+        while (pos >= 0) {
+            WindowState win = windows.get(pos);
+            pos--;
+            if (win.mAppToken != null) {
+                // We hit an application window. so the orientation will be determined by the
+                // app window. No point in continuing further.
+                return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            }
+            if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
+                continue;
+            }
+            int req = win.mAttrs.screenOrientation;
+            if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
+                    (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
+                continue;
+            }
+
+            if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req);
+            return (mLastWindowForcedOrientation=req);
+        }
+        return (mLastWindowForcedOrientation=ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    public int getOrientationFromAppTokensLocked() {
+        int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        boolean findingBehind = false;
+        boolean lastFullscreen = false;
+        // TODO: Multi window.
+        DisplayContent displayContent = getDefaultDisplayContentLocked();
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int firstToken = tokens.size() - 1;
+            for (int tokenNdx = firstToken; tokenNdx >= 0; --tokenNdx) {
+                final AppWindowToken atoken = tokens.get(tokenNdx);
+
+                if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + atoken);
+
+                // if we're about to tear down this window and not seek for
+                // the behind activity, don't use it for orientation
+                if (!findingBehind
+                        && (!atoken.hidden && atoken.hiddenRequested)) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken
+                            + " -- going to hide");
+                    continue;
+                }
+
+                if (tokenNdx == firstToken) {
+                    // If we have hit a new Task, and the bottom
+                    // of the previous group didn't explicitly say to use
+                    // the orientation behind it, and the last app was
+                    // full screen, then we'll stick with the
+                    // user's orientation.
+                    if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND
+                            && lastFullscreen) {
+                        if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
+                                + " -- end of group, return " + lastOrientation);
+                        return lastOrientation;
+                    }
+                }
+
+                // We ignore any hidden applications on the top.
+                if (atoken.hiddenRequested || atoken.willBeHidden) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken
+                            + " -- hidden on top");
+                    continue;
+                }
+
+                if (tokenNdx == 0) {
+                    // Last token in this task.
+                    lastOrientation = atoken.requestedOrientation;
+                }
+
+                int or = atoken.requestedOrientation;
+                // If this application is fullscreen, and didn't explicitly say
+                // to use the orientation behind it, then just take whatever
+                // orientation it has and ignores whatever is under it.
+                lastFullscreen = atoken.appFullscreen;
+                if (lastFullscreen
+                        && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
+                            + " -- full screen, return " + or);
+                    return or;
+                }
+                // If this application has requested an explicit orientation,
+                // then use it.
+                if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+                        && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken
+                            + " -- explicitly set, return " + or);
+                    return or;
+                }
+                findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
+            }
+        }
+        if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation");
+        return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    }
+
+    @Override
+    public Configuration updateOrientationFromAppTokens(
+            Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "updateOrientationFromAppTokens()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        Configuration config = null;
+        long ident = Binder.clearCallingIdentity();
+
+        synchronized(mWindowMap) {
+            config = updateOrientationFromAppTokensLocked(currentConfig,
+                    freezeThisOneIfNeeded);
+        }
+
+        Binder.restoreCallingIdentity(ident);
+        return config;
+    }
+
+    private Configuration updateOrientationFromAppTokensLocked(
+            Configuration currentConfig, IBinder freezeThisOneIfNeeded) {
+        Configuration config = null;
+
+        if (updateOrientationFromAppTokensLocked(false)) {
+            if (freezeThisOneIfNeeded != null) {
+                AppWindowToken atoken = findAppWindowToken(freezeThisOneIfNeeded);
+                if (atoken != null) {
+                    startAppFreezingScreenLocked(atoken, ActivityInfo.CONFIG_ORIENTATION);
+                }
+            }
+            config = computeNewConfigurationLocked();
+
+        } else if (currentConfig != null) {
+            // No obvious action we need to take, but if our current
+            // state mismatches the activity manager's, update it,
+            // disregarding font scale, which should remain set to
+            // the value of the previous configuration.
+            mTempConfiguration.setToDefaults();
+            mTempConfiguration.fontScale = currentConfig.fontScale;
+            if (computeScreenConfigurationLocked(mTempConfiguration)) {
+                if (currentConfig.diff(mTempConfiguration) != 0) {
+                    mWaitingForConfig = true;
+                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                    displayContent.layoutNeeded = true;
+                    int anim[] = new int[2];
+                    if (displayContent.isDimming()) {
+                        anim[0] = anim[1] = 0;
+                    } else {
+                        mPolicy.selectRotationAnimationLw(anim);
+                    }
+                    startFreezingDisplayLocked(false, anim[0], anim[1]);
+                    config = new Configuration(mTempConfiguration);
+                }
+            }
+        }
+
+        return config;
+    }
+
+    /*
+     * Determine the new desired orientation of the display, returning
+     * a non-null new Configuration if it has changed from the current
+     * orientation.  IF TRUE IS RETURNED SOMEONE MUST CALL
+     * setNewConfiguration() TO TELL THE WINDOW MANAGER IT CAN UNFREEZE THE
+     * SCREEN.  This will typically be done for you if you call
+     * sendNewConfiguration().
+     *
+     * The orientation is computed from non-application windows first. If none of
+     * the non-application windows specify orientation, the orientation is computed from
+     * application tokens.
+     * @see android.view.IWindowManager#updateOrientationFromAppTokens(
+     * android.os.IBinder)
+     */
+    boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            int req = getOrientationFromWindowsLocked();
+            if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+                req = getOrientationFromAppTokensLocked();
+            }
+
+            if (req != mForcedAppOrientation) {
+                mForcedAppOrientation = req;
+                //send a message to Policy indicating orientation change to take
+                //action like disabling/enabling sensors etc.,
+                mPolicy.setCurrentOrientationLw(req);
+                if (updateRotationUncheckedLocked(inTransaction)) {
+                    // changed
+                    return true;
+                }
+            }
+
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public void setNewConfiguration(Configuration config) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setNewConfiguration()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            mCurConfiguration = new Configuration(config);
+            if (mWaitingForConfig) {
+                mWaitingForConfig = false;
+                mLastFinishedFreezeSource = "new-config";
+            }
+            performLayoutAndPlaceSurfacesLocked();
+        }
+    }
+
+    @Override
+    public void setAppOrientation(IApplicationToken token, int requestedOrientation) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppOrientation()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            AppWindowToken atoken = findAppWindowToken(token.asBinder());
+            if (atoken == null) {
+                Slog.w(TAG, "Attempted to set orientation of non-existing app token: " + token);
+                return;
+            }
+
+            atoken.requestedOrientation = requestedOrientation;
+        }
+    }
+
+    @Override
+    public int getAppOrientation(IApplicationToken token) {
+        synchronized(mWindowMap) {
+            AppWindowToken wtoken = findAppWindowToken(token.asBinder());
+            if (wtoken == null) {
+                return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+            }
+
+            return wtoken.requestedOrientation;
+        }
+    }
+
+    /** Call while in a Surface transaction. */
+    void setFocusedStackLayer() {
+        mFocusedStackLayer = 0;
+        if (mFocusedApp != null) {
+            final WindowList windows = mFocusedApp.allAppWindows;
+            for (int i = windows.size() - 1; i >= 0; --i) {
+                final WindowState win = windows.get(i);
+                final int animLayer = win.mWinAnimator.mAnimLayer;
+                if (win.mAttachedWindow == null && win.isVisibleLw() &&
+                        animLayer > mFocusedStackLayer) {
+                    mFocusedStackLayer = animLayer + LAYER_OFFSET_FOCUSED_STACK;
+                }
+            }
+        }
+        if (DEBUG_LAYERS) Slog.v(TAG, "Setting FocusedStackFrame to layer=" +
+                mFocusedStackLayer);
+        mFocusedStackFrame.setLayer(mFocusedStackLayer);
+    }
+
+    void setFocusedStackFrame() {
+        final TaskStack stack;
+        if (mFocusedApp != null) {
+            Task task = mTaskIdToTask.get(mFocusedApp.groupId);
+            stack = task.mStack;
+            task.getDisplayContent().setTouchExcludeRegion(stack);
+        } else {
+            stack = null;
+        }
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedStackFrame");
+        SurfaceControl.openTransaction();
+        try {
+            if (stack == null) {
+                mFocusedStackFrame.setVisibility(false);
+            } else {
+                mFocusedStackFrame.setBounds(stack);
+                final boolean multipleStacks = !stack.isFullscreen();
+                mFocusedStackFrame.setVisibility(multipleStacks);
+            }
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedStackFrame");
+        }
+    }
+
+    @Override
+    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setFocusedApp()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            boolean changed = false;
+            if (token == null) {
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp);
+                changed = mFocusedApp != null;
+                mFocusedApp = null;
+                if (changed) {
+                    mInputMonitor.setFocusedAppLw(null);
+                }
+            } else {
+                AppWindowToken newFocus = findAppWindowToken(token);
+                if (newFocus == null) {
+                    Slog.w(TAG, "Attempted to set focus to non-existing app token: " + token);
+                    return;
+                }
+                changed = mFocusedApp != newFocus;
+                if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Set focused app to: " + newFocus
+                        + " old focus=" + mFocusedApp + " moveFocusNow=" + moveFocusNow);
+                mFocusedApp = newFocus;
+                if (changed) {
+                    mInputMonitor.setFocusedAppLw(newFocus);
+                }
+            }
+
+            if (moveFocusNow && changed) {
+                final long origId = Binder.clearCallingIdentity();
+                updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void prepareAppTransition(int transit, boolean alwaysKeepCurrent) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "prepareAppTransition()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (DEBUG_APP_TRANSITIONS) Slog.v(
+                    TAG, "Prepare app transition: transit=" + transit
+                    + " " + mAppTransition
+                    + " alwaysKeepCurrent=" + alwaysKeepCurrent
+                    + " Callers=" + Debug.getCallers(3));
+            if (okToDisplay()) {
+                if (!mAppTransition.isTransitionSet() || mAppTransition.isTransitionNone()) {
+                    mAppTransition.setAppTransition(transit);
+                } else if (!alwaysKeepCurrent) {
+                    if (transit == AppTransition.TRANSIT_TASK_OPEN
+                            && mAppTransition.isTransitionEqual(
+                                    AppTransition.TRANSIT_TASK_CLOSE)) {
+                        // Opening a new task always supersedes a close for the anim.
+                        mAppTransition.setAppTransition(transit);
+                    } else if (transit == AppTransition.TRANSIT_ACTIVITY_OPEN
+                            && mAppTransition.isTransitionEqual(
+                                AppTransition.TRANSIT_ACTIVITY_CLOSE)) {
+                        // Opening a new activity always supersedes a close for the anim.
+                        mAppTransition.setAppTransition(transit);
+                    }
+                }
+                mAppTransition.prepare();
+                mStartingIconInTransition = false;
+                mSkipAppTransitionAnimation = false;
+                mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+                mH.sendEmptyMessageDelayed(H.APP_TRANSITION_TIMEOUT, 5000);
+            }
+        }
+    }
+
+    @Override
+    public int getPendingAppTransition() {
+        return mAppTransition.getAppTransition();
+    }
+
+    @Override
+    public void overridePendingAppTransition(String packageName,
+            int enterAnim, int exitAnim, IRemoteCallback startedCallback) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransition(packageName, enterAnim, exitAnim,
+                    startedCallback);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionScaleUp(int startX, int startY, int startWidth,
+            int startHeight) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionScaleUp(startX, startY, startWidth,
+                    startHeight);
+        }
+    }
+
+    @Override
+    public void overridePendingAppTransitionThumb(Bitmap srcThumb, int startX,
+            int startY, IRemoteCallback startedCallback, boolean scaleUp) {
+        synchronized(mWindowMap) {
+            mAppTransition.overridePendingAppTransitionThumb(srcThumb, startX, startY,
+                    startedCallback, scaleUp);
+        }
+    }
+
+    @Override
+    public void executeAppTransition() {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "executeAppTransition()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (DEBUG_APP_TRANSITIONS) {
+                RuntimeException e = new RuntimeException("here");
+                e.fillInStackTrace();
+                Slog.w(TAG, "Execute app transition: " + mAppTransition, e);
+            }
+            if (mAppTransition.isTransitionSet()) {
+                mAppTransition.setReady();
+                final long origId = Binder.clearCallingIdentity();
+                performLayoutAndPlaceSurfacesLocked();
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void setAppStartingWindow(IBinder token, String pkg,
+            int theme, CompatibilityInfo compatInfo,
+            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo,
+            int windowFlags, IBinder transferFrom, boolean createIfNeeded) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppStartingWindow()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(
+                    TAG, "setAppStartingWindow: token=" + token + " pkg=" + pkg
+                    + " transferFrom=" + transferFrom);
+
+            AppWindowToken wtoken = findAppWindowToken(token);
+            if (wtoken == null) {
+                Slog.w(TAG, "Attempted to set icon of non-existing app token: " + token);
+                return;
+            }
+
+            // If the display is frozen, we won't do anything until the
+            // actual window is displayed so there is no reason to put in
+            // the starting window.
+            if (!okToDisplay()) {
+                return;
+            }
+
+            if (wtoken.startingData != null) {
+                return;
+            }
+
+            if (transferFrom != null) {
+                AppWindowToken ttoken = findAppWindowToken(transferFrom);
+                if (ttoken != null) {
+                    WindowState startingWindow = ttoken.startingWindow;
+                    if (startingWindow != null) {
+                        if (mStartingIconInTransition) {
+                            // In this case, the starting icon has already
+                            // been displayed, so start letting windows get
+                            // shown immediately without any more transitions.
+                            mSkipAppTransitionAnimation = true;
+                        }
+                        if (DEBUG_STARTING_WINDOW) Slog.v(TAG,
+                                "Moving existing starting " + startingWindow + " from " + ttoken
+                                + " to " + wtoken);
+                        final long origId = Binder.clearCallingIdentity();
+
+                        // Transfer the starting window over to the new
+                        // token.
+                        wtoken.startingData = ttoken.startingData;
+                        wtoken.startingView = ttoken.startingView;
+                        wtoken.startingDisplayed = ttoken.startingDisplayed;
+                        ttoken.startingDisplayed = false;
+                        wtoken.startingWindow = startingWindow;
+                        wtoken.reportedVisible = ttoken.reportedVisible;
+                        ttoken.startingData = null;
+                        ttoken.startingView = null;
+                        ttoken.startingWindow = null;
+                        ttoken.startingMoved = true;
+                        startingWindow.mToken = wtoken;
+                        startingWindow.mRootToken = wtoken;
+                        startingWindow.mAppToken = wtoken;
+                        startingWindow.mWinAnimator.mAppAnimator = wtoken.mAppAnimator;
+
+                        if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) {
+                            Slog.v(TAG, "Removing starting window: " + startingWindow);
+                        }
+                        removeStartingWindowTimeout(ttoken);
+                        startingWindow.getWindowList().remove(startingWindow);
+                        mWindowsChanged = true;
+                        if (DEBUG_ADD_REMOVE) Slog.v(TAG,
+                                "Removing starting " + startingWindow + " from " + ttoken);
+                        ttoken.windows.remove(startingWindow);
+                        ttoken.allAppWindows.remove(startingWindow);
+                        addWindowToListInOrderLocked(startingWindow, true);
+
+                        // Propagate other interesting state between the
+                        // tokens.  If the old token is displayed, we should
+                        // immediately force the new one to be displayed.  If
+                        // it is animating, we need to move that animation to
+                        // the new one.
+                        if (ttoken.allDrawn) {
+                            wtoken.allDrawn = true;
+                            wtoken.deferClearAllDrawn = ttoken.deferClearAllDrawn;
+                        }
+                        if (ttoken.firstWindowDrawn) {
+                            wtoken.firstWindowDrawn = true;
+                        }
+                        if (!ttoken.hidden) {
+                            wtoken.hidden = false;
+                            wtoken.hiddenRequested = false;
+                            wtoken.willBeHidden = false;
+                        }
+                        if (wtoken.clientHidden != ttoken.clientHidden) {
+                            wtoken.clientHidden = ttoken.clientHidden;
+                            wtoken.sendAppVisibilityToClients();
+                        }
+                        final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator;
+                        final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator;
+                        if (tAppAnimator.animation != null) {
+                            wAppAnimator.animation = tAppAnimator.animation;
+                            wAppAnimator.animating = tAppAnimator.animating;
+                            wAppAnimator.animLayerAdjustment = tAppAnimator.animLayerAdjustment;
+                            tAppAnimator.animation = null;
+                            tAppAnimator.animLayerAdjustment = 0;
+                            wAppAnimator.updateLayers();
+                            tAppAnimator.updateLayers();
+                        }
+
+                        updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                                true /*updateInputWindows*/);
+                        getDefaultDisplayContentLocked().layoutNeeded = true;
+                        performLayoutAndPlaceSurfacesLocked();
+                        Binder.restoreCallingIdentity(origId);
+                        return;
+                    } else if (ttoken.startingData != null) {
+                        // The previous app was getting ready to show a
+                        // starting window, but hasn't yet done so.  Steal it!
+                        if (DEBUG_STARTING_WINDOW) Slog.v(TAG,
+                                "Moving pending starting from " + ttoken
+                                + " to " + wtoken);
+                        wtoken.startingData = ttoken.startingData;
+                        ttoken.startingData = null;
+                        ttoken.startingMoved = true;
+                        Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
+                        // Note: we really want to do sendMessageAtFrontOfQueue() because we
+                        // want to process the message ASAP, before any other queued
+                        // messages.
+                        mH.sendMessageAtFrontOfQueue(m);
+                        return;
+                    }
+                    final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator;
+                    final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator;
+                    if (tAppAnimator.thumbnail != null) {
+                        // The old token is animating with a thumbnail, transfer
+                        // that to the new token.
+                        if (wAppAnimator.thumbnail != null) {
+                            wAppAnimator.thumbnail.destroy();
+                        }
+                        wAppAnimator.thumbnail = tAppAnimator.thumbnail;
+                        wAppAnimator.thumbnailX = tAppAnimator.thumbnailX;
+                        wAppAnimator.thumbnailY = tAppAnimator.thumbnailY;
+                        wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
+                        wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
+                        tAppAnimator.thumbnail = null;
+                    }
+                }
+            }
+
+            // There is no existing starting window, and the caller doesn't
+            // want us to create one, so that's it!
+            if (!createIfNeeded) {
+                return;
+            }
+
+            // If this is a translucent window, then don't
+            // show a starting window -- the current effect (a full-screen
+            // opaque starting window that fades away to the real contents
+            // when it is ready) does not work for this.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Checking theme of starting window: 0x"
+                    + Integer.toHexString(theme));
+            if (theme != 0) {
+                AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
+                        com.android.internal.R.styleable.Window, mCurrentUserId);
+                if (ent == null) {
+                    // Whoops!  App doesn't exist.  Um.  Okay.  We'll just
+                    // pretend like we didn't see that.
+                    return;
+                }
+                if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Translucent="
+                        + ent.array.getBoolean(
+                                com.android.internal.R.styleable.Window_windowIsTranslucent, false)
+                        + " Floating="
+                        + ent.array.getBoolean(
+                                com.android.internal.R.styleable.Window_windowIsFloating, false)
+                        + " ShowWallpaper="
+                        + ent.array.getBoolean(
+                                com.android.internal.R.styleable.Window_windowShowWallpaper, false));
+                if (ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowIsTranslucent, false)) {
+                    return;
+                }
+                if (ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowIsFloating, false)) {
+                    return;
+                }
+                if (ent.array.getBoolean(
+                        com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
+                    if (mWallpaperTarget == null) {
+                        // If this theme is requesting a wallpaper, and the wallpaper
+                        // is not curently visible, then this effectively serves as
+                        // an opaque window and our starting window transition animation
+                        // can still work.  We just need to make sure the starting window
+                        // is also showing the wallpaper.
+                        windowFlags |= FLAG_SHOW_WALLPAPER;
+                    } else {
+                        return;
+                    }
+                }
+            }
+
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Creating StartingData");
+            mStartingIconInTransition = true;
+            wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,
+                    labelRes, icon, logo, windowFlags);
+            Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);
+            // Note: we really want to do sendMessageAtFrontOfQueue() because we
+            // want to process the message ASAP, before any other queued
+            // messages.
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Enqueueing ADD_STARTING");
+            mH.sendMessageAtFrontOfQueue(m);
+        }
+    }
+
+    @Override
+    public void setAppWillBeHidden(IBinder token) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppWillBeHidden()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        AppWindowToken wtoken;
+
+        synchronized(mWindowMap) {
+            wtoken = findAppWindowToken(token);
+            if (wtoken == null) {
+                Slog.w(TAG, "Attempted to set will be hidden of non-existing app token: " + token);
+                return;
+            }
+            wtoken.willBeHidden = true;
+        }
+    }
+
+    public void setAppFullscreen(IBinder token, boolean toOpaque) {
+        AppWindowToken atoken = findAppWindowToken(token);
+        if (atoken != null) {
+            atoken.appFullscreen = toOpaque;
+            requestTraversal();
+        }
+    }
+
+    boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp,
+            boolean visible, int transit, boolean performLayout) {
+        boolean delayed = false;
+
+        if (wtoken.clientHidden == visible) {
+            wtoken.clientHidden = !visible;
+            wtoken.sendAppVisibilityToClients();
+        }
+
+        wtoken.willBeHidden = false;
+        if (wtoken.hidden == visible) {
+            boolean changed = false;
+            if (DEBUG_APP_TRANSITIONS) Slog.v(
+                TAG, "Changing app " + wtoken + " hidden=" + wtoken.hidden
+                + " performLayout=" + performLayout);
+
+            boolean runningAppAnimation = false;
+
+            if (transit != AppTransition.TRANSIT_UNSET) {
+                if (wtoken.mAppAnimator.animation == AppWindowAnimator.sDummyAnimation) {
+                    wtoken.mAppAnimator.animation = null;
+                }
+                if (applyAnimationLocked(wtoken, lp, transit, visible)) {
+                    delayed = runningAppAnimation = true;
+                }
+                WindowState window = wtoken.findMainWindow();
+                //TODO (multidisplay): Magnification is supported only for the default display.
+                if (window != null && mDisplayMagnifier != null
+                        && window.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                    mDisplayMagnifier.onAppWindowTransitionLocked(window, transit);
+                }
+                changed = true;
+            }
+
+            final int N = wtoken.allAppWindows.size();
+            for (int i=0; i<N; i++) {
+                WindowState win = wtoken.allAppWindows.get(i);
+                if (win == wtoken.startingWindow) {
+                    continue;
+                }
+
+                //Slog.i(TAG, "Window " + win + ": vis=" + win.isVisible());
+                //win.dump("  ");
+                if (visible) {
+                    if (!win.isVisibleNow()) {
+                        if (!runningAppAnimation) {
+                            win.mWinAnimator.applyAnimationLocked(
+                                    WindowManagerPolicy.TRANSIT_ENTER, true);
+                            //TODO (multidisplay): Magnification is supported only for the default
+                            if (mDisplayMagnifier != null
+                                    && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                                mDisplayMagnifier.onWindowTransitionLocked(win,
+                                        WindowManagerPolicy.TRANSIT_ENTER);
+                            }
+                        }
+                        changed = true;
+                        win.mDisplayContent.layoutNeeded = true;
+                    }
+                } else if (win.isVisibleNow()) {
+                    if (!runningAppAnimation) {
+                        win.mWinAnimator.applyAnimationLocked(
+                                WindowManagerPolicy.TRANSIT_EXIT, false);
+                        //TODO (multidisplay): Magnification is supported only for the default
+                        if (mDisplayMagnifier != null
+                                && win.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                            mDisplayMagnifier.onWindowTransitionLocked(win,
+                                    WindowManagerPolicy.TRANSIT_EXIT);
+                        }
+                    }
+                    changed = true;
+                    win.mDisplayContent.layoutNeeded = true;
+                }
+            }
+
+            wtoken.hidden = wtoken.hiddenRequested = !visible;
+            if (!visible) {
+                unsetAppFreezingScreenLocked(wtoken, true, true);
+            } else {
+                // If we are being set visible, and the starting window is
+                // not yet displayed, then make sure it doesn't get displayed.
+                WindowState swin = wtoken.startingWindow;
+                if (swin != null && !swin.isDrawnLw()) {
+                    swin.mPolicyVisibility = false;
+                    swin.mPolicyVisibilityAfterAnim = false;
+                 }
+            }
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "setTokenVisibilityLocked: " + wtoken
+                      + ": hidden=" + wtoken.hidden + " hiddenRequested="
+                      + wtoken.hiddenRequested);
+
+            if (changed) {
+                mInputMonitor.setUpdateInputWindowsNeededLw();
+                if (performLayout) {
+                    updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                            false /*updateInputWindows*/);
+                    performLayoutAndPlaceSurfacesLocked();
+                }
+                mInputMonitor.updateInputWindowsLw(false /*force*/);
+            }
+        }
+
+        if (wtoken.mAppAnimator.animation != null) {
+            delayed = true;
+        }
+
+        for (int i = wtoken.allAppWindows.size() - 1; i >= 0 && !delayed; i--) {
+            if (wtoken.allAppWindows.get(i).mWinAnimator.isWindowAnimating()) {
+                delayed = true;
+            }
+        }
+
+        return delayed;
+    }
+
+    @Override
+    public void setAppVisibility(IBinder token, boolean visible) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppVisibility()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        AppWindowToken wtoken;
+
+        synchronized(mWindowMap) {
+            wtoken = findAppWindowToken(token);
+            if (wtoken == null) {
+                Slog.w(TAG, "Attempted to set visibility of non-existing app token: " + token);
+                return;
+            }
+
+            if (DEBUG_APP_TRANSITIONS || DEBUG_ORIENTATION) {
+                RuntimeException e = null;
+                if (!HIDE_STACK_CRAWLS) {
+                    e = new RuntimeException();
+                    e.fillInStackTrace();
+                }
+                Slog.v(TAG, "setAppVisibility(" + token + ", visible=" + visible
+                        + "): " + mAppTransition
+                        + " hidden=" + wtoken.hidden
+                        + " hiddenRequested=" + wtoken.hiddenRequested, e);
+            }
+
+            // If we are preparing an app transition, then delay changing
+            // the visibility of this token until we execute that transition.
+            if (okToDisplay() && mAppTransition.isTransitionSet()) {
+                wtoken.hiddenRequested = !visible;
+
+                if (!wtoken.startingDisplayed) {
+                    if (DEBUG_APP_TRANSITIONS) Slog.v(
+                            TAG, "Setting dummy animation on: " + wtoken);
+                    wtoken.mAppAnimator.setDummyAnimation();
+                }
+                mOpeningApps.remove(wtoken);
+                mClosingApps.remove(wtoken);
+                wtoken.waitingToShow = wtoken.waitingToHide = false;
+                wtoken.inPendingTransaction = true;
+                if (visible) {
+                    mOpeningApps.add(wtoken);
+                    wtoken.startingMoved = false;
+
+                    // If the token is currently hidden (should be the
+                    // common case), then we need to set up to wait for
+                    // its windows to be ready.
+                    if (wtoken.hidden) {
+                        wtoken.allDrawn = false;
+                        wtoken.deferClearAllDrawn = false;
+                        wtoken.waitingToShow = true;
+
+                        if (wtoken.clientHidden) {
+                            // In the case where we are making an app visible
+                            // but holding off for a transition, we still need
+                            // to tell the client to make its windows visible so
+                            // they get drawn.  Otherwise, we will wait on
+                            // performing the transition until all windows have
+                            // been drawn, they never will be, and we are sad.
+                            wtoken.clientHidden = false;
+                            wtoken.sendAppVisibilityToClients();
+                        }
+                    }
+                } else {
+                    mClosingApps.add(wtoken);
+
+                    // If the token is currently visible (should be the
+                    // common case), then set up to wait for it to be hidden.
+                    if (!wtoken.hidden) {
+                        wtoken.waitingToHide = true;
+                    }
+                }
+                return;
+            }
+
+            final long origId = Binder.clearCallingIdentity();
+            setTokenVisibilityLocked(wtoken, null, visible, AppTransition.TRANSIT_UNSET,
+                    true);
+            wtoken.updateReportedVisibilityLocked();
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    void unsetAppFreezingScreenLocked(AppWindowToken wtoken,
+            boolean unfreezeSurfaceNow, boolean force) {
+        if (wtoken.mAppAnimator.freezingScreen) {
+            if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + wtoken
+                    + " force=" + force);
+            final int N = wtoken.allAppWindows.size();
+            boolean unfrozeWindows = false;
+            for (int i=0; i<N; i++) {
+                WindowState w = wtoken.allAppWindows.get(i);
+                if (w.mAppFreezing) {
+                    w.mAppFreezing = false;
+                    if (w.mHasSurface && !w.mOrientationChanging) {
+                        if (DEBUG_ORIENTATION) Slog.v(TAG, "set mOrientationChanging of " + w);
+                        w.mOrientationChanging = true;
+                        mInnerFields.mOrientationChangeComplete = false;
+                    }
+                    w.mLastFreezeDuration = 0;
+                    unfrozeWindows = true;
+                    w.mDisplayContent.layoutNeeded = true;
+                }
+            }
+            if (force || unfrozeWindows) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG, "No longer freezing: " + wtoken);
+                wtoken.mAppAnimator.freezingScreen = false;
+                wtoken.mAppAnimator.lastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                        - mDisplayFreezeTime);
+                mAppsFreezingScreen--;
+                mLastFinishedFreezeSource = wtoken;
+            }
+            if (unfreezeSurfaceNow) {
+                if (unfrozeWindows) {
+                    performLayoutAndPlaceSurfacesLocked();
+                }
+                stopFreezingDisplayLocked();
+            }
+        }
+    }
+
+    public void startAppFreezingScreenLocked(AppWindowToken wtoken,
+            int configChanges) {
+        if (DEBUG_ORIENTATION) {
+            RuntimeException e = null;
+            if (!HIDE_STACK_CRAWLS) {
+                e = new RuntimeException();
+                e.fillInStackTrace();
+            }
+            Slog.i(TAG, "Set freezing of " + wtoken.appToken
+                    + ": hidden=" + wtoken.hidden + " freezing="
+                    + wtoken.mAppAnimator.freezingScreen, e);
+        }
+        if (!wtoken.hiddenRequested) {
+            if (!wtoken.mAppAnimator.freezingScreen) {
+                wtoken.mAppAnimator.freezingScreen = true;
+                wtoken.mAppAnimator.lastFreezeDuration = 0;
+                mAppsFreezingScreen++;
+                if (mAppsFreezingScreen == 1) {
+                    startFreezingDisplayLocked(false, 0, 0);
+                    mH.removeMessages(H.APP_FREEZE_TIMEOUT);
+                    mH.sendEmptyMessageDelayed(H.APP_FREEZE_TIMEOUT, 5000);
+                }
+            }
+            final int N = wtoken.allAppWindows.size();
+            for (int i=0; i<N; i++) {
+                WindowState w = wtoken.allAppWindows.get(i);
+                w.mAppFreezing = true;
+            }
+        }
+    }
+
+    @Override
+    public void startAppFreezingScreen(IBinder token, int configChanges) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppFreezingScreen()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (configChanges == 0 && okToDisplay()) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping set freeze of " + token);
+                return;
+            }
+
+            AppWindowToken wtoken = findAppWindowToken(token);
+            if (wtoken == null || wtoken.appToken == null) {
+                Slog.w(TAG, "Attempted to freeze screen with non-existing app token: " + wtoken);
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            startAppFreezingScreenLocked(wtoken, configChanges);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void stopAppFreezingScreen(IBinder token, boolean force) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setAppFreezingScreen()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized(mWindowMap) {
+            AppWindowToken wtoken = findAppWindowToken(token);
+            if (wtoken == null || wtoken.appToken == null) {
+                return;
+            }
+            final long origId = Binder.clearCallingIdentity();
+            if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + token
+                    + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.mAppAnimator.freezingScreen);
+            unsetAppFreezingScreenLocked(wtoken, true, force);
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void removeAppToken(IBinder token) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "removeAppToken()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        AppWindowToken wtoken = null;
+        AppWindowToken startingToken = null;
+        boolean delayed = false;
+
+        final long origId = Binder.clearCallingIdentity();
+        synchronized(mWindowMap) {
+            WindowToken basewtoken = mTokenMap.remove(token);
+            if (basewtoken != null && (wtoken=basewtoken.appWindowToken) != null) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Removing app token: " + wtoken);
+                delayed = setTokenVisibilityLocked(wtoken, null, false,
+                        AppTransition.TRANSIT_UNSET, true);
+                wtoken.inPendingTransaction = false;
+                mOpeningApps.remove(wtoken);
+                wtoken.waitingToShow = false;
+                if (mClosingApps.contains(wtoken)) {
+                    delayed = true;
+                } else if (mAppTransition.isTransitionSet()) {
+                    mClosingApps.add(wtoken);
+                    wtoken.waitingToHide = true;
+                    delayed = true;
+                }
+                if (DEBUG_APP_TRANSITIONS) Slog.v(
+                        TAG, "Removing app " + wtoken + " delayed=" + delayed
+                        + " animation=" + wtoken.mAppAnimator.animation
+                        + " animating=" + wtoken.mAppAnimator.animating);
+                final Task task = mTaskIdToTask.get(wtoken.groupId);
+                DisplayContent displayContent = task.getDisplayContent();
+                if (delayed) {
+                    // set the token aside because it has an active animation to be finished
+                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                            "removeAppToken make exiting: " + wtoken);
+                    displayContent.mExitingAppTokens.add(wtoken);
+                } else {
+                    // Make sure there is no animation running on this token,
+                    // so any windows associated with it will be removed as
+                    // soon as their animations are complete
+                    wtoken.mAppAnimator.clearAnimation();
+                    wtoken.mAppAnimator.animating = false;
+                }
+                if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                        "removeAppToken: " + wtoken);
+
+                if (task.removeAppToken(wtoken)) {
+                    mTaskIdToTask.delete(wtoken.groupId);
+                }
+                wtoken.removed = true;
+                if (wtoken.startingData != null) {
+                    startingToken = wtoken;
+                }
+                unsetAppFreezingScreenLocked(wtoken, true, true);
+                if (mFocusedApp == wtoken) {
+                    if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Removing focused app token:" + wtoken);
+                    mFocusedApp = null;
+                    updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
+                    mInputMonitor.setFocusedAppLw(null);
+                }
+            } else {
+                Slog.w(TAG, "Attempted to remove non-existing app token: " + token);
+            }
+
+            if (!delayed && wtoken != null) {
+                wtoken.updateReportedVisibilityLocked();
+            }
+        }
+        Binder.restoreCallingIdentity(origId);
+
+        // Will only remove if startingToken non null.
+        scheduleRemoveStartingWindow(startingToken);
+    }
+
+    void removeStartingWindowTimeout(AppWindowToken wtoken) {
+        if (wtoken != null) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
+                    ": Remove starting window timeout " + wtoken + (wtoken != null ?
+                    " startingWindow=" + wtoken.startingWindow : ""));
+            mH.removeMessages(H.REMOVE_STARTING_TIMEOUT, wtoken);
+        }
+    }
+
+    void scheduleRemoveStartingWindow(AppWindowToken wtoken) {
+        if (wtoken != null && wtoken.startingWindow != null) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) +
+                    ": Schedule remove starting " + wtoken + (wtoken != null ?
+                    " startingWindow=" + wtoken.startingWindow : ""));
+            removeStartingWindowTimeout(wtoken);
+            Message m = mH.obtainMessage(H.REMOVE_STARTING, wtoken);
+            mH.sendMessage(m);
+        }
+    }
+    private boolean tmpRemoveAppWindowsLocked(WindowToken token) {
+        final int NW = token.windows.size();
+        if (NW > 0) {
+            mWindowsChanged = true;
+        }
+        for (int i=0; i<NW; i++) {
+            WindowState win = token.windows.get(i);
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Tmp removing app window " + win);
+            win.getWindowList().remove(win);
+            int j = win.mChildWindows.size();
+            while (j > 0) {
+                j--;
+                WindowState cwin = win.mChildWindows.get(j);
+                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG,
+                        "Tmp removing child window " + cwin);
+                cwin.getWindowList().remove(cwin);
+            }
+        }
+        return NW > 0;
+    }
+
+    void dumpAppTokensLocked() {
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+            Slog.v(TAG, "  Display " + displayContent.getDisplayId());
+            final ArrayList<Task> tasks = displayContent.getTasks();
+            int i = displayContent.numTokens();
+            for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+                for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
+                    final AppWindowToken wtoken = tokens.get(tokenNdx);
+                    Slog.v(TAG, "  #" + --i + ": " + wtoken.token);
+                }
+            }
+        }
+    }
+
+    void dumpWindowsLocked() {
+        int i = 0;
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+            for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) {
+                Slog.v(TAG, "  #" + i++ + ": " + windows.get(winNdx));
+            }
+        }
+    }
+
+    private int findAppWindowInsertionPointLocked(AppWindowToken target) {
+        final int taskId = target.groupId;
+        Task targetTask = mTaskIdToTask.get(taskId);
+        if (targetTask == null) {
+            Slog.w(TAG, "findAppWindowInsertionPointLocked: no Task for " + target + " taskId="
+                    + taskId);
+            return 0;
+        }
+        DisplayContent displayContent = targetTask.getDisplayContent();
+        if (displayContent == null) {
+            Slog.w(TAG, "findAppWindowInsertionPointLocked: no DisplayContent for " + target);
+            return 0;
+        }
+        final WindowList windows = displayContent.getWindowList();
+        final int NW = windows.size();
+
+        boolean found = false;
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+            final Task task = tasks.get(taskNdx);
+            if (!found && task.taskId != taskId) {
+                continue;
+            }
+            AppTokenList tokens = task.mAppTokens;
+            for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                if (!found && wtoken == target) {
+                    found = true;
+                }
+                if (found) {
+                    // Find the first app token below the new position that has
+                    // a window displayed.
+                    if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows in " + wtoken.token);
+                    if (wtoken.sendingToBottom) {
+                        if (DEBUG_REORDER) Slog.v(TAG, "Skipping token -- currently sending to bottom");
+                        continue;
+                    }
+                    for (int i = wtoken.windows.size() - 1; i >= 0; --i) {
+                        WindowState win = wtoken.windows.get(i);
+                        for (int j = win.mChildWindows.size() - 1; j >= 0; --j) {
+                            WindowState cwin = win.mChildWindows.get(j);
+                            if (cwin.mSubLayer >= 0) {
+                                for (int pos = NW - 1; pos >= 0; pos--) {
+                                    if (windows.get(pos) == cwin) {
+                                        if (DEBUG_REORDER) Slog.v(TAG,
+                                                "Found child win @" + (pos + 1));
+                                        return pos + 1;
+                                    }
+                                }
+                            }
+                        }
+                        for (int pos = NW - 1; pos >= 0; pos--) {
+                            if (windows.get(pos) == win) {
+                                if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos + 1));
+                                return pos + 1;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        // Never put an app window underneath wallpaper.
+        for (int pos = NW - 1; pos >= 0; pos--) {
+            if (windows.get(pos).mIsWallpaper) {
+                if (DEBUG_REORDER) Slog.v(TAG, "Found wallpaper @" + pos);
+                return pos + 1;
+            }
+        }
+        return 0;
+    }
+
+    private final int reAddWindowLocked(int index, WindowState win) {
+        final WindowList windows = win.getWindowList();
+        final int NCW = win.mChildWindows.size();
+        boolean added = false;
+        for (int j=0; j<NCW; j++) {
+            WindowState cwin = win.mChildWindows.get(j);
+            if (!added && cwin.mSubLayer >= 0) {
+                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding child window at "
+                        + index + ": " + cwin);
+                win.mRebuilding = false;
+                windows.add(index, win);
+                index++;
+                added = true;
+            }
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+                    + index + ": " + cwin);
+            cwin.mRebuilding = false;
+            windows.add(index, cwin);
+            index++;
+        }
+        if (!added) {
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Re-adding window at "
+                    + index + ": " + win);
+            win.mRebuilding = false;
+            windows.add(index, win);
+            index++;
+        }
+        mWindowsChanged = true;
+        return index;
+    }
+
+    private final int reAddAppWindowsLocked(final DisplayContent displayContent, int index,
+                                            WindowToken token) {
+        final int NW = token.windows.size();
+        for (int i=0; i<NW; i++) {
+            final WindowState win = token.windows.get(i);
+            if (win.mDisplayContent == displayContent) {
+                index = reAddWindowLocked(index, win);
+            }
+        }
+        return index;
+    }
+
+    void moveStackWindowsLocked(DisplayContent displayContent) {
+        // First remove all of the windows from the list.
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = numTokens - 1; tokenNdx >= 0; --tokenNdx) {
+                tmpRemoveAppWindowsLocked(tokens.get(tokenNdx));
+            }
+        }
+
+        // And now add them back at the correct place.
+        // Where to start adding?
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            int pos = findAppWindowInsertionPointLocked(tokens.get(0));
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                if (wtoken != null) {
+                    final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken);
+                    if (newPos != pos) {
+                        displayContent.layoutNeeded = true;
+                    }
+                    pos = newPos;
+                }
+            }
+        }
+
+        if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                false /*updateInputWindows*/)) {
+            assignLayersLocked(displayContent.getWindowList());
+        }
+
+        mInputMonitor.setUpdateInputWindowsNeededLw();
+        performLayoutAndPlaceSurfacesLocked();
+        mInputMonitor.updateInputWindowsLw(false /*force*/);
+
+        //dump();
+    }
+
+    public void moveTaskToTop(int taskId) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                Task task = mTaskIdToTask.get(taskId);
+                if (task == null) {
+                    // Normal behavior, addAppToken will be called next and task will be created.
+                    return;
+                }
+                final TaskStack stack = task.mStack;
+                final DisplayContent displayContent = task.getDisplayContent();
+                displayContent.moveStack(stack, true);
+                final TaskStack homeStack = displayContent.getHomeStack();
+                if (homeStack != stack) {
+                    // When a non-home stack moves to the top, the home stack moves to the
+                    // bottom.
+                    displayContent.moveStack(homeStack, false);
+                }
+                stack.moveTaskToTop(task);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    public void moveTaskToBottom(int taskId) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                Task task = mTaskIdToTask.get(taskId);
+                if (task == null) {
+                    Slog.e(TAG, "moveTaskToBottom: taskId=" + taskId
+                            + " not found in mTaskIdToTask");
+                    return;
+                }
+                final TaskStack stack = task.mStack;
+                stack.moveTaskToBottom(task);
+                moveStackWindowsLocked(stack.getDisplayContent());
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    /**
+     * Create a new TaskStack and place it next to an existing stack.
+     * @param stackId The unique identifier of the new stack.
+     */
+    public void createStack(int stackId, int displayId) {
+        synchronized (mWindowMap) {
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                if (displayContent.getDisplayId() == displayId) {
+                    TaskStack stack = displayContent.createStack(stackId);
+                    mStackIdToStack.put(stackId, stack);
+                    performLayoutAndPlaceSurfacesLocked();
+                    return;
+                }
+            }
+        }
+    }
+
+    public int removeStack(int stackId) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(stackId);
+            if (stack != null) {
+                mStackIdToStack.delete(stackId);
+                int nextStackId = stack.remove();
+                stack.getDisplayContent().layoutNeeded = true;
+                requestTraversalLocked();
+                if (nextStackId > HOME_STACK_ID) {
+                    return nextStackId;
+                }
+            }
+            if (DEBUG_STACK) Slog.i(TAG, "removeStack: could not find stackId=" + stackId);
+        }
+        return HOME_STACK_ID;
+    }
+
+    public void removeTask(int taskId) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId);
+                return;
+            }
+            final TaskStack stack = task.mStack;
+            EventLog.writeEvent(EventLogTags.WM_TASK_REMOVED, taskId, "removeTask");
+            stack.removeTask(task);
+            stack.getDisplayContent().layoutNeeded = true;
+        }
+    }
+
+    public void addTask(int taskId, int stackId, boolean toTop) {
+        synchronized (mWindowMap) {
+            Task task = mTaskIdToTask.get(taskId);
+            if (task == null) {
+                return;
+            }
+            TaskStack stack = mStackIdToStack.get(stackId);
+            stack.addTask(task, toTop);
+            final DisplayContent displayContent = stack.getDisplayContent();
+            displayContent.layoutNeeded = true;
+            performLayoutAndPlaceSurfacesLocked();
+        }
+    }
+
+    public void resizeStack(int stackId, Rect bounds) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(stackId);
+            if (stack == null) {
+                throw new IllegalArgumentException("resizeStack: stackId " + stackId
+                        + " not found.");
+            }
+            if (stack.setBounds(bounds)) {
+                stack.getDisplayContent().layoutNeeded = true;
+                performLayoutAndPlaceSurfacesLocked();
+            }
+        }
+    }
+
+    public void getStackBounds(int stackId, Rect bounds) {
+        final TaskStack stack = mStackIdToStack.get(stackId);
+        if (stack != null) {
+            stack.getBounds(bounds);
+            return;
+        }
+        bounds.setEmpty();
+    }
+
+    // -------------------------------------------------------------
+    // Misc IWindowSession methods
+    // -------------------------------------------------------------
+
+    @Override
+    public void startFreezingScreen(int exitAnim, int enterAnim) {
+        if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
+                "startFreezingScreen()")) {
+            throw new SecurityException("Requires FREEZE_SCREEN permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (!mClientFreezingScreen) {
+                mClientFreezingScreen = true;
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    startFreezingDisplayLocked(false, exitAnim, enterAnim);
+                    mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
+                    mH.sendEmptyMessageDelayed(H.CLIENT_FREEZE_TIMEOUT, 5000);
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void stopFreezingScreen() {
+        if (!checkCallingPermission(android.Manifest.permission.FREEZE_SCREEN,
+                "stopFreezingScreen()")) {
+            throw new SecurityException("Requires FREEZE_SCREEN permission");
+        }
+
+        synchronized(mWindowMap) {
+            if (mClientFreezingScreen) {
+                mClientFreezingScreen = false;
+                mLastFinishedFreezeSource = "client";
+                final long origId = Binder.clearCallingIdentity();
+                try {
+                    stopFreezingDisplayLocked();
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void disableKeyguard(IBinder token, String tag) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+
+        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
+                KeyguardDisableHandler.KEYGUARD_DISABLE, new Pair<IBinder, String>(token, tag)));
+    }
+
+    @Override
+    public void reenableKeyguard(IBinder token) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+
+        mKeyguardDisableHandler.sendMessage(mKeyguardDisableHandler.obtainMessage(
+                KeyguardDisableHandler.KEYGUARD_REENABLE, token));
+    }
+
+    /**
+     * @see android.app.KeyguardManager#exitKeyguardSecurely
+     */
+    @Override
+    public void exitKeyguardSecurely(final IOnKeyguardExitResult callback) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+            != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+        mPolicy.exitKeyguardSecurely(new WindowManagerPolicy.OnKeyguardExitResult() {
+            @Override
+            public void onKeyguardExitResult(boolean success) {
+                try {
+                    callback.onKeyguardExitResult(success);
+                } catch (RemoteException e) {
+                    // Client has died, we don't care.
+                }
+            }
+        });
+    }
+
+    @Override
+    public boolean inKeyguardRestrictedInputMode() {
+        return mPolicy.inKeyguardRestrictedKeyInputMode();
+    }
+
+    @Override
+    public boolean isKeyguardLocked() {
+        return mPolicy.isKeyguardLocked();
+    }
+
+    @Override
+    public boolean isKeyguardSecure() {
+        return mPolicy.isKeyguardSecure();
+    }
+
+    @Override
+    public void dismissKeyguard() {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DISABLE_KEYGUARD)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Requires DISABLE_KEYGUARD permission");
+        }
+        synchronized(mWindowMap) {
+            mPolicy.dismissKeyguardLw();
+        }
+    }
+
+    @Override
+    public void closeSystemDialogs(String reason) {
+        synchronized(mWindowMap) {
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                final int numWindows = windows.size();
+                for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                    final WindowState w = windows.get(winNdx);
+                    if (w.mHasSurface) {
+                        try {
+                            w.mClient.closeSystemDialogs(reason);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    static float fixScale(float scale) {
+        if (scale < 0) scale = 0;
+        else if (scale > 20) scale = 20;
+        return Math.abs(scale);
+    }
+
+    @Override
+    public void setAnimationScale(int which, float scale) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
+                "setAnimationScale()")) {
+            throw new SecurityException("Requires SET_ANIMATION_SCALE permission");
+        }
+
+        scale = fixScale(scale);
+        switch (which) {
+            case 0: mWindowAnimationScale = scale; break;
+            case 1: mTransitionAnimationScale = scale; break;
+            case 2: mAnimatorDurationScale = scale; break;
+        }
+
+        // Persist setting
+        mH.sendEmptyMessage(H.PERSIST_ANIMATION_SCALE);
+    }
+
+    @Override
+    public void setAnimationScales(float[] scales) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ANIMATION_SCALE,
+                "setAnimationScale()")) {
+            throw new SecurityException("Requires SET_ANIMATION_SCALE permission");
+        }
+
+        if (scales != null) {
+            if (scales.length >= 1) {
+                mWindowAnimationScale = fixScale(scales[0]);
+            }
+            if (scales.length >= 2) {
+                mTransitionAnimationScale = fixScale(scales[1]);
+            }
+            if (scales.length >= 3) {
+                setAnimatorDurationScale(fixScale(scales[2]));
+            }
+        }
+
+        // Persist setting
+        mH.sendEmptyMessage(H.PERSIST_ANIMATION_SCALE);
+    }
+
+    private void setAnimatorDurationScale(float scale) {
+        mAnimatorDurationScale = scale;
+        ValueAnimator.setDurationScale(scale);
+    }
+
+    @Override
+    public float getAnimationScale(int which) {
+        switch (which) {
+            case 0: return mWindowAnimationScale;
+            case 1: return mTransitionAnimationScale;
+            case 2: return mAnimatorDurationScale;
+        }
+        return 0;
+    }
+
+    @Override
+    public float[] getAnimationScales() {
+        return new float[] { mWindowAnimationScale, mTransitionAnimationScale,
+                mAnimatorDurationScale };
+    }
+
+    @Override
+    public void registerPointerEventListener(PointerEventListener listener) {
+        mPointerEventDispatcher.registerInputEventListener(listener);
+    }
+
+    @Override
+    public void unregisterPointerEventListener(PointerEventListener listener) {
+        mPointerEventDispatcher.unregisterInputEventListener(listener);
+    }
+
+    // Called by window manager policy. Not exposed externally.
+    @Override
+    public int getLidState() {
+        int sw = mInputManager.getSwitchState(-1, InputDevice.SOURCE_ANY,
+                InputManagerService.SW_LID);
+        if (sw > 0) {
+            // Switch state: AKEY_STATE_DOWN or AKEY_STATE_VIRTUAL.
+            return LID_CLOSED;
+        } else if (sw == 0) {
+            // Switch state: AKEY_STATE_UP.
+            return LID_OPEN;
+        } else {
+            // Switch state: AKEY_STATE_UNKNOWN.
+            return LID_ABSENT;
+        }
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void switchKeyboardLayout(int deviceId, int direction) {
+        mInputManager.switchKeyboardLayout(deviceId, direction);
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void shutdown(boolean confirm) {
+        ShutdownThread.shutdown(mContext, confirm);
+    }
+
+    // Called by window manager policy.  Not exposed externally.
+    @Override
+    public void rebootSafeMode(boolean confirm) {
+        ShutdownThread.rebootSafeMode(mContext, confirm);
+    }
+
+    @Override
+    public void setInputFilter(IInputFilter filter) {
+        if (!checkCallingPermission(android.Manifest.permission.FILTER_EVENTS, "setInputFilter()")) {
+            throw new SecurityException("Requires FILTER_EVENTS permission");
+        }
+        mInputManager.setInputFilter(filter);
+    }
+
+    @Override
+    public void setTouchExplorationEnabled(boolean enabled) {
+        mPolicy.setTouchExplorationEnabled(enabled);
+    }
+
+    public void setCurrentUser(final int newUserId) {
+        synchronized (mWindowMap) {
+            int oldUserId = mCurrentUserId;
+            mCurrentUserId = newUserId;
+            mAppTransition.setCurrentUser(newUserId);
+            mPolicy.setCurrentUserLw(newUserId);
+
+            // Hide windows that should not be seen by the new user.
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                displayContent.switchUserStacks(oldUserId, newUserId);
+                rebuildAppWindowListLocked(displayContent);
+            }
+            performLayoutAndPlaceSurfacesLocked();
+        }
+    }
+
+    public void enableScreenAfterBoot() {
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.i(TAG, "enableScreenAfterBoot: mDisplayEnabled=" + mDisplayEnabled
+                        + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                        + " mShowingBootMessages=" + mShowingBootMessages
+                        + " mSystemBooted=" + mSystemBooted, here);
+            }
+            if (mSystemBooted) {
+                return;
+            }
+            mSystemBooted = true;
+            hideBootMessagesLocked();
+            // If the screen still doesn't come up after 30 seconds, give
+            // up and turn it on.
+            mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30*1000);
+        }
+
+        mPolicy.systemBooted();
+
+        performEnableScreen();
+    }
+
+    void enableScreenIfNeededLocked() {
+        if (DEBUG_BOOT) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.i(TAG, "enableScreenIfNeededLocked: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted, here);
+        }
+        if (mDisplayEnabled) {
+            return;
+        }
+        if (!mSystemBooted && !mShowingBootMessages) {
+            return;
+        }
+        mH.sendEmptyMessage(H.ENABLE_SCREEN);
+    }
+
+    public void performBootTimeout() {
+        synchronized(mWindowMap) {
+            if (mDisplayEnabled) {
+                return;
+            }
+            Slog.w(TAG, "***** BOOT TIMEOUT: forcing display enabled");
+            mForceDisplayEnabled = true;
+        }
+        performEnableScreen();
+    }
+
+    public void performEnableScreen() {
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.i(TAG, "performEnableScreen: mDisplayEnabled=" + mDisplayEnabled
+                        + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                        + " mShowingBootMessages=" + mShowingBootMessages
+                        + " mSystemBooted=" + mSystemBooted
+                        + " mOnlyCore=" + mOnlyCore, here);
+            }
+            if (mDisplayEnabled) {
+                return;
+            }
+            if (!mSystemBooted && !mShowingBootMessages) {
+                return;
+            }
+
+            if (!mForceDisplayEnabled) {
+                // Don't enable the screen until all existing windows
+                // have been drawn.
+                boolean haveBootMsg = false;
+                boolean haveApp = false;
+                // if the wallpaper service is disabled on the device, we're never going to have
+                // wallpaper, don't bother waiting for it
+                boolean haveWallpaper = false;
+                boolean wallpaperEnabled = mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_enableWallpaperService)
+                        && !mOnlyCore;
+                boolean haveKeyguard = true;
+                // TODO(multidisplay): Expand to all displays?
+                final WindowList windows = getDefaultWindowListLocked();
+                final int N = windows.size();
+                for (int i=0; i<N; i++) {
+                    WindowState w = windows.get(i);
+                    if (w.mAttrs.type == TYPE_KEYGUARD) {
+                        // Only if there is a keyguard attached to the window manager
+                        // will we consider ourselves as having a keyguard.  If it
+                        // isn't attached, we don't know if it wants to be shown or
+                        // hidden.  If it is attached, we will say we have a keyguard
+                        // if the window doesn't want to be visible, because in that
+                        // case it explicitly doesn't want to be shown so we should
+                        // not delay turning the screen on for it.
+                        boolean vis = w.mViewVisibility == View.VISIBLE
+                                && w.mPolicyVisibility;
+                        haveKeyguard = !vis;
+                    }
+                    if (w.isVisibleLw() && !w.mObscured && !w.isDrawnLw()) {
+                        return;
+                    }
+                    if (w.isDrawnLw()) {
+                        if (w.mAttrs.type == TYPE_BOOT_PROGRESS) {
+                            haveBootMsg = true;
+                        } else if (w.mAttrs.type == TYPE_APPLICATION) {
+                            haveApp = true;
+                        } else if (w.mAttrs.type == TYPE_WALLPAPER) {
+                            haveWallpaper = true;
+                        } else if (w.mAttrs.type == TYPE_KEYGUARD) {
+                            haveKeyguard = true;
+                        }
+                    }
+                }
+
+                if (DEBUG_SCREEN_ON || DEBUG_BOOT) {
+                    Slog.i(TAG, "******** booted=" + mSystemBooted + " msg=" + mShowingBootMessages
+                            + " haveBoot=" + haveBootMsg + " haveApp=" + haveApp
+                            + " haveWall=" + haveWallpaper + " wallEnabled=" + wallpaperEnabled
+                            + " haveKeyguard=" + haveKeyguard);
+                }
+
+                // If we are turning on the screen to show the boot message,
+                // don't do it until the boot message is actually displayed.
+                if (!mSystemBooted && !haveBootMsg) {
+                    return;
+                }
+
+                // If we are turning on the screen after the boot is completed
+                // normally, don't do so until we have the application and
+                // wallpaper.
+                if (mSystemBooted && ((!haveApp && !haveKeyguard) ||
+                        (wallpaperEnabled && !haveWallpaper))) {
+                    return;
+                }
+            }
+
+            mDisplayEnabled = true;
+            if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG, "******************** ENABLING SCREEN!");
+            if (false) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+                this.dump(null, pw, null);
+                pw.flush();
+                Slog.i(TAG, sw.toString());
+            }
+            try {
+                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
+                if (surfaceFlinger != null) {
+                    //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
+                    Parcel data = Parcel.obtain();
+                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
+                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
+                                            data, null, 0);
+                    data.recycle();
+                }
+            } catch (RemoteException ex) {
+                Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");
+            }
+
+            // Enable input dispatch.
+            mInputMonitor.setEventDispatchingLw(mEventDispatchingEnabled);
+        }
+
+        mPolicy.enableScreenAfterBoot();
+
+        // Make sure the last requested orientation has been applied.
+        updateRotationUnchecked(false, false);
+    }
+
+    public void showBootMessage(final CharSequence msg, final boolean always) {
+        boolean first = false;
+        synchronized(mWindowMap) {
+            if (DEBUG_BOOT) {
+                RuntimeException here = new RuntimeException("here");
+                here.fillInStackTrace();
+                Slog.i(TAG, "showBootMessage: msg=" + msg + " always=" + always
+                        + " mAllowBootMessages=" + mAllowBootMessages
+                        + " mShowingBootMessages=" + mShowingBootMessages
+                        + " mSystemBooted=" + mSystemBooted, here);
+            }
+            if (!mAllowBootMessages) {
+                return;
+            }
+            if (!mShowingBootMessages) {
+                if (!always) {
+                    return;
+                }
+                first = true;
+            }
+            if (mSystemBooted) {
+                return;
+            }
+            mShowingBootMessages = true;
+            mPolicy.showBootMessage(msg, always);
+        }
+        if (first) {
+            performEnableScreen();
+        }
+    }
+
+    public void hideBootMessagesLocked() {
+        if (DEBUG_BOOT) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.i(TAG, "hideBootMessagesLocked: mDisplayEnabled=" + mDisplayEnabled
+                    + " mForceDisplayEnabled=" + mForceDisplayEnabled
+                    + " mShowingBootMessages=" + mShowingBootMessages
+                    + " mSystemBooted=" + mSystemBooted, here);
+        }
+        if (mShowingBootMessages) {
+            mShowingBootMessages = false;
+            mPolicy.hideBootMessages();
+        }
+    }
+
+    @Override
+    public void setInTouchMode(boolean mode) {
+        synchronized(mWindowMap) {
+            mInTouchMode = mode;
+        }
+    }
+
+    // TODO: more accounting of which pid(s) turned it on, keep count,
+    // only allow disables from pids which have count on, etc.
+    @Override
+    public void showStrictModeViolation(boolean on) {
+        int pid = Binder.getCallingPid();
+        mH.sendMessage(mH.obtainMessage(H.SHOW_STRICT_MODE_VIOLATION, on ? 1 : 0, pid));
+    }
+
+    private void showStrictModeViolation(int arg, int pid) {
+        final boolean on = arg != 0;
+        synchronized(mWindowMap) {
+            // Ignoring requests to enable the red border from clients
+            // which aren't on screen.  (e.g. Broadcast Receivers in
+            // the background..)
+            if (on) {
+                boolean isVisible = false;
+                final int numDisplays = mDisplayContents.size();
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                    final int numWindows = windows.size();
+                    for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                        final WindowState ws = windows.get(winNdx);
+                        if (ws.mSession.mPid == pid && ws.isVisibleLw()) {
+                            isVisible = true;
+                            break;
+                        }
+                    }
+                }
+                if (!isVisible) {
+                    return;
+                }
+            }
+
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION showStrictModeViolation");
+            SurfaceControl.openTransaction();
+            try {
+                // TODO(multi-display): support multiple displays
+                if (mStrictModeFlash == null) {
+                    mStrictModeFlash = new StrictModeFlash(
+                            getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+                }
+                mStrictModeFlash.setVisibility(on);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION showStrictModeViolation");
+            }
+        }
+    }
+
+    @Override
+    public void setStrictModeVisualIndicatorPreference(String value) {
+        SystemProperties.set(StrictMode.VISUAL_PROPERTY, value);
+    }
+
+    /**
+     * Takes a snapshot of the screen.  In landscape mode this grabs the whole screen.
+     * In portrait mode, it grabs the upper region of the screen based on the vertical dimension
+     * of the target image.
+     *
+     * @param displayId the Display to take a screenshot of.
+     * @param width the width of the target bitmap
+     * @param height the height of the target bitmap
+     * @param force565 if true the returned bitmap will be RGB_565, otherwise it
+     *                 will be the same config as the surface
+     */
+    @Override
+    public Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
+            int height, boolean force565) {
+        if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+                "screenshotApplications()")) {
+            throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+        }
+
+        Bitmap rawss = null;
+
+        int maxLayer = 0;
+        final Rect frame = new Rect();
+
+        float scale = 0;
+        int dw, dh;
+        int rot = Surface.ROTATION_0;
+
+        boolean screenshotReady;
+        int minLayer;
+        if (appToken == null) {
+            screenshotReady = true;
+            minLayer = 0;
+        } else {
+            screenshotReady = false;
+            minLayer = Integer.MAX_VALUE;
+        }
+
+        int retryCount = 0;
+        WindowState appWin = null;
+
+        do {
+            if (retryCount++ > 0) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent == null) {
+                    return null;
+                }
+                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                dw = displayInfo.logicalWidth;
+                dh = displayInfo.logicalHeight;
+
+                int aboveAppLayer = mPolicy.windowTypeToLayerLw(TYPE_APPLICATION)
+                        * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+                aboveAppLayer += TYPE_LAYER_MULTIPLIER;
+
+                boolean isImeTarget = mInputMethodTarget != null
+                        && mInputMethodTarget.mAppToken != null
+                        && mInputMethodTarget.mAppToken.appToken != null
+                        && mInputMethodTarget.mAppToken.appToken.asBinder() == appToken;
+
+                // Figure out the part of the screen that is actually the app.
+                boolean including = false;
+                appWin = null;
+                final WindowList windows = displayContent.getWindowList();
+                final Rect stackBounds = new Rect();
+                for (int i = windows.size() - 1; i >= 0; i--) {
+                    WindowState ws = windows.get(i);
+                    if (!ws.mHasSurface) {
+                        continue;
+                    }
+                    if (ws.mLayer >= aboveAppLayer) {
+                        continue;
+                    }
+                    // When we will skip windows: when we are not including
+                    // ones behind a window we didn't skip, and we are actually
+                    // taking a screenshot of a specific app.
+                    if (!including && appToken != null) {
+                        // Also, we can possibly skip this window if it is not
+                        // an IME target or the application for the screenshot
+                        // is not the current IME target.
+                        if (!ws.mIsImWindow || !isImeTarget) {
+                            // And finally, this window is of no interest if it
+                            // is not associated with the screenshot app.
+                            if (ws.mAppToken == null || ws.mAppToken.token != appToken) {
+                                continue;
+                            }
+                            appWin = ws;
+                            ws.getStackBounds(stackBounds);
+                        }
+                    }
+
+                    // We keep on including windows until we go past a full-screen
+                    // window.
+                    including = !ws.mIsImWindow && !ws.isFullscreen(dw, dh);
+
+                    final WindowStateAnimator winAnim = ws.mWinAnimator;
+                    if (maxLayer < winAnim.mSurfaceLayer) {
+                        maxLayer = winAnim.mSurfaceLayer;
+                    }
+                    if (minLayer > winAnim.mSurfaceLayer) {
+                        minLayer = winAnim.mSurfaceLayer;
+                    }
+
+                    // Don't include wallpaper in bounds calculation
+                    if (!ws.mIsWallpaper) {
+                        final Rect wf = ws.mFrame;
+                        final Rect cr = ws.mContentInsets;
+                        int left = wf.left + cr.left;
+                        int top = wf.top + cr.top;
+                        int right = wf.right - cr.right;
+                        int bottom = wf.bottom - cr.bottom;
+                        frame.union(left, top, right, bottom);
+                        frame.intersect(stackBounds);
+                    }
+
+                    if (ws.mAppToken != null && ws.mAppToken.token == appToken &&
+                            ws.isDisplayedLw()) {
+                        screenshotReady = true;
+                    }
+                }
+
+                if (appToken != null && appWin == null) {
+                    // Can't find a window to snapshot.
+                    if (DEBUG_SCREENSHOT) Slog.i(TAG,
+                            "Screenshot: Couldn't find a surface matching " + appToken);
+                    return null;
+                }
+                if (!screenshotReady) {
+                    // Delay and hope that window gets drawn.
+                    if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot: No image ready for " + appToken
+                            + ", " + appWin + " drawState=" + appWin.mWinAnimator.mDrawState);
+                    continue;
+                }
+
+                // Constrain frame to the screen size.
+                frame.intersect(0, 0, dw, dh);
+
+                if (frame.isEmpty() || maxLayer == 0) {
+                    if (DEBUG_SCREENSHOT) Slog.i(TAG, "Screenshot of " + appToken
+                            + ": returning null frame=" + frame.toShortString() + " maxLayer="
+                            + maxLayer);
+                    return null;
+                }
+
+                // The screenshot API does not apply the current screen rotation.
+                rot = getDefaultDisplayContentLocked().getDisplay().getRotation();
+                int fw = frame.width();
+                int fh = frame.height();
+
+                // Constrain thumbnail to smaller of screen width or height. Assumes aspect
+                // of thumbnail is the same as the screen (in landscape) or square.
+                scale = Math.max(width / (float) fw, height / (float) fh);
+                /*
+                float targetWidthScale = width / (float) fw;
+                float targetHeightScale = height / (float) fh;
+                if (fw <= fh) {
+                    scale = targetWidthScale;
+                    // If aspect of thumbnail is the same as the screen (in landscape),
+                    // select the slightly larger value so we fill the entire bitmap
+                    if (targetHeightScale > scale && (int) (targetHeightScale * fw) == width) {
+                        scale = targetHeightScale;
+                    }
+                } else {
+                    scale = targetHeightScale;
+                    // If aspect of thumbnail is the same as the screen (in landscape),
+                    // select the slightly larger value so we fill the entire bitmap
+                    if (targetWidthScale > scale && (int) (targetWidthScale * fh) == height) {
+                        scale = targetWidthScale;
+                    }
+                }
+                */
+
+                // The screen shot will contain the entire screen.
+                dw = (int)(dw*scale);
+                dh = (int)(dh*scale);
+                if (rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270) {
+                    int tmp = dw;
+                    dw = dh;
+                    dh = tmp;
+                    rot = (rot == Surface.ROTATION_90) ? Surface.ROTATION_270 : Surface.ROTATION_90;
+                }
+                if (DEBUG_SCREENSHOT) {
+                    Slog.i(TAG, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
+                            + maxLayer + " appToken=" + appToken);
+                    for (int i = 0; i < windows.size(); i++) {
+                        WindowState win = windows.get(i);
+                        Slog.i(TAG, win + ": " + win.mLayer
+                                + " animLayer=" + win.mWinAnimator.mAnimLayer
+                                + " surfaceLayer=" + win.mWinAnimator.mSurfaceLayer);
+                    }
+                }
+                rawss = SurfaceControl.screenshot(dw, dh, minLayer, maxLayer);
+            }
+        } while (!screenshotReady && retryCount <= MAX_SCREENSHOT_RETRIES);
+        if (retryCount > MAX_SCREENSHOT_RETRIES)  Slog.i(TAG, "Screenshot max retries " +
+                retryCount + " of " + appToken + " appWin=" + (appWin == null ?
+                        "null" : (appWin + " drawState=" + appWin.mWinAnimator.mDrawState)));
+
+        if (rawss == null) {
+            Slog.w(TAG, "Screenshot failure taking screenshot for (" + dw + "x" + dh
+                    + ") to layer " + maxLayer);
+            return null;
+        }
+
+        Bitmap bm = Bitmap.createBitmap(width, height, force565 ? Config.RGB_565 : rawss.getConfig());
+        frame.scale(scale);
+        Matrix matrix = new Matrix();
+        ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix);
+        // TODO: Test for RTL vs. LTR and use frame.right-width instead of -frame.left
+        matrix.postTranslate(-FloatMath.ceil(frame.left), -FloatMath.ceil(frame.top));
+        Canvas canvas = new Canvas(bm);
+        canvas.drawColor(0xFF000000);
+        canvas.drawBitmap(rawss, matrix, null);
+        canvas.setBitmap(null);
+
+        if (DEBUG_SCREENSHOT) {
+            // TEST IF IT's ALL BLACK
+            int[] buffer = new int[bm.getWidth() * bm.getHeight()];
+            bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight());
+            boolean allBlack = true;
+            final int firstColor = buffer[0];
+            for (int i = 0; i < buffer.length; i++) {
+                if (buffer[i] != firstColor) {
+                    allBlack = false;
+                    break;
+                }
+            }
+            if (allBlack) {
+                Slog.i(TAG, "Screenshot " + appWin + " was monochrome(" +
+                        Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
+                        (appWin != null ? appWin.mWinAnimator.mSurfaceLayer : "null") +
+                        " minLayer=" + minLayer + " maxLayer=" + maxLayer);
+            }
+        }
+
+        rawss.recycle();
+        return bm;
+    }
+
+    /**
+     * Freeze rotation changes.  (Enable "rotation lock".)
+     * Persists across reboots.
+     * @param rotation The desired rotation to freeze to, or -1 to use the
+     * current rotation.
+     */
+    @Override
+    public void freezeRotation(int rotation) {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "freezeRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+        if (rotation < -1 || rotation > Surface.ROTATION_270) {
+            throw new IllegalArgumentException("Rotation argument must be -1 or a valid "
+                    + "rotation constant.");
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG, "freezeRotation: mRotation=" + mRotation);
+
+        long origId = Binder.clearCallingIdentity();
+        try {
+            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_LOCKED,
+                    rotation == -1 ? mRotation : rotation);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        updateRotationUnchecked(false, false);
+    }
+
+    /**
+     * Thaw rotation changes.  (Disable "rotation lock".)
+     * Persists across reboots.
+     */
+    @Override
+    public void thawRotation() {
+        if (!checkCallingPermission(android.Manifest.permission.SET_ORIENTATION,
+                "thawRotation()")) {
+            throw new SecurityException("Requires SET_ORIENTATION permission");
+        }
+
+        if (DEBUG_ORIENTATION) Slog.v(TAG, "thawRotation: mRotation=" + mRotation);
+
+        long origId = Binder.clearCallingIdentity();
+        try {
+            mPolicy.setUserRotationMode(WindowManagerPolicy.USER_ROTATION_FREE,
+                    777); // rot not used
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        updateRotationUnchecked(false, false);
+    }
+
+    /**
+     * Recalculate the current rotation.
+     *
+     * Called by the window manager policy whenever the state of the system changes
+     * such that the current rotation might need to be updated, such as when the
+     * device is docked or rotated into a new posture.
+     */
+    @Override
+    public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
+        updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
+    }
+
+    /**
+     * Temporarily pauses rotation changes until resumed.
+     *
+     * This can be used to prevent rotation changes from occurring while the user is
+     * performing certain operations, such as drag and drop.
+     *
+     * This call nests and must be matched by an equal number of calls to
+     * {@link #resumeRotationLocked}.
+     */
+    void pauseRotationLocked() {
+        mDeferredRotationPauseCount += 1;
+    }
+
+    /**
+     * Resumes normal rotation changes after being paused.
+     */
+    void resumeRotationLocked() {
+        if (mDeferredRotationPauseCount > 0) {
+            mDeferredRotationPauseCount -= 1;
+            if (mDeferredRotationPauseCount == 0) {
+                boolean changed = updateRotationUncheckedLocked(false);
+                if (changed) {
+                    mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+                }
+            }
+        }
+    }
+
+    public void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
+        if(DEBUG_ORIENTATION) Slog.v(TAG, "updateRotationUnchecked("
+                   + "alwaysSendConfiguration=" + alwaysSendConfiguration + ")");
+
+        long origId = Binder.clearCallingIdentity();
+        boolean changed;
+        synchronized(mWindowMap) {
+            changed = updateRotationUncheckedLocked(false);
+            if (!changed || forceRelayout) {
+                getDefaultDisplayContentLocked().layoutNeeded = true;
+                performLayoutAndPlaceSurfacesLocked();
+            }
+        }
+
+        if (changed || alwaysSendConfiguration) {
+            sendNewConfiguration();
+        }
+
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    // TODO(multidisplay): Rotate any display?
+    /**
+     * Updates the current rotation.
+     *
+     * Returns true if the rotation has been changed.  In this case YOU
+     * MUST CALL sendNewConfiguration() TO UNFREEZE THE SCREEN.
+     */
+    public boolean updateRotationUncheckedLocked(boolean inTransaction) {
+        if (mDeferredRotationPauseCount > 0) {
+            // Rotation updates have been paused temporarily.  Defer the update until
+            // updates have been resumed.
+            if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, rotation is paused.");
+            return false;
+        }
+
+        ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+        if (screenRotationAnimation != null && screenRotationAnimation.isAnimating()) {
+            // Rotation updates cannot be performed while the previous rotation change
+            // animation is still in progress.  Skip this update.  We will try updating
+            // again after the animation is finished and the display is unfrozen.
+            if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, animation in progress.");
+            return false;
+        }
+
+        if (!mDisplayEnabled) {
+            // No point choosing a rotation if the display is not enabled.
+            if (DEBUG_ORIENTATION) Slog.v(TAG, "Deferring rotation, display is not enabled.");
+            return false;
+        }
+
+        // TODO: Implement forced rotation changes.
+        //       Set mAltOrientation to indicate that the application is receiving
+        //       an orientation that has different metrics than it expected.
+        //       eg. Portrait instead of Landscape.
+
+        int rotation = mPolicy.rotationForOrientationLw(mForcedAppOrientation, mRotation);
+        boolean altOrientation = !mPolicy.rotationHasCompatibleMetricsLw(
+                mForcedAppOrientation, rotation);
+
+        if (DEBUG_ORIENTATION) {
+            Slog.v(TAG, "Application requested orientation "
+                    + mForcedAppOrientation + ", got rotation " + rotation
+                    + " which has " + (altOrientation ? "incompatible" : "compatible")
+                    + " metrics");
+        }
+
+        if (mRotation == rotation && mAltOrientation == altOrientation) {
+            // No change.
+            return false;
+        }
+
+        if (DEBUG_ORIENTATION) {
+            Slog.v(TAG,
+                "Rotation changed to " + rotation + (altOrientation ? " (alt)" : "")
+                + " from " + mRotation + (mAltOrientation ? " (alt)" : "")
+                + ", forceApp=" + mForcedAppOrientation);
+        }
+
+        mRotation = rotation;
+        mAltOrientation = altOrientation;
+        mPolicy.setRotationLw(mRotation);
+
+        mWindowsFreezingScreen = true;
+        mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
+        mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION);
+        mWaitingForConfig = true;
+        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+        displayContent.layoutNeeded = true;
+        final int[] anim = new int[2];
+        if (displayContent.isDimming()) {
+            anim[0] = anim[1] = 0;
+        } else {
+            mPolicy.selectRotationAnimationLw(anim);
+        }
+        startFreezingDisplayLocked(inTransaction, anim[0], anim[1]);
+        // startFreezingDisplayLocked can reset the ScreenRotationAnimation.
+        screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(Display.DEFAULT_DISPLAY);
+
+        // We need to update our screen size information to match the new
+        // rotation.  Note that this is redundant with the later call to
+        // sendNewConfiguration() that must be called after this function
+        // returns...  however we need to do the screen size part of that
+        // before then so we have the correct size to use when initializing
+        // the rotation animation for the new rotation.
+        computeScreenConfigurationLocked(null);
+
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        if (!inTransaction) {
+            if (SHOW_TRANSACTIONS) {
+                Slog.i(TAG, ">>> OPEN TRANSACTION setRotationUnchecked");
+            }
+            SurfaceControl.openTransaction();
+        }
+        try {
+            // NOTE: We disable the rotation in the emulator because
+            //       it doesn't support hardware OpenGL emulation yet.
+            if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
+                    && screenRotationAnimation.hasScreenshot()) {
+                if (screenRotationAnimation.setRotationInTransaction(
+                        rotation, mFxSession,
+                        MAX_ANIMATION_DURATION, mTransitionAnimationScale,
+                        displayInfo.logicalWidth, displayInfo.logicalHeight)) {
+                    scheduleAnimationLocked();
+                }
+            }
+
+            mDisplayManagerService.performTraversalInTransactionFromWindowManager();
+        } finally {
+            if (!inTransaction) {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) {
+                    Slog.i(TAG, "<<< CLOSE TRANSACTION setRotationUnchecked");
+                }
+            }
+        }
+
+        final WindowList windows = displayContent.getWindowList();
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            WindowState w = windows.get(i);
+            if (w.mHasSurface) {
+                if (DEBUG_ORIENTATION) Slog.v(TAG, "Set mOrientationChanging of " + w);
+                w.mOrientationChanging = true;
+                mInnerFields.mOrientationChangeComplete = false;
+            }
+            w.mLastFreezeDuration = 0;
+        }
+
+        for (int i=mRotationWatchers.size()-1; i>=0; i--) {
+            try {
+                mRotationWatchers.get(i).onRotationChanged(rotation);
+            } catch (RemoteException e) {
+            }
+        }
+
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mDisplayMagnifier != null
+                && displayContent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            mDisplayMagnifier.onRotationChangedLocked(getDefaultDisplayContentLocked(), rotation);
+        }
+
+        return true;
+    }
+
+    @Override
+    public int getRotation() {
+        return mRotation;
+    }
+
+    @Override
+    public boolean isRotationFrozen() {
+        return mPolicy.getUserRotationMode() == WindowManagerPolicy.USER_ROTATION_LOCKED;
+    }
+
+    @Override
+    public int watchRotation(IRotationWatcher watcher) {
+        final IBinder watcherBinder = watcher.asBinder();
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                synchronized (mWindowMap) {
+                    for (int i=0; i<mRotationWatchers.size(); i++) {
+                        if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
+                            IRotationWatcher removed = mRotationWatchers.remove(i);
+                            if (removed != null) {
+                                removed.asBinder().unlinkToDeath(this, 0);
+                            }
+                            i--;
+                        }
+                    }
+                }
+            }
+        };
+
+        synchronized (mWindowMap) {
+            try {
+                watcher.asBinder().linkToDeath(dr, 0);
+                mRotationWatchers.add(watcher);
+            } catch (RemoteException e) {
+                // Client died, no cleanup needed.
+            }
+
+            return mRotation;
+        }
+    }
+
+    @Override
+    public void removeRotationWatcher(IRotationWatcher watcher) {
+        final IBinder watcherBinder = watcher.asBinder();
+        synchronized (mWindowMap) {
+            for (int i=0; i<mRotationWatchers.size(); i++) {
+                if (watcherBinder == mRotationWatchers.get(i).asBinder()) {
+                    mRotationWatchers.remove(i);
+                    i--;
+                }
+            }
+        }
+    }
+
+    /**
+     * Apps that use the compact menu panel (as controlled by the panelMenuIsCompact
+     * theme attribute) on devices that feature a physical options menu key attempt to position
+     * their menu panel window along the edge of the screen nearest the physical menu key.
+     * This lowers the travel distance between invoking the menu panel and selecting
+     * a menu option.
+     *
+     * This method helps control where that menu is placed. Its current implementation makes
+     * assumptions about the menu key and its relationship to the screen based on whether
+     * the device's natural orientation is portrait (width < height) or landscape.
+     *
+     * The menu key is assumed to be located along the bottom edge of natural-portrait
+     * devices and along the right edge of natural-landscape devices. If these assumptions
+     * do not hold for the target device, this method should be changed to reflect that.
+     *
+     * @return A {@link Gravity} value for placing the options menu window
+     */
+    @Override
+    public int getPreferredOptionsPanelGravity() {
+        synchronized (mWindowMap) {
+            final int rotation = getRotation();
+
+            // TODO(multidisplay): Assume that such devices physical keys are on the main screen.
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            if (displayContent.mInitialDisplayWidth < displayContent.mInitialDisplayHeight) {
+                // On devices with a natural orientation of portrait
+                switch (rotation) {
+                    default:
+                    case Surface.ROTATION_0:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_90:
+                        return Gravity.RIGHT | Gravity.BOTTOM;
+                    case Surface.ROTATION_180:
+                        return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                    case Surface.ROTATION_270:
+                        return Gravity.START | Gravity.BOTTOM;
+                }
+            }
+
+            // On devices with a natural orientation of landscape
+            switch (rotation) {
+                default:
+                case Surface.ROTATION_0:
+                    return Gravity.RIGHT | Gravity.BOTTOM;
+                case Surface.ROTATION_90:
+                    return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+                case Surface.ROTATION_180:
+                    return Gravity.START | Gravity.BOTTOM;
+                case Surface.ROTATION_270:
+                    return Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
+            }
+        }
+    }
+
+    /**
+     * Starts the view server on the specified port.
+     *
+     * @param port The port to listener to.
+     *
+     * @return True if the server was successfully started, false otherwise.
+     *
+     * @see com.android.server.wm.ViewServer
+     * @see com.android.server.wm.ViewServer#VIEW_SERVER_DEFAULT_PORT
+     */
+    @Override
+    public boolean startViewServer(int port) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "startViewServer")) {
+            return false;
+        }
+
+        if (port < 1024) {
+            return false;
+        }
+
+        if (mViewServer != null) {
+            if (!mViewServer.isRunning()) {
+                try {
+                    return mViewServer.start();
+                } catch (IOException e) {
+                    Slog.w(TAG, "View server did not start");
+                }
+            }
+            return false;
+        }
+
+        try {
+            mViewServer = new ViewServer(this, port);
+            return mViewServer.start();
+        } catch (IOException e) {
+            Slog.w(TAG, "View server did not start");
+        }
+        return false;
+    }
+
+    private boolean isSystemSecure() {
+        return "1".equals(SystemProperties.get(SYSTEM_SECURE, "1")) &&
+                "0".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0"));
+    }
+
+    /**
+     * Stops the view server if it exists.
+     *
+     * @return True if the server stopped, false if it wasn't started or
+     *         couldn't be stopped.
+     *
+     * @see com.android.server.wm.ViewServer
+     */
+    @Override
+    public boolean stopViewServer() {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "stopViewServer")) {
+            return false;
+        }
+
+        if (mViewServer != null) {
+            return mViewServer.stop();
+        }
+        return false;
+    }
+
+    /**
+     * Indicates whether the view server is running.
+     *
+     * @return True if the server is running, false otherwise.
+     *
+     * @see com.android.server.wm.ViewServer
+     */
+    @Override
+    public boolean isViewServerRunning() {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        if (!checkCallingPermission(Manifest.permission.DUMP, "isViewServerRunning")) {
+            return false;
+        }
+
+        return mViewServer != null && mViewServer.isRunning();
+    }
+
+    /**
+     * Lists all availble windows in the system. The listing is written in the
+     * specified Socket's output stream with the following syntax:
+     * windowHashCodeInHexadecimal windowName
+     * Each line of the ouput represents a different window.
+     *
+     * @param client The remote client to send the listing to.
+     * @return False if an error occured, true otherwise.
+     */
+    boolean viewServerListWindows(Socket client) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean result = true;
+
+        WindowList windows = new WindowList();
+        synchronized (mWindowMap) {
+            //noinspection unchecked
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                windows.addAll(displayContent.getWindowList());
+            }
+        }
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+            final int count = windows.size();
+            for (int i = 0; i < count; i++) {
+                final WindowState w = windows.get(i);
+                out.write(Integer.toHexString(System.identityHashCode(w)));
+                out.write(' ');
+                out.append(w.mAttrs.getTitle());
+                out.write('\n');
+            }
+
+            out.write("DONE.\n");
+            out.flush();
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    // TODO(multidisplay): Extend to multiple displays.
+    /**
+     * Returns the focused window in the following format:
+     * windowHashCodeInHexadecimal windowName
+     *
+     * @param client The remote client to send the listing to.
+     * @return False if an error occurred, true otherwise.
+     */
+    boolean viewServerGetFocusedWindow(Socket client) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean result = true;
+
+        WindowState focusedWindow = getFocusedWindow();
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            OutputStream clientStream = client.getOutputStream();
+            out = new BufferedWriter(new OutputStreamWriter(clientStream), 8 * 1024);
+
+            if(focusedWindow != null) {
+                out.write(Integer.toHexString(System.identityHashCode(focusedWindow)));
+                out.write(' ');
+                out.append(focusedWindow.mAttrs.getTitle());
+            }
+            out.write('\n');
+            out.flush();
+        } catch (Exception e) {
+            result = false;
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    result = false;
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends a command to a target window. The result of the command, if any, will be
+     * written in the output stream of the specified socket.
+     *
+     * The parameters must follow this syntax:
+     * windowHashcode extra
+     *
+     * Where XX is the length in characeters of the windowTitle.
+     *
+     * The first parameter is the target window. The window with the specified hashcode
+     * will be the target. If no target can be found, nothing happens. The extra parameters
+     * will be delivered to the target window and as parameters to the command itself.
+     *
+     * @param client The remote client to sent the result, if any, to.
+     * @param command The command to execute.
+     * @param parameters The command parameters.
+     *
+     * @return True if the command was successfully delivered, false otherwise. This does
+     *         not indicate whether the command itself was successful.
+     */
+    boolean viewServerWindowCommand(Socket client, String command, String parameters) {
+        if (isSystemSecure()) {
+            return false;
+        }
+
+        boolean success = true;
+        Parcel data = null;
+        Parcel reply = null;
+
+        BufferedWriter out = null;
+
+        // Any uncaught exception will crash the system process
+        try {
+            // Find the hashcode of the window
+            int index = parameters.indexOf(' ');
+            if (index == -1) {
+                index = parameters.length();
+            }
+            final String code = parameters.substring(0, index);
+            int hashCode = (int) Long.parseLong(code, 16);
+
+            // Extract the command's parameter after the window description
+            if (index < parameters.length()) {
+                parameters = parameters.substring(index + 1);
+            } else {
+                parameters = "";
+            }
+
+            final WindowState window = findWindow(hashCode);
+            if (window == null) {
+                return false;
+            }
+
+            data = Parcel.obtain();
+            data.writeInterfaceToken("android.view.IWindow");
+            data.writeString(command);
+            data.writeString(parameters);
+            data.writeInt(1);
+            ParcelFileDescriptor.fromSocket(client).writeToParcel(data, 0);
+
+            reply = Parcel.obtain();
+
+            final IBinder binder = window.mClient.asBinder();
+            // TODO: GET THE TRANSACTION CODE IN A SAFER MANNER
+            binder.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0);
+
+            reply.readException();
+
+            if (!client.isOutputShutdown()) {
+                out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
+                out.write("DONE\n");
+                out.flush();
+            }
+
+        } catch (Exception e) {
+            Slog.w(TAG, "Could not send command " + command + " with parameters " + parameters, e);
+            success = false;
+        } finally {
+            if (data != null) {
+                data.recycle();
+            }
+            if (reply != null) {
+                reply.recycle();
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+
+                }
+            }
+        }
+
+        return success;
+    }
+
+    public void addWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.add(listener);
+        }
+    }
+
+    public void removeWindowChangeListener(WindowChangeListener listener) {
+        synchronized(mWindowMap) {
+            mWindowChangeListeners.remove(listener);
+        }
+    }
+
+    private void notifyWindowsChanged() {
+        WindowChangeListener[] windowChangeListeners;
+        synchronized(mWindowMap) {
+            if(mWindowChangeListeners.isEmpty()) {
+                return;
+            }
+            windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
+            windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+        }
+        int N = windowChangeListeners.length;
+        for(int i = 0; i < N; i++) {
+            windowChangeListeners[i].windowsChanged();
+        }
+    }
+
+    private void notifyFocusChanged() {
+        WindowChangeListener[] windowChangeListeners;
+        synchronized(mWindowMap) {
+            if(mWindowChangeListeners.isEmpty()) {
+                return;
+            }
+            windowChangeListeners = new WindowChangeListener[mWindowChangeListeners.size()];
+            windowChangeListeners = mWindowChangeListeners.toArray(windowChangeListeners);
+        }
+        int N = windowChangeListeners.length;
+        for(int i = 0; i < N; i++) {
+            windowChangeListeners[i].focusChanged();
+        }
+    }
+
+    private WindowState findWindow(int hashCode) {
+        if (hashCode == -1) {
+            // TODO(multidisplay): Extend to multiple displays.
+            return getFocusedWindow();
+        }
+
+        synchronized (mWindowMap) {
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                final int numWindows = windows.size();
+                for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                    final WindowState w = windows.get(winNdx);
+                    if (System.identityHashCode(w) == hashCode) {
+                        return w;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /*
+     * Instruct the Activity Manager to fetch the current configuration and broadcast
+     * that to config-changed listeners if appropriate.
+     */
+    void sendNewConfiguration() {
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+    }
+
+    public Configuration computeNewConfiguration() {
+        synchronized (mWindowMap) {
+            Configuration config = computeNewConfigurationLocked();
+            if (config == null && mWaitingForConfig) {
+                // Nothing changed but we are waiting for something... stop that!
+                mWaitingForConfig = false;
+                mLastFinishedFreezeSource = "new-config";
+                performLayoutAndPlaceSurfacesLocked();
+            }
+            return config;
+        }
+    }
+
+    Configuration computeNewConfigurationLocked() {
+        Configuration config = new Configuration();
+        config.fontScale = 0;
+        if (!computeScreenConfigurationLocked(config)) {
+            return null;
+        }
+        return config;
+    }
+
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation, int dw, int dh) {
+        // TODO: Multidisplay: for now only use with default display.
+        final int width = mPolicy.getConfigDisplayWidth(dw, dh, rotation);
+        if (width < displayInfo.smallestNominalAppWidth) {
+            displayInfo.smallestNominalAppWidth = width;
+        }
+        if (width > displayInfo.largestNominalAppWidth) {
+            displayInfo.largestNominalAppWidth = width;
+        }
+        final int height = mPolicy.getConfigDisplayHeight(dw, dh, rotation);
+        if (height < displayInfo.smallestNominalAppHeight) {
+            displayInfo.smallestNominalAppHeight = height;
+        }
+        if (height > displayInfo.largestNominalAppHeight) {
+            displayInfo.largestNominalAppHeight = height;
+        }
+    }
+
+    private int reduceConfigLayout(int curLayout, int rotation, float density,
+            int dw, int dh) {
+        // TODO: Multidisplay: for now only use with default display.
+        // Get the app screen size at this rotation.
+        int w = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation);
+        int h = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation);
+
+        // Compute the screen layout size class for this rotation.
+        int longSize = w;
+        int shortSize = h;
+        if (longSize < shortSize) {
+            int tmp = longSize;
+            longSize = shortSize;
+            shortSize = tmp;
+        }
+        longSize = (int)(longSize/density);
+        shortSize = (int)(shortSize/density);
+        return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
+    }
+
+    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated,
+                  int dw, int dh, float density, Configuration outConfig) {
+        // TODO: Multidisplay: for now only use with default display.
+
+        // We need to determine the smallest width that will occur under normal
+        // operation.  To this, start with the base screen size and compute the
+        // width under the different possible rotations.  We need to un-rotate
+        // the current screen dimensions before doing this.
+        int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        displayInfo.smallestNominalAppWidth = 1<<30;
+        displayInfo.smallestNominalAppHeight = 1<<30;
+        displayInfo.largestNominalAppWidth = 0;
+        displayInfo.largestNominalAppHeight = 0;
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, unrotDh, unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, unrotDh, unrotDw);
+        int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh);
+        sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw);
+        outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
+        outConfig.screenLayout = sl;
+    }
+
+    private int reduceCompatConfigWidthSize(int curSize, int rotation, DisplayMetrics dm,
+            int dw, int dh) {
+        // TODO: Multidisplay: for now only use with default display.
+        dm.noncompatWidthPixels = mPolicy.getNonDecorDisplayWidth(dw, dh, rotation);
+        dm.noncompatHeightPixels = mPolicy.getNonDecorDisplayHeight(dw, dh, rotation);
+        float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
+        int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
+        if (curSize == 0 || size < curSize) {
+            curSize = size;
+        }
+        return curSize;
+    }
+
+    private int computeCompatSmallestWidth(boolean rotated, DisplayMetrics dm, int dw, int dh) {
+        // TODO: Multidisplay: for now only use with default display.
+        mTmpDisplayMetrics.setTo(dm);
+        final DisplayMetrics tmpDm = mTmpDisplayMetrics;
+        final int unrotDw, unrotDh;
+        if (rotated) {
+            unrotDw = dh;
+            unrotDh = dw;
+        } else {
+            unrotDw = dw;
+            unrotDh = dh;
+        }
+        int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, tmpDm, unrotDw, unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, tmpDm, unrotDh, unrotDw);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, tmpDm, unrotDw, unrotDh);
+        sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, tmpDm, unrotDh, unrotDw);
+        return sw;
+    }
+
+    boolean computeScreenConfigurationLocked(Configuration config) {
+        if (!mDisplayReady) {
+            return false;
+        }
+
+        // TODO(multidisplay): For now, apply Configuration to main screen only.
+        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+
+        // Use the effective "visual" dimensions based on current rotation
+        final boolean rotated = (mRotation == Surface.ROTATION_90
+                || mRotation == Surface.ROTATION_270);
+        final int realdw = rotated ?
+                displayContent.mBaseDisplayHeight : displayContent.mBaseDisplayWidth;
+        final int realdh = rotated ?
+                displayContent.mBaseDisplayWidth : displayContent.mBaseDisplayHeight;
+        int dw = realdw;
+        int dh = realdh;
+
+        if (mAltOrientation) {
+            if (realdw > realdh) {
+                // Turn landscape into portrait.
+                int maxw = (int)(realdh/1.3f);
+                if (maxw < realdw) {
+                    dw = maxw;
+                }
+            } else {
+                // Turn portrait into landscape.
+                int maxh = (int)(realdw/1.3f);
+                if (maxh < realdh) {
+                    dh = maxh;
+                }
+            }
+        }
+
+        if (config != null) {
+            config.orientation = (dw <= dh) ? Configuration.ORIENTATION_PORTRAIT :
+                    Configuration.ORIENTATION_LANDSCAPE;
+        }
+
+        // Update application display metrics.
+        final int appWidth = mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation);
+        final int appHeight = mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation);
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        synchronized(displayContent.mDisplaySizeLock) {
+            displayInfo.rotation = mRotation;
+            displayInfo.logicalWidth = dw;
+            displayInfo.logicalHeight = dh;
+            displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity;
+            displayInfo.appWidth = appWidth;
+            displayInfo.appHeight = appHeight;
+            displayInfo.getLogicalMetrics(mRealDisplayMetrics,
+                    CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+            displayInfo.getAppMetrics(mDisplayMetrics);
+            mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
+                    displayContent.getDisplayId(), displayInfo);
+        }
+        if (false) {
+            Slog.i(TAG, "Set app display size: " + appWidth + " x " + appHeight);
+        }
+
+        final DisplayMetrics dm = mDisplayMetrics;
+        mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(dm,
+                mCompatDisplayMetrics);
+
+        if (config != null) {
+            config.screenWidthDp = (int)(mPolicy.getConfigDisplayWidth(dw, dh, mRotation)
+                    / dm.density);
+            config.screenHeightDp = (int)(mPolicy.getConfigDisplayHeight(dw, dh, mRotation)
+                    / dm.density);
+            computeSizeRangesAndScreenLayout(displayInfo, rotated, dw, dh, dm.density, config);
+
+            config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
+            config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
+            config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, dm, dw, dh);
+            config.densityDpi = displayContent.mBaseDisplayDensity;
+
+            // Update the configuration based on available input devices, lid switch,
+            // and platform configuration.
+            config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+            config.keyboard = Configuration.KEYBOARD_NOKEYS;
+            config.navigation = Configuration.NAVIGATION_NONAV;
+
+            int keyboardPresence = 0;
+            int navigationPresence = 0;
+            final InputDevice[] devices = mInputManager.getInputDevices();
+            final int len = devices.length;
+            for (int i = 0; i < len; i++) {
+                InputDevice device = devices[i];
+                if (!device.isVirtual()) {
+                    final int sources = device.getSources();
+                    final int presenceFlag = device.isExternal() ?
+                            WindowManagerPolicy.PRESENCE_EXTERNAL :
+                                    WindowManagerPolicy.PRESENCE_INTERNAL;
+
+                    if (mIsTouchDevice) {
+                        if ((sources & InputDevice.SOURCE_TOUCHSCREEN) ==
+                                InputDevice.SOURCE_TOUCHSCREEN) {
+                            config.touchscreen = Configuration.TOUCHSCREEN_FINGER;
+                        }
+                    } else {
+                        config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
+                    }
+
+                    if ((sources & InputDevice.SOURCE_TRACKBALL) == InputDevice.SOURCE_TRACKBALL) {
+                        config.navigation = Configuration.NAVIGATION_TRACKBALL;
+                        navigationPresence |= presenceFlag;
+                    } else if ((sources & InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
+                            && config.navigation == Configuration.NAVIGATION_NONAV) {
+                        config.navigation = Configuration.NAVIGATION_DPAD;
+                        navigationPresence |= presenceFlag;
+                    }
+
+                    if (device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) {
+                        config.keyboard = Configuration.KEYBOARD_QWERTY;
+                        keyboardPresence |= presenceFlag;
+                    }
+                }
+            }
+
+            // Determine whether a hard keyboard is available and enabled.
+            boolean hardKeyboardAvailable = config.keyboard != Configuration.KEYBOARD_NOKEYS;
+            if (hardKeyboardAvailable != mHardKeyboardAvailable) {
+                mHardKeyboardAvailable = hardKeyboardAvailable;
+                mHardKeyboardEnabled = hardKeyboardAvailable;
+                mH.removeMessages(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+                mH.sendEmptyMessage(H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
+            }
+            if (!mHardKeyboardEnabled) {
+                config.keyboard = Configuration.KEYBOARD_NOKEYS;
+            }
+
+            // Let the policy update hidden states.
+            config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
+            config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
+            config.navigationHidden = Configuration.NAVIGATIONHIDDEN_NO;
+            mPolicy.adjustConfigurationLw(config, keyboardPresence, navigationPresence);
+        }
+
+        return true;
+    }
+
+    public boolean isHardKeyboardAvailable() {
+        synchronized (mWindowMap) {
+            return mHardKeyboardAvailable;
+        }
+    }
+
+    public boolean isHardKeyboardEnabled() {
+        synchronized (mWindowMap) {
+            return mHardKeyboardEnabled;
+        }
+    }
+
+    public void setHardKeyboardEnabled(boolean enabled) {
+        synchronized (mWindowMap) {
+            if (mHardKeyboardEnabled != enabled) {
+                mHardKeyboardEnabled = enabled;
+                mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+            }
+        }
+    }
+
+    public void setOnHardKeyboardStatusChangeListener(
+            OnHardKeyboardStatusChangeListener listener) {
+        synchronized (mWindowMap) {
+            mHardKeyboardStatusChangeListener = listener;
+        }
+    }
+
+    void notifyHardKeyboardStatusChange() {
+        final boolean available, enabled;
+        final OnHardKeyboardStatusChangeListener listener;
+        synchronized (mWindowMap) {
+            listener = mHardKeyboardStatusChangeListener;
+            available = mHardKeyboardAvailable;
+            enabled = mHardKeyboardEnabled;
+        }
+        if (listener != null) {
+            listener.onHardKeyboardStatusChange(available, enabled);
+        }
+    }
+
+    // -------------------------------------------------------------
+    // Drag and drop
+    // -------------------------------------------------------------
+
+    IBinder prepareDragSurface(IWindow window, SurfaceSession session,
+            int flags, int width, int height, Surface outSurface) {
+        if (DEBUG_DRAG) {
+            Slog.d(TAG, "prepare drag surface: w=" + width + " h=" + height
+                    + " flags=" + Integer.toHexString(flags) + " win=" + window
+                    + " asbinder=" + window.asBinder());
+        }
+
+        final int callerPid = Binder.getCallingPid();
+        final long origId = Binder.clearCallingIdentity();
+        IBinder token = null;
+
+        try {
+            synchronized (mWindowMap) {
+                try {
+                    if (mDragState == null) {
+                        // TODO(multi-display): support other displays
+                        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                        final Display display = displayContent.getDisplay();
+                        SurfaceControl surface = new SurfaceControl(session, "drag surface",
+                                width, height, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+                        surface.setLayerStack(display.getLayerStack());
+                        if (SHOW_TRANSACTIONS) Slog.i(TAG, "  DRAG "
+                                + surface + ": CREATE");
+                        outSurface.copyFrom(surface);
+                        final IBinder winBinder = window.asBinder();
+                        token = new Binder();
+                        mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder);
+                        token = mDragState.mToken = new Binder();
+
+                        // 5 second timeout for this window to actually begin the drag
+                        mH.removeMessages(H.DRAG_START_TIMEOUT, winBinder);
+                        Message msg = mH.obtainMessage(H.DRAG_START_TIMEOUT, winBinder);
+                        mH.sendMessageDelayed(msg, 5000);
+                    } else {
+                        Slog.w(TAG, "Drag already in progress");
+                    }
+                } catch (OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e);
+                    if (mDragState != null) {
+                        mDragState.reset();
+                        mDragState = null;
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+
+        return token;
+    }
+
+    // -------------------------------------------------------------
+    // Input Events and Focus Management
+    // -------------------------------------------------------------
+
+    final InputMonitor mInputMonitor = new InputMonitor(this);
+    private boolean mEventDispatchingEnabled;
+
+    @Override
+    public void pauseKeyDispatching(IBinder _token) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "pauseKeyDispatching()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized (mWindowMap) {
+            WindowToken token = mTokenMap.get(_token);
+            if (token != null) {
+                mInputMonitor.pauseDispatchingLw(token);
+            }
+        }
+    }
+
+    @Override
+    public void resumeKeyDispatching(IBinder _token) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "resumeKeyDispatching()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized (mWindowMap) {
+            WindowToken token = mTokenMap.get(_token);
+            if (token != null) {
+                mInputMonitor.resumeDispatchingLw(token);
+            }
+        }
+    }
+
+    @Override
+    public void setEventDispatching(boolean enabled) {
+        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
+                "setEventDispatching()")) {
+            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
+        }
+
+        synchronized (mWindowMap) {
+            mEventDispatchingEnabled = enabled;
+            if (mDisplayEnabled) {
+                mInputMonitor.setEventDispatchingLw(enabled);
+            }
+            sendScreenStatusToClientsLocked();
+        }
+    }
+
+    @Override
+    public IBinder getFocusedWindowToken() {
+        if (!checkCallingPermission(android.Manifest.permission.RETRIEVE_WINDOW_INFO,
+                "getFocusedWindowToken()")) {
+            throw new SecurityException("Requires RETRIEVE_WINDOW_INFO permission.");
+        }
+        synchronized (mWindowMap) {
+            WindowState windowState = getFocusedWindowLocked();
+            if (windowState != null) {
+                return windowState.mClient.asBinder();
+            }
+            return null;
+        }
+    }
+
+    private WindowState getFocusedWindow() {
+        synchronized (mWindowMap) {
+            return getFocusedWindowLocked();
+        }
+    }
+
+    private WindowState getFocusedWindowLocked() {
+        return mCurrentFocus;
+    }
+
+    public boolean detectSafeMode() {
+        if (!mInputMonitor.waitForInputDevicesReady(
+                INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
+            Slog.w(TAG, "Devices still not ready after waiting "
+                   + INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS
+                   + " milliseconds before attempting to detect safe mode.");
+        }
+
+        int menuState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
+                KeyEvent.KEYCODE_MENU);
+        int sState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY, KeyEvent.KEYCODE_S);
+        int dpadState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_DPAD,
+                KeyEvent.KEYCODE_DPAD_CENTER);
+        int trackballState = mInputManager.getScanCodeState(-1, InputDevice.SOURCE_TRACKBALL,
+                InputManagerService.BTN_MOUSE);
+        int volumeDownState = mInputManager.getKeyCodeState(-1, InputDevice.SOURCE_ANY,
+                KeyEvent.KEYCODE_VOLUME_DOWN);
+        mSafeMode = menuState > 0 || sState > 0 || dpadState > 0 || trackballState > 0
+                || volumeDownState > 0;
+        try {
+            if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0) {
+                mSafeMode = true;
+                SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
+            }
+        } catch (IllegalArgumentException e) {
+        }
+        if (mSafeMode) {
+            Log.i(TAG, "SAFE MODE ENABLED (menu=" + menuState + " s=" + sState
+                    + " dpad=" + dpadState + " trackball=" + trackballState + ")");
+        } else {
+            Log.i(TAG, "SAFE MODE not enabled");
+        }
+        mPolicy.setSafeMode(mSafeMode);
+        return mSafeMode;
+    }
+
+    public void displayReady() {
+        displayReady(Display.DEFAULT_DISPLAY);
+
+        synchronized(mWindowMap) {
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            readForcedDisplaySizeAndDensityLocked(displayContent);
+            mDisplayReady = true;
+        }
+
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+
+        synchronized(mWindowMap) {
+            mIsTouchDevice = mContext.getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_TOUCHSCREEN);
+            configureDisplayPolicyLocked(getDefaultDisplayContentLocked());
+        }
+
+        try {
+            mActivityManager.updateConfiguration(null);
+        } catch (RemoteException e) {
+        }
+    }
+
+    private void displayReady(int displayId) {
+        synchronized(mWindowMap) {
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null) {
+                mAnimator.addDisplayLocked(displayId);
+                synchronized(displayContent.mDisplaySizeLock) {
+                    // Bootstrap the default logical display from the display manager.
+                    final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                    DisplayInfo newDisplayInfo = mDisplayManagerService.getDisplayInfo(displayId);
+                    if (newDisplayInfo != null) {
+                        displayInfo.copyFrom(newDisplayInfo);
+                    }
+                    displayContent.mInitialDisplayWidth = displayInfo.logicalWidth;
+                    displayContent.mInitialDisplayHeight = displayInfo.logicalHeight;
+                    displayContent.mInitialDisplayDensity = displayInfo.logicalDensityDpi;
+                    displayContent.mBaseDisplayWidth = displayContent.mInitialDisplayWidth;
+                    displayContent.mBaseDisplayHeight = displayContent.mInitialDisplayHeight;
+                    displayContent.mBaseDisplayDensity = displayContent.mInitialDisplayDensity;
+                    displayContent.mBaseDisplayRect.set(0, 0,
+                            displayContent.mBaseDisplayWidth, displayContent.mBaseDisplayHeight);
+                }
+            }
+        }
+    }
+
+    public void systemReady() {
+        mPolicy.systemReady();
+    }
+
+    // TODO(multidisplay): Call isScreenOn for each display.
+    private void sendScreenStatusToClientsLocked() {
+        final boolean on = mPowerManager.isScreenOn();
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+            final int numWindows = windows.size();
+            for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                try {
+                    windows.get(winNdx).mClient.dispatchScreenState(on);
+                } catch (RemoteException e) {
+                    // Ignored
+                }
+            }
+        }
+    }
+
+    // -------------------------------------------------------------
+    // Async Handler
+    // -------------------------------------------------------------
+
+    final class H extends Handler {
+        public static final int REPORT_FOCUS_CHANGE = 2;
+        public static final int REPORT_LOSING_FOCUS = 3;
+        public static final int DO_TRAVERSAL = 4;
+        public static final int ADD_STARTING = 5;
+        public static final int REMOVE_STARTING = 6;
+        public static final int FINISHED_STARTING = 7;
+        public static final int REPORT_APPLICATION_TOKEN_WINDOWS = 8;
+        public static final int REPORT_APPLICATION_TOKEN_DRAWN = 9;
+        public static final int WINDOW_FREEZE_TIMEOUT = 11;
+
+        public static final int APP_TRANSITION_TIMEOUT = 13;
+        public static final int PERSIST_ANIMATION_SCALE = 14;
+        public static final int FORCE_GC = 15;
+        public static final int ENABLE_SCREEN = 16;
+        public static final int APP_FREEZE_TIMEOUT = 17;
+        public static final int SEND_NEW_CONFIGURATION = 18;
+        public static final int REPORT_WINDOWS_CHANGE = 19;
+        public static final int DRAG_START_TIMEOUT = 20;
+        public static final int DRAG_END_TIMEOUT = 21;
+        public static final int REPORT_HARD_KEYBOARD_STATUS_CHANGE = 22;
+        public static final int BOOT_TIMEOUT = 23;
+        public static final int WAITING_FOR_DRAWN_TIMEOUT = 24;
+        public static final int SHOW_STRICT_MODE_VIOLATION = 25;
+        public static final int DO_ANIMATION_CALLBACK = 26;
+
+        public static final int DO_DISPLAY_ADDED = 27;
+        public static final int DO_DISPLAY_REMOVED = 28;
+        public static final int DO_DISPLAY_CHANGED = 29;
+
+        public static final int CLIENT_FREEZE_TIMEOUT = 30;
+        public static final int TAP_OUTSIDE_STACK = 31;
+        public static final int NOTIFY_ACTIVITY_DRAWN = 32;
+
+        public static final int REMOVE_STARTING_TIMEOUT = 33;
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.v(TAG, "handleMessage: entry what=" + msg.what);
+            }
+            switch (msg.what) {
+                case REPORT_FOCUS_CHANGE: {
+                    WindowState lastFocus;
+                    WindowState newFocus;
+
+                    synchronized(mWindowMap) {
+                        lastFocus = mLastFocus;
+                        newFocus = mCurrentFocus;
+                        if (lastFocus == newFocus) {
+                            // Focus is not changing, so nothing to do.
+                            return;
+                        }
+                        mLastFocus = newFocus;
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Focus moving from " + lastFocus +
+                                " to " + newFocus);
+                        if (newFocus != null && lastFocus != null
+                                && !newFocus.isDisplayedLw()) {
+                            //Slog.i(TAG, "Delaying loss of focus...");
+                            mLosingFocus.add(lastFocus);
+                            lastFocus = null;
+                        }
+                    }
+
+                    //System.out.println("Changing focus from " + lastFocus
+                    //                   + " to " + newFocus);
+                    if (newFocus != null) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Gaining focus: " + newFocus);
+                        newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+                        notifyFocusChanged();
+                    }
+
+                    if (lastFocus != null) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Losing focus: " + lastFocus);
+                        lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+                    }
+                } break;
+
+                case REPORT_LOSING_FOCUS: {
+                    ArrayList<WindowState> losers;
+
+                    synchronized(mWindowMap) {
+                        losers = mLosingFocus;
+                        mLosingFocus = new ArrayList<WindowState>();
+                    }
+
+                    final int N = losers.size();
+                    for (int i=0; i<N; i++) {
+                        if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Losing delayed focus: " +
+                                losers.get(i));
+                        losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+                    }
+                } break;
+
+                case DO_TRAVERSAL: {
+                    synchronized(mWindowMap) {
+                        mTraversalScheduled = false;
+                        performLayoutAndPlaceSurfacesLocked();
+                    }
+                } break;
+
+                case ADD_STARTING: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+                    final StartingData sd = wtoken.startingData;
+
+                    if (sd == null) {
+                        // Animation has been canceled... do nothing.
+                        return;
+                    }
+
+                    if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Add starting "
+                            + wtoken + ": pkg=" + sd.pkg);
+
+                    View view = null;
+                    try {
+                        view = mPolicy.addStartingWindow(
+                            wtoken.token, sd.pkg, sd.theme, sd.compatInfo,
+                            sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags);
+                    } catch (Exception e) {
+                        Slog.w(TAG, "Exception when adding starting window", e);
+                    }
+
+                    if (view != null) {
+                        boolean abort = false;
+
+                        synchronized(mWindowMap) {
+                            if (wtoken.removed || wtoken.startingData == null) {
+                                // If the window was successfully added, then
+                                // we need to remove it.
+                                if (wtoken.startingWindow != null) {
+                                    if (DEBUG_STARTING_WINDOW) Slog.v(TAG,
+                                            "Aborted starting " + wtoken
+                                            + ": removed=" + wtoken.removed
+                                            + " startingData=" + wtoken.startingData);
+                                    removeStartingWindowTimeout(wtoken);
+                                    wtoken.startingWindow = null;
+                                    wtoken.startingData = null;
+                                    abort = true;
+                                }
+                            } else {
+                                wtoken.startingView = view;
+                            }
+                            if (DEBUG_STARTING_WINDOW && !abort) Slog.v(TAG,
+                                    "Added starting " + wtoken
+                                    + ": startingWindow="
+                                    + wtoken.startingWindow + " startingView="
+                                    + wtoken.startingView);
+                        }
+
+                        if (abort) {
+                            try {
+                                mPolicy.removeStartingWindow(wtoken.token, view);
+                            } catch (Exception e) {
+                                Slog.w(TAG, "Exception when removing starting window", e);
+                            }
+                        }
+                    }
+                } break;
+
+                case REMOVE_STARTING_TIMEOUT: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+                    Slog.e(TAG, "Starting window " + wtoken + " timed out");
+                    // Fall through.
+                }
+                case REMOVE_STARTING: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+                    IBinder token = null;
+                    View view = null;
+                    synchronized (mWindowMap) {
+                        if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Remove starting "
+                                + wtoken + ": startingWindow="
+                                + wtoken.startingWindow + " startingView="
+                                + wtoken.startingView);
+                        if (wtoken.startingWindow != null) {
+                            view = wtoken.startingView;
+                            token = wtoken.token;
+                            wtoken.startingData = null;
+                            wtoken.startingView = null;
+                            wtoken.startingWindow = null;
+                            wtoken.startingDisplayed = false;
+                        }
+                    }
+                    if (view != null) {
+                        try {
+                            mPolicy.removeStartingWindow(token, view);
+                        } catch (Exception e) {
+                            Slog.w(TAG, "Exception when removing starting window", e);
+                        }
+                    }
+                } break;
+
+                case FINISHED_STARTING: {
+                    IBinder token = null;
+                    View view = null;
+                    while (true) {
+                        synchronized (mWindowMap) {
+                            final int N = mFinishedStarting.size();
+                            if (N <= 0) {
+                                break;
+                            }
+                            AppWindowToken wtoken = mFinishedStarting.remove(N-1);
+
+                            if (DEBUG_STARTING_WINDOW) Slog.v(TAG,
+                                    "Finished starting " + wtoken
+                                    + ": startingWindow=" + wtoken.startingWindow
+                                    + " startingView=" + wtoken.startingView);
+
+                            if (wtoken.startingWindow == null) {
+                                continue;
+                            }
+
+                            view = wtoken.startingView;
+                            token = wtoken.token;
+                            wtoken.startingData = null;
+                            wtoken.startingView = null;
+                            wtoken.startingWindow = null;
+                            wtoken.startingDisplayed = false;
+                        }
+
+                        try {
+                            mPolicy.removeStartingWindow(token, view);
+                        } catch (Exception e) {
+                            Slog.w(TAG, "Exception when removing starting window", e);
+                        }
+                    }
+                } break;
+
+                case REPORT_APPLICATION_TOKEN_DRAWN: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+
+                    try {
+                        if (DEBUG_VISIBILITY) Slog.v(
+                                TAG, "Reporting drawn in " + wtoken);
+                        wtoken.appToken.windowsDrawn();
+                    } catch (RemoteException ex) {
+                    }
+                } break;
+
+                case REPORT_APPLICATION_TOKEN_WINDOWS: {
+                    final AppWindowToken wtoken = (AppWindowToken)msg.obj;
+
+                    boolean nowVisible = msg.arg1 != 0;
+                    boolean nowGone = msg.arg2 != 0;
+
+                    try {
+                        if (DEBUG_VISIBILITY) Slog.v(
+                                TAG, "Reporting visible in " + wtoken
+                                + " visible=" + nowVisible
+                                + " gone=" + nowGone);
+                        if (nowVisible) {
+                            wtoken.appToken.windowsVisible();
+                        } else {
+                            wtoken.appToken.windowsGone();
+                        }
+                    } catch (RemoteException ex) {
+                    }
+                } break;
+
+                case WINDOW_FREEZE_TIMEOUT: {
+                    // TODO(multidisplay): Can non-default displays rotate?
+                    synchronized (mWindowMap) {
+                        Slog.w(TAG, "Window freeze timeout expired.");
+                        final WindowList windows = getDefaultWindowListLocked();
+                        int i = windows.size();
+                        while (i > 0) {
+                            i--;
+                            WindowState w = windows.get(i);
+                            if (w.mOrientationChanging) {
+                                w.mOrientationChanging = false;
+                                w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                                        - mDisplayFreezeTime);
+                                Slog.w(TAG, "Force clearing orientation change: " + w);
+                            }
+                        }
+                        performLayoutAndPlaceSurfacesLocked();
+                    }
+                    break;
+                }
+
+                case APP_TRANSITION_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        if (mAppTransition.isTransitionSet()) {
+                            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT");
+                            mAppTransition.setTimeout();
+                            performLayoutAndPlaceSurfacesLocked();
+                        }
+                    }
+                    break;
+                }
+
+                case PERSIST_ANIMATION_SCALE: {
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScale);
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.TRANSITION_ANIMATION_SCALE, mTransitionAnimationScale);
+                    Settings.Global.putFloat(mContext.getContentResolver(),
+                            Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScale);
+                    break;
+                }
+
+                case FORCE_GC: {
+                    synchronized (mWindowMap) {
+                        // Since we're holding both mWindowMap and mAnimator we don't need to
+                        // hold mAnimator.mLayoutToAnim.
+                        if (mAnimator.mAnimating || mAnimationScheduled) {
+                            // If we are animating, don't do the gc now but
+                            // delay a bit so we don't interrupt the animation.
+                            sendEmptyMessageDelayed(H.FORCE_GC, 2000);
+                            return;
+                        }
+                        // If we are currently rotating the display, it will
+                        // schedule a new message when done.
+                        if (mDisplayFrozen) {
+                            return;
+                        }
+                    }
+                    Runtime.getRuntime().gc();
+                    break;
+                }
+
+                case ENABLE_SCREEN: {
+                    performEnableScreen();
+                    break;
+                }
+
+                case APP_FREEZE_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        Slog.w(TAG, "App freeze timeout expired.");
+                        DisplayContent displayContent = getDefaultDisplayContentLocked();
+                        final ArrayList<Task> tasks = displayContent.getTasks();
+                        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                            AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+                            for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
+                                AppWindowToken tok = tokens.get(tokenNdx);
+                                if (tok.mAppAnimator.freezingScreen) {
+                                    Slog.w(TAG, "Force clearing freeze: " + tok);
+                                    unsetAppFreezingScreenLocked(tok, true, true);
+                                }
+                            }
+                        }
+                    }
+                    break;
+                }
+
+                case CLIENT_FREEZE_TIMEOUT: {
+                    synchronized (mWindowMap) {
+                        if (mClientFreezingScreen) {
+                            mClientFreezingScreen = false;
+                            mLastFinishedFreezeSource = "client-timeout";
+                            stopFreezingDisplayLocked();
+                        }
+                    }
+                    break;
+                }
+
+                case SEND_NEW_CONFIGURATION: {
+                    removeMessages(SEND_NEW_CONFIGURATION);
+                    sendNewConfiguration();
+                    break;
+                }
+
+                case REPORT_WINDOWS_CHANGE: {
+                    if (mWindowsChanged) {
+                        synchronized (mWindowMap) {
+                            mWindowsChanged = false;
+                        }
+                        notifyWindowsChanged();
+                    }
+                    break;
+                }
+
+                case DRAG_START_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG, "Timeout starting drag by win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the app that has failed to start the drag in time
+                        if (mDragState != null) {
+                            mDragState.unregister();
+                            mInputMonitor.updateInputWindowsLw(true /*force*/);
+                            mDragState.reset();
+                            mDragState = null;
+                        }
+                    }
+                    break;
+                }
+
+                case DRAG_END_TIMEOUT: {
+                    IBinder win = (IBinder)msg.obj;
+                    if (DEBUG_DRAG) {
+                        Slog.w(TAG, "Timeout ending drag to win " + win);
+                    }
+                    synchronized (mWindowMap) {
+                        // !!! TODO: ANR the drag-receiving app
+                        if (mDragState != null) {
+                            mDragState.mDragResult = false;
+                            mDragState.endDragLw();
+                        }
+                    }
+                    break;
+                }
+
+                case REPORT_HARD_KEYBOARD_STATUS_CHANGE: {
+                    notifyHardKeyboardStatusChange();
+                    break;
+                }
+
+                case BOOT_TIMEOUT: {
+                    performBootTimeout();
+                    break;
+                }
+
+                case WAITING_FOR_DRAWN_TIMEOUT: {
+                    Pair<WindowState, IRemoteCallback> pair;
+                    synchronized (mWindowMap) {
+                        pair = (Pair<WindowState, IRemoteCallback>)msg.obj;
+                        Slog.w(TAG, "Timeout waiting for drawn: " + pair.first);
+                        if (!mWaitingForDrawn.remove(pair)) {
+                            return;
+                        }
+                    }
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
+
+                case SHOW_STRICT_MODE_VIOLATION: {
+                    showStrictModeViolation(msg.arg1, msg.arg2);
+                    break;
+                }
+
+                case DO_ANIMATION_CALLBACK: {
+                    try {
+                        ((IRemoteCallback)msg.obj).sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+                }
+
+                case DO_DISPLAY_ADDED:
+                    synchronized (mWindowMap) {
+                        handleDisplayAddedLocked(msg.arg1);
+                    }
+                    break;
+
+                case DO_DISPLAY_REMOVED:
+                    synchronized (mWindowMap) {
+                        handleDisplayRemovedLocked(msg.arg1);
+                    }
+                    break;
+
+                case DO_DISPLAY_CHANGED:
+                    synchronized (mWindowMap) {
+                        handleDisplayChangedLocked(msg.arg1);
+                    }
+                    break;
+
+                case TAP_OUTSIDE_STACK: {
+                    int stackId;
+                    synchronized (mWindowMap) {
+                        stackId = ((DisplayContent)msg.obj).stackIdFromPoint(msg.arg1, msg.arg2);
+                    }
+                    if (stackId >= 0) {
+                        try {
+                            mActivityManager.setFocusedStack(stackId);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+                break;
+                case NOTIFY_ACTIVITY_DRAWN:
+                    try {
+                        mActivityManager.notifyActivityDrawn((IBinder) msg.obj);
+                    } catch (RemoteException e) {
+                    }
+                    break;
+            }
+            if (DEBUG_WINDOW_TRACE) {
+                Slog.v(TAG, "handleMessage: exit");
+            }
+        }
+    }
+
+    // -------------------------------------------------------------
+    // IWindowManager API
+    // -------------------------------------------------------------
+
+    @Override
+    public IWindowSession openSession(IInputMethodClient client,
+            IInputContext inputContext) {
+        if (client == null) throw new IllegalArgumentException("null client");
+        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
+        Session session = new Session(this, client, inputContext);
+        return session;
+    }
+
+    @Override
+    public boolean inputMethodClientHasFocus(IInputMethodClient client) {
+        synchronized (mWindowMap) {
+            // The focus for the client is the window immediately below
+            // where we would place the input method window.
+            int idx = findDesiredInputMethodWindowIndexLocked(false);
+            if (idx > 0) {
+                // TODO(multidisplay): IMEs are only supported on the default display.
+                WindowState imFocus = getDefaultWindowListLocked().get(idx-1);
+                if (DEBUG_INPUT_METHOD) {
+                    Slog.i(TAG, "Desired input method target: " + imFocus);
+                    Slog.i(TAG, "Current focus: " + mCurrentFocus);
+                    Slog.i(TAG, "Last focus: " + mLastFocus);
+                }
+                if (imFocus != null) {
+                    // This may be a starting window, in which case we still want
+                    // to count it as okay.
+                    if (imFocus.mAttrs.type == LayoutParams.TYPE_APPLICATION_STARTING
+                            && imFocus.mAppToken != null) {
+                        // The client has definitely started, so it really should
+                        // have a window in this app token.  Let's look for it.
+                        for (int i=0; i<imFocus.mAppToken.windows.size(); i++) {
+                            WindowState w = imFocus.mAppToken.windows.get(i);
+                            if (w != imFocus) {
+                                Log.i(TAG, "Switching to real app window: " + w);
+                                imFocus = w;
+                                break;
+                            }
+                        }
+                    }
+                    if (DEBUG_INPUT_METHOD) {
+                        Slog.i(TAG, "IM target client: " + imFocus.mSession.mClient);
+                        if (imFocus.mSession.mClient != null) {
+                            Slog.i(TAG, "IM target client binder: "
+                                    + imFocus.mSession.mClient.asBinder());
+                            Slog.i(TAG, "Requesting client binder: " + client.asBinder());
+                        }
+                    }
+                    if (imFocus.mSession.mClient != null &&
+                            imFocus.mSession.mClient.asBinder() == client.asBinder()) {
+                        return true;
+                    }
+                }
+            }
+
+            // Okay, how about this...  what is the current focus?
+            // It seems in some cases we may not have moved the IM
+            // target window, such as when it was in a pop-up window,
+            // so let's also look at the current focus.  (An example:
+            // go to Gmail, start searching so the keyboard goes up,
+            // press home.  Sometimes the IME won't go down.)
+            // Would be nice to fix this more correctly, but it's
+            // way at the end of a release, and this should be good enough.
+            if (mCurrentFocus != null && mCurrentFocus.mSession.mClient != null
+                    && mCurrentFocus.mSession.mClient.asBinder() == client.asBinder()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public void getInitialDisplaySize(int displayId, Point size) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                synchronized(displayContent.mDisplaySizeLock) {
+                    size.x = displayContent.mInitialDisplayWidth;
+                    size.y = displayContent.mInitialDisplayHeight;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void getBaseDisplaySize(int displayId, Point size) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                synchronized(displayContent.mDisplaySizeLock) {
+                    size.x = displayContent.mBaseDisplayWidth;
+                    size.y = displayContent.mBaseDisplayHeight;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void setForcedDisplaySize(int displayId, int width, int height) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                // Set some sort of reasonable bounds on the size of the display that we
+                // will try to emulate.
+                final int MIN_WIDTH = 200;
+                final int MIN_HEIGHT = 200;
+                final int MAX_SCALE = 2;
+                final DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent != null) {
+                    width = Math.min(Math.max(width, MIN_WIDTH),
+                            displayContent.mInitialDisplayWidth * MAX_SCALE);
+                    height = Math.min(Math.max(height, MIN_HEIGHT),
+                            displayContent.mInitialDisplayHeight * MAX_SCALE);
+                    setForcedDisplaySizeLocked(displayContent, width, height);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_SIZE_FORCED, width + "," + height);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) {
+        String sizeStr = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.DISPLAY_SIZE_FORCED);
+        if (sizeStr == null || sizeStr.length() == 0) {
+            sizeStr = SystemProperties.get(SIZE_OVERRIDE, null);
+        }
+        if (sizeStr != null && sizeStr.length() > 0) {
+            final int pos = sizeStr.indexOf(',');
+            if (pos > 0 && sizeStr.lastIndexOf(',') == pos) {
+                int width, height;
+                try {
+                    width = Integer.parseInt(sizeStr.substring(0, pos));
+                    height = Integer.parseInt(sizeStr.substring(pos+1));
+                    synchronized(displayContent.mDisplaySizeLock) {
+                        if (displayContent.mBaseDisplayWidth != width
+                                || displayContent.mBaseDisplayHeight != height) {
+                            Slog.i(TAG, "FORCED DISPLAY SIZE: " + width + "x" + height);
+                            displayContent.mBaseDisplayWidth = width;
+                            displayContent.mBaseDisplayHeight = height;
+                        }
+                    }
+                } catch (NumberFormatException ex) {
+                }
+            }
+        }
+        String densityStr = Settings.Global.getString(mContext.getContentResolver(),
+                Settings.Global.DISPLAY_DENSITY_FORCED);
+        if (densityStr == null || densityStr.length() == 0) {
+            densityStr = SystemProperties.get(DENSITY_OVERRIDE, null);
+        }
+        if (densityStr != null && densityStr.length() > 0) {
+            int density;
+            try {
+                density = Integer.parseInt(densityStr);
+                synchronized(displayContent.mDisplaySizeLock) {
+                    if (displayContent.mBaseDisplayDensity != density) {
+                        Slog.i(TAG, "FORCED DISPLAY DENSITY: " + density);
+                        displayContent.mBaseDisplayDensity = density;
+                    }
+                }
+            } catch (NumberFormatException ex) {
+            }
+        }
+    }
+
+    // displayContent must not be null
+    private void setForcedDisplaySizeLocked(DisplayContent displayContent, int width, int height) {
+        Slog.i(TAG, "Using new display size: " + width + "x" + height);
+
+        synchronized(displayContent.mDisplaySizeLock) {
+            displayContent.mBaseDisplayWidth = width;
+            displayContent.mBaseDisplayHeight = height;
+        }
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    @Override
+    public void clearForcedDisplaySize(int displayId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent != null) {
+                    setForcedDisplaySizeLocked(displayContent, displayContent.mInitialDisplayWidth,
+                            displayContent.mInitialDisplayHeight);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_SIZE_FORCED, "");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    @Override
+    public int getInitialDisplayDensity(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                synchronized(displayContent.mDisplaySizeLock) {
+                    return displayContent.mInitialDisplayDensity;
+                }
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getBaseDisplayDensity(int displayId) {
+        synchronized (mWindowMap) {
+            final DisplayContent displayContent = getDisplayContentLocked(displayId);
+            if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) {
+                synchronized(displayContent.mDisplaySizeLock) {
+                    return displayContent.mBaseDisplayDensity;
+                }
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public void setForcedDisplayDensity(int displayId, int density) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent != null) {
+                    setForcedDisplayDensityLocked(displayContent, density);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_DENSITY_FORCED, Integer.toString(density));
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // displayContent must not be null
+    private void setForcedDisplayDensityLocked(DisplayContent displayContent, int density) {
+        Slog.i(TAG, "Using new display density: " + density);
+
+        synchronized(displayContent.mDisplaySizeLock) {
+            displayContent.mBaseDisplayDensity = density;
+        }
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    @Override
+    public void clearForcedDisplayDensity(int displayId) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        if (displayId != Display.DEFAULT_DISPLAY) {
+            throw new IllegalArgumentException("Can only set the default display");
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                final DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent != null) {
+                    setForcedDisplayDensityLocked(displayContent,
+                            displayContent.mInitialDisplayDensity);
+                    Settings.Global.putString(mContext.getContentResolver(),
+                            Settings.Global.DISPLAY_DENSITY_FORCED, "");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    // displayContent must not be null
+    private void reconfigureDisplayLocked(DisplayContent displayContent) {
+        // TODO: Multidisplay: for now only use with default display.
+        configureDisplayPolicyLocked(displayContent);
+        displayContent.layoutNeeded = true;
+
+        boolean configChanged = updateOrientationFromAppTokensLocked(false);
+        mTempConfiguration.setToDefaults();
+        mTempConfiguration.fontScale = mCurConfiguration.fontScale;
+        if (computeScreenConfigurationLocked(mTempConfiguration)) {
+            if (mCurConfiguration.diff(mTempConfiguration) != 0) {
+                configChanged = true;
+            }
+        }
+
+        if (configChanged) {
+            mWaitingForConfig = true;
+            startFreezingDisplayLocked(false, 0, 0);
+            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+        }
+
+        performLayoutAndPlaceSurfacesLocked();
+    }
+
+    private void configureDisplayPolicyLocked(DisplayContent displayContent) {
+        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
+                displayContent.mBaseDisplayWidth,
+                displayContent.mBaseDisplayHeight,
+                displayContent.mBaseDisplayDensity);
+
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
+                displayInfo.overscanLeft, displayInfo.overscanTop,
+                displayInfo.overscanRight, displayInfo.overscanBottom);
+    }
+
+    @Override
+    public void setOverscan(int displayId, int left, int top, int right, int bottom) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.WRITE_SECURE_SETTINGS) !=
+                PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Must hold permission " +
+                    android.Manifest.permission.WRITE_SECURE_SETTINGS);
+        }
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized(mWindowMap) {
+                DisplayContent displayContent = getDisplayContentLocked(displayId);
+                if (displayContent != null) {
+                    setOverscanLocked(displayContent, left, top, right, bottom);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private void setOverscanLocked(DisplayContent displayContent,
+            int left, int top, int right, int bottom) {
+        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        synchronized (displayContent.mDisplaySizeLock) {
+            displayInfo.overscanLeft = left;
+            displayInfo.overscanTop = top;
+            displayInfo.overscanRight = right;
+            displayInfo.overscanBottom = bottom;
+        }
+
+        mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom);
+        mDisplaySettings.writeSettingsLocked();
+
+        reconfigureDisplayLocked(displayContent);
+    }
+
+    // -------------------------------------------------------------
+    // Internals
+    // -------------------------------------------------------------
+
+    final WindowState windowForClientLocked(Session session, IWindow client,
+            boolean throwOnError) {
+        return windowForClientLocked(session, client.asBinder(), throwOnError);
+    }
+
+    final WindowState windowForClientLocked(Session session, IBinder client,
+            boolean throwOnError) {
+        WindowState win = mWindowMap.get(client);
+        if (localLOGV) Slog.v(
+            TAG, "Looking up client " + client + ": " + win);
+        if (win == null) {
+            RuntimeException ex = new IllegalArgumentException(
+                    "Requested window " + client + " does not exist");
+            if (throwOnError) {
+                throw ex;
+            }
+            Slog.w(TAG, "Failed looking up window", ex);
+            return null;
+        }
+        if (session != null && win.mSession != session) {
+            RuntimeException ex = new IllegalArgumentException(
+                    "Requested window " + client + " is in session " +
+                    win.mSession + ", not " + session);
+            if (throwOnError) {
+                throw ex;
+            }
+            Slog.w(TAG, "Failed looking up window", ex);
+            return null;
+        }
+
+        return win;
+    }
+
+    final void rebuildAppWindowListLocked() {
+        // TODO: Multidisplay, when ActivityStacks and tasks exist on more than one display.
+        rebuildAppWindowListLocked(getDefaultDisplayContentLocked());
+    }
+
+    private void rebuildAppWindowListLocked(final DisplayContent displayContent) {
+        final WindowList windows = displayContent.getWindowList();
+        int NW = windows.size();
+        int i;
+        int lastBelow = -1;
+        int numRemoved = 0;
+
+        if (mRebuildTmp.length < NW) {
+            mRebuildTmp = new WindowState[NW+10];
+        }
+
+        // First remove all existing app windows.
+        i=0;
+        while (i < NW) {
+            WindowState w = windows.get(i);
+            if (w.mAppToken != null) {
+                WindowState win = windows.remove(i);
+                win.mRebuilding = true;
+                mRebuildTmp[numRemoved] = win;
+                mWindowsChanged = true;
+                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win);
+                NW--;
+                numRemoved++;
+                continue;
+            } else if (lastBelow == i-1) {
+                if (w.mAttrs.type == TYPE_WALLPAPER || w.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
+                    lastBelow = i;
+                }
+            }
+            i++;
+        }
+
+        // Keep whatever windows were below the app windows still below,
+        // by skipping them.
+        lastBelow++;
+        i = lastBelow;
+
+        // First add all of the exiting app tokens...  these are no longer
+        // in the main app list, but still have windows shown.  We put them
+        // in the back because now that the animation is over we no longer
+        // will care about them.
+        AppTokenList exitingAppTokens = displayContent.mExitingAppTokens;
+        int NT = exitingAppTokens.size();
+        for (int j=0; j<NT; j++) {
+            i = reAddAppWindowsLocked(displayContent, i, exitingAppTokens.get(j));
+        }
+
+        // And add in the still active app tokens in Z order.
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                i = reAddAppWindowsLocked(displayContent, i, wtoken);
+            }
+        }
+
+        i -= lastBelow;
+        if (i != numRemoved) {
+            Slog.w(TAG, "Rebuild removed " + numRemoved + " windows but added " + i,
+                    new RuntimeException("here").fillInStackTrace());
+            for (i=0; i<numRemoved; i++) {
+                WindowState ws = mRebuildTmp[i];
+                if (ws.mRebuilding) {
+                    StringWriter sw = new StringWriter();
+                    PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+                    ws.dump(pw, "", true);
+                    pw.flush();
+                    Slog.w(TAG, "This window was lost: " + ws);
+                    Slog.w(TAG, sw.toString());
+                    ws.mWinAnimator.destroySurfaceLocked();
+                }
+            }
+            Slog.w(TAG, "Current app token list:");
+            dumpAppTokensLocked();
+            Slog.w(TAG, "Final window list:");
+            dumpWindowsLocked();
+        }
+    }
+
+    private final void assignLayersLocked(WindowList windows) {
+        int N = windows.size();
+        int curBaseLayer = 0;
+        int curLayer = 0;
+        int i;
+
+        if (DEBUG_LAYERS) Slog.v(TAG, "Assigning layers based on windows=" + windows,
+                new RuntimeException("here").fillInStackTrace());
+
+        boolean anyLayerChanged = false;
+
+        for (i=0; i<N; i++) {
+            final WindowState w = windows.get(i);
+            final WindowStateAnimator winAnimator = w.mWinAnimator;
+            boolean layerChanged = false;
+            int oldLayer = w.mLayer;
+            if (w.mBaseLayer == curBaseLayer || w.mIsImWindow
+                    || (i > 0 && w.mIsWallpaper)) {
+                curLayer += WINDOW_LAYER_MULTIPLIER;
+                w.mLayer = curLayer;
+            } else {
+                curBaseLayer = curLayer = w.mBaseLayer;
+                w.mLayer = curLayer;
+            }
+            if (w.mLayer != oldLayer) {
+                layerChanged = true;
+                anyLayerChanged = true;
+            }
+            final AppWindowToken wtoken = w.mAppToken;
+            oldLayer = winAnimator.mAnimLayer;
+            if (w.mTargetAppToken != null) {
+                winAnimator.mAnimLayer =
+                        w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment;
+            } else if (wtoken != null) {
+                winAnimator.mAnimLayer =
+                        w.mLayer + wtoken.mAppAnimator.animLayerAdjustment;
+            } else {
+                winAnimator.mAnimLayer = w.mLayer;
+            }
+            if (w.mIsImWindow) {
+                winAnimator.mAnimLayer += mInputMethodAnimLayerAdjustment;
+            } else if (w.mIsWallpaper) {
+                winAnimator.mAnimLayer += mWallpaperAnimLayerAdjustment;
+            }
+            if (winAnimator.mAnimLayer != oldLayer) {
+                layerChanged = true;
+                anyLayerChanged = true;
+            }
+            if (layerChanged && w.getStack().isDimming(winAnimator)) {
+                // Force an animation pass just to update the mDimLayer layer.
+                scheduleAnimationLocked();
+            }
+            if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": "
+                    + "mBase=" + w.mBaseLayer
+                    + " mLayer=" + w.mLayer
+                    + (wtoken == null ?
+                            "" : " mAppLayer=" + wtoken.mAppAnimator.animLayerAdjustment)
+                    + " =mAnimLayer=" + winAnimator.mAnimLayer);
+            //System.out.println(
+            //    "Assigned layer " + curLayer + " to " + w.mClient.asBinder());
+        }
+
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mDisplayMagnifier != null && anyLayerChanged
+                && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) {
+            mDisplayMagnifier.onWindowLayersChangedLocked();
+        }
+    }
+
+    private final void performLayoutAndPlaceSurfacesLocked() {
+        int loopCount = 6;
+        do {
+            mTraversalScheduled = false;
+            performLayoutAndPlaceSurfacesLockedLoop();
+            mH.removeMessages(H.DO_TRAVERSAL);
+            loopCount--;
+        } while (mTraversalScheduled && loopCount > 0);
+        mInnerFields.mWallpaperActionPending = false;
+    }
+
+    private boolean mInLayout = false;
+    private final void performLayoutAndPlaceSurfacesLockedLoop() {
+        if (mInLayout) {
+            if (DEBUG) {
+                throw new RuntimeException("Recursive call!");
+            }
+            Slog.w(TAG, "performLayoutAndPlaceSurfacesLocked called while in layout. Callers="
+                    + Debug.getCallers(3));
+            return;
+        }
+
+        if (mWaitingForConfig) {
+            // Our configuration has changed (most likely rotation), but we
+            // don't yet have the complete configuration to report to
+            // applications.  Don't do any window layout until we have it.
+            return;
+        }
+
+        if (!mDisplayReady) {
+            // Not yet initialized, nothing to do.
+            return;
+        }
+
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
+        mInLayout = true;
+        boolean recoveringMemory = false;
+
+        try {
+            if (mForceRemoves != null) {
+                recoveringMemory = true;
+                // Wait a little bit for things to settle down, and off we go.
+                for (int i=0; i<mForceRemoves.size(); i++) {
+                    WindowState ws = mForceRemoves.get(i);
+                    Slog.i(TAG, "Force removing: " + ws);
+                    removeWindowInnerLocked(ws.mSession, ws);
+                }
+                mForceRemoves = null;
+                Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
+                Object tmp = new Object();
+                synchronized (tmp) {
+                    try {
+                        tmp.wait(250);
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Unhandled exception while force removing for memory", e);
+        }
+
+        try {
+            performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
+
+            mInLayout = false;
+
+            if (needsLayout()) {
+                if (++mLayoutRepeatCount < 6) {
+                    requestTraversalLocked();
+                } else {
+                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
+                    mLayoutRepeatCount = 0;
+                }
+            } else {
+                mLayoutRepeatCount = 0;
+            }
+
+            if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
+                mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
+                mH.sendEmptyMessage(H.REPORT_WINDOWS_CHANGE);
+            }
+        } catch (RuntimeException e) {
+            mInLayout = false;
+            Log.wtf(TAG, "Unhandled exception while laying out windows", e);
+        }
+
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+    }
+
+    private final void performLayoutLockedInner(final DisplayContent displayContent,
+                                    boolean initial, boolean updateInputWindows) {
+        if (!displayContent.layoutNeeded) {
+            return;
+        }
+        displayContent.layoutNeeded = false;
+        WindowList windows = displayContent.getWindowList();
+        boolean isDefaultDisplay = displayContent.isDefaultDisplay;
+
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
+        final int NFW = mFakeWindows.size();
+        for (int i=0; i<NFW; i++) {
+            mFakeWindows.get(i).layout(dw, dh);
+        }
+
+        final int N = windows.size();
+        int i;
+
+        if (DEBUG_LAYOUT) {
+            Slog.v(TAG, "-------------------------------------");
+            Slog.v(TAG, "performLayout: needed="
+                    + displayContent.layoutNeeded + " dw=" + dw + " dh=" + dh);
+        }
+
+        WindowStateAnimator universeBackground = null;
+
+        mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
+        if (isDefaultDisplay) {
+            // Not needed on non-default displays.
+            mSystemDecorLayer = mPolicy.getSystemDecorLayerLw();
+            mScreenRect.set(0, 0, dw, dh);
+        }
+
+        mPolicy.getContentRectLw(mTmpContentRect);
+        displayContent.resize(mTmpContentRect);
+
+        int seq = mLayoutSeq+1;
+        if (seq < 0) seq = 0;
+        mLayoutSeq = seq;
+
+        boolean behindDream = false;
+
+        // First perform layout of any root windows (not attached
+        // to another window).
+        int topAttached = -1;
+        for (i = N-1; i >= 0; i--) {
+            final WindowState win = windows.get(i);
+
+            // Don't do layout of a window if it is not visible, or
+            // soon won't be visible, to avoid wasting time and funky
+            // changes while a window is animating away.
+            final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
+                    || win.isGoneForLayoutLw();
+
+            if (DEBUG_LAYOUT && !win.mLayoutAttached) {
+                Slog.v(TAG, "1ST PASS " + win
+                        + ": gone=" + gone + " mHaveFrame=" + win.mHaveFrame
+                        + " mLayoutAttached=" + win.mLayoutAttached
+                        + " screen changed=" + win.isConfigChanged());
+                final AppWindowToken atoken = win.mAppToken;
+                if (gone) Slog.v(TAG, "  GONE: mViewVisibility="
+                        + win.mViewVisibility + " mRelayoutCalled="
+                        + win.mRelayoutCalled + " hidden="
+                        + win.mRootToken.hidden + " hiddenRequested="
+                        + (atoken != null && atoken.hiddenRequested)
+                        + " mAttachedHidden=" + win.mAttachedHidden);
+                else Slog.v(TAG, "  VIS: mViewVisibility="
+                        + win.mViewVisibility + " mRelayoutCalled="
+                        + win.mRelayoutCalled + " hidden="
+                        + win.mRootToken.hidden + " hiddenRequested="
+                        + (atoken != null && atoken.hiddenRequested)
+                        + " mAttachedHidden=" + win.mAttachedHidden);
+            }
+
+            // If this view is GONE, then skip it -- keep the current
+            // frame, and let the caller know so they can ignore it
+            // if they want.  (We do the normal layout for INVISIBLE
+            // windows, since that means "perform layout as normal,
+            // just don't display").
+            if (!gone || !win.mHaveFrame || win.mLayoutNeeded
+                    || ((win.isConfigChanged() || win.setInsetsChanged()) &&
+                            (win.mAttrs.type == TYPE_KEYGUARD ||
+                            win.mAppToken != null && win.mAppToken.layoutConfigChanges))
+                    || win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
+                if (!win.mLayoutAttached) {
+                    if (initial) {
+                        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+                        win.mContentChanged = false;
+                    }
+                    if (win.mAttrs.type == TYPE_DREAM) {
+                        // Don't layout windows behind a dream, so that if it
+                        // does stuff like hide the status bar we won't get a
+                        // bad transition when it goes away.
+                        behindDream = true;
+                    }
+                    win.mLayoutNeeded = false;
+                    win.prelayout();
+                    mPolicy.layoutWindowLw(win, win.mAttrs, null);
+                    win.mLayoutSeq = seq;
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame="
+                            + win.mFrame + " mContainingFrame="
+                            + win.mContainingFrame + " mDisplayFrame="
+                            + win.mDisplayFrame);
+                } else {
+                    if (topAttached < 0) topAttached = i;
+                }
+            }
+            if (win.mViewVisibility == View.VISIBLE
+                    && win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND
+                    && universeBackground == null) {
+                universeBackground = win.mWinAnimator;
+            }
+        }
+
+        if (mAnimator.mUniverseBackground  != universeBackground) {
+            mFocusMayChange = true;
+            mAnimator.mUniverseBackground = universeBackground;
+        }
+
+        boolean attachedBehindDream = false;
+
+        // Now perform layout of attached windows, which usually
+        // depend on the position of the window they are attached to.
+        // XXX does not deal with windows that are attached to windows
+        // that are themselves attached.
+        for (i = topAttached; i >= 0; i--) {
+            final WindowState win = windows.get(i);
+
+            if (win.mLayoutAttached) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + win
+                        + " mHaveFrame=" + win.mHaveFrame
+                        + " mViewVisibility=" + win.mViewVisibility
+                        + " mRelayoutCalled=" + win.mRelayoutCalled);
+                // If this view is GONE, then skip it -- keep the current
+                // frame, and let the caller know so they can ignore it
+                // if they want.  (We do the normal layout for INVISIBLE
+                // windows, since that means "perform layout as normal,
+                // just don't display").
+                if (attachedBehindDream && mPolicy.canBeForceHidden(win, win.mAttrs)) {
+                    continue;
+                }
+                if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
+                        || !win.mHaveFrame || win.mLayoutNeeded) {
+                    if (initial) {
+                        //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
+                        win.mContentChanged = false;
+                    }
+                    win.mLayoutNeeded = false;
+                    win.prelayout();
+                    mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
+                    win.mLayoutSeq = seq;
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame="
+                            + win.mFrame + " mContainingFrame="
+                            + win.mContainingFrame + " mDisplayFrame="
+                            + win.mDisplayFrame);
+                }
+            } else if (win.mAttrs.type == TYPE_DREAM) {
+                // Don't layout windows behind a dream, so that if it
+                // does stuff like hide the status bar we won't get a
+                // bad transition when it goes away.
+                attachedBehindDream = behindDream;
+            }
+        }
+
+        // Window frames may have changed.  Tell the input dispatcher about it.
+        mInputMonitor.setUpdateInputWindowsNeededLw();
+        if (updateInputWindows) {
+            mInputMonitor.updateInputWindowsLw(false /*force*/);
+        }
+
+        mPolicy.finishLayoutLw();
+    }
+
+    void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
+        // If the screen is currently frozen or off, then keep
+        // it frozen/off until this window draws at its new
+        // orientation.
+        if (!okToDisplay()) {
+            if (DEBUG_ORIENTATION) Slog.v(TAG,
+                    "Changing surface while display frozen: " + w);
+            w.mOrientationChanging = true;
+            w.mLastFreezeDuration = 0;
+            mInnerFields.mOrientationChangeComplete = false;
+            if (!mWindowsFreezingScreen) {
+                mWindowsFreezingScreen = true;
+                // XXX should probably keep timeout from
+                // when we first froze the display.
+                mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
+                mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT,
+                        WINDOW_FREEZE_TIMEOUT_DURATION);
+            }
+        }
+    }
+
+    /**
+     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
+     * @param windows List of windows on default display.
+     * @return bitmap indicating if another pass through layout must be made.
+     */
+    public int handleAppTransitionReadyLocked(WindowList windows) {
+        int changes = 0;
+        int i;
+        int NN = mOpeningApps.size();
+        boolean goodToGo = true;
+        if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                "Checking " + NN + " opening apps (frozen="
+                + mDisplayFrozen + " timeout="
+                + mAppTransition.isTimeout() + ")...");
+        if (!mDisplayFrozen && !mAppTransition.isTimeout()) {
+            // If the display isn't frozen, wait to do anything until
+            // all of the apps are ready.  Otherwise just go because
+            // we'll unfreeze the display when everyone is ready.
+            for (i=0; i<NN && goodToGo; i++) {
+                AppWindowToken wtoken = mOpeningApps.get(i);
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "Check opening app=" + wtoken + ": allDrawn="
+                        + wtoken.allDrawn + " startingDisplayed="
+                        + wtoken.startingDisplayed + " startingMoved="
+                        + wtoken.startingMoved);
+                if (!wtoken.allDrawn && !wtoken.startingDisplayed
+                        && !wtoken.startingMoved) {
+                    goodToGo = false;
+                }
+            }
+        }
+        if (goodToGo) {
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
+            int transit = mAppTransition.getAppTransition();
+            if (mSkipAppTransitionAnimation) {
+                transit = AppTransition.TRANSIT_UNSET;
+            }
+            mAppTransition.goodToGo();
+            mStartingIconInTransition = false;
+            mSkipAppTransitionAnimation = false;
+
+            mH.removeMessages(H.APP_TRANSITION_TIMEOUT);
+
+            rebuildAppWindowListLocked();
+
+            // if wallpaper is animating in or out set oldWallpaper to null else to wallpaper
+            WindowState oldWallpaper =
+                    mWallpaperTarget != null && mWallpaperTarget.mWinAnimator.isAnimating()
+                        && !mWallpaperTarget.mWinAnimator.isDummyAnimation()
+                    ? null : mWallpaperTarget;
+
+            mInnerFields.mWallpaperMayChange = false;
+
+            // The top-most window will supply the layout params,
+            // and we will determine it below.
+            LayoutParams animLp = null;
+            int bestAnimLayer = -1;
+            boolean fullscreenAnim = false;
+
+            if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                    "New wallpaper target=" + mWallpaperTarget
+                    + ", oldWallpaper=" + oldWallpaper
+                    + ", lower target=" + mLowerWallpaperTarget
+                    + ", upper target=" + mUpperWallpaperTarget);
+
+            boolean openingAppHasWallpaper = false;
+            boolean closingAppHasWallpaper = false;
+            final AppWindowToken lowerWallpaperAppToken;
+            final AppWindowToken upperWallpaperAppToken;
+            if (mLowerWallpaperTarget == null) {
+                lowerWallpaperAppToken = upperWallpaperAppToken = null;
+            } else {
+                lowerWallpaperAppToken = mLowerWallpaperTarget.mAppToken;
+                upperWallpaperAppToken = mUpperWallpaperTarget.mAppToken;
+            }
+
+            // Do a first pass through the tokens for two
+            // things:
+            // (1) Determine if both the closing and opening
+            // app token sets are wallpaper targets, in which
+            // case special animations are needed
+            // (since the wallpaper needs to stay static
+            // behind them).
+            // (2) Find the layout params of the top-most
+            // application window in the tokens, which is
+            // what will control the animation theme.
+            final int NC = mClosingApps.size();
+            NN = NC + mOpeningApps.size();
+            for (i=0; i<NN; i++) {
+                final AppWindowToken wtoken;
+                if (i < NC) {
+                    wtoken = mClosingApps.get(i);
+                    if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
+                        closingAppHasWallpaper = true;
+                    }
+                } else {
+                    wtoken = mOpeningApps.get(i - NC);
+                    if (wtoken == lowerWallpaperAppToken || wtoken == upperWallpaperAppToken) {
+                        openingAppHasWallpaper = true;
+                    }
+                }
+
+                if (wtoken.appFullscreen) {
+                    WindowState ws = wtoken.findMainWindow();
+                    if (ws != null) {
+                        animLp = ws.mAttrs;
+                        bestAnimLayer = ws.mLayer;
+                        fullscreenAnim = true;
+                    }
+                } else if (!fullscreenAnim) {
+                    WindowState ws = wtoken.findMainWindow();
+                    if (ws != null) {
+                        if (ws.mLayer > bestAnimLayer) {
+                            animLp = ws.mAttrs;
+                            bestAnimLayer = ws.mLayer;
+                        }
+                    }
+                }
+            }
+
+            mAnimateWallpaperWithTarget = false;
+            if (closingAppHasWallpaper && openingAppHasWallpaper) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Wallpaper animation!");
+                switch (transit) {
+                    case AppTransition.TRANSIT_ACTIVITY_OPEN:
+                    case AppTransition.TRANSIT_TASK_OPEN:
+                    case AppTransition.TRANSIT_TASK_TO_FRONT:
+                        transit = AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN;
+                        break;
+                    case AppTransition.TRANSIT_ACTIVITY_CLOSE:
+                    case AppTransition.TRANSIT_TASK_CLOSE:
+                    case AppTransition.TRANSIT_TASK_TO_BACK:
+                        transit = AppTransition.TRANSIT_WALLPAPER_INTRA_CLOSE;
+                        break;
+                }
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "New transit: " + transit);
+            } else if ((oldWallpaper != null) && !mOpeningApps.isEmpty()
+                    && !mOpeningApps.contains(oldWallpaper.mAppToken)) {
+                // We are transitioning from an activity with
+                // a wallpaper to one without.
+                transit = AppTransition.TRANSIT_WALLPAPER_CLOSE;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "New transit away from wallpaper: " + transit);
+            } else if (mWallpaperTarget != null && mWallpaperTarget.isVisibleLw()) {
+                // We are transitioning from an activity without
+                // a wallpaper to now showing the wallpaper
+                transit = AppTransition.TRANSIT_WALLPAPER_OPEN;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "New transit into wallpaper: " + transit);
+            } else {
+                mAnimateWallpaperWithTarget = true;
+            }
+
+            // If all closing windows are obscured, then there is
+            // no need to do an animation.  This is the case, for
+            // example, when this transition is being done behind
+            // the lock screen.
+            if (!mPolicy.allowAppAnimationsLw()) {
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG,
+                        "Animations disallowed by keyguard or dream.");
+                animLp = null;
+            }
+
+            AppWindowToken topOpeningApp = null;
+            int topOpeningLayer = 0;
+
+            NN = mOpeningApps.size();
+            for (i=0; i<NN; i++) {
+                AppWindowToken wtoken = mOpeningApps.get(i);
+                final AppWindowAnimator appAnimator = wtoken.mAppAnimator;
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken);
+                appAnimator.clearThumbnail();
+                wtoken.inPendingTransaction = false;
+                appAnimator.animation = null;
+                setTokenVisibilityLocked(wtoken, animLp, true, transit, false);
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.waitingToShow = false;
+
+                appAnimator.mAllAppWinAnimators.clear();
+                final int N = wtoken.allAppWindows.size();
+                for (int j = 0; j < N; j++) {
+                    appAnimator.mAllAppWinAnimators.add(wtoken.allAppWindows.get(j).mWinAnimator);
+                }
+                mAnimator.mAnimating |= appAnimator.showAllWindowsLocked();
+
+                if (animLp != null) {
+                    int layer = -1;
+                    for (int j=0; j<wtoken.windows.size(); j++) {
+                        WindowState win = wtoken.windows.get(j);
+                        if (win.mWinAnimator.mAnimLayer > layer) {
+                            layer = win.mWinAnimator.mAnimLayer;
+                        }
+                    }
+                    if (topOpeningApp == null || layer > topOpeningLayer) {
+                        topOpeningApp = wtoken;
+                        topOpeningLayer = layer;
+                    }
+                }
+            }
+            NN = mClosingApps.size();
+            for (i=0; i<NN; i++) {
+                AppWindowToken wtoken = mClosingApps.get(i);
+                if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken);
+                wtoken.mAppAnimator.clearThumbnail();
+                wtoken.inPendingTransaction = false;
+                wtoken.mAppAnimator.animation = null;
+                setTokenVisibilityLocked(wtoken, animLp, false, transit, false);
+                wtoken.updateReportedVisibilityLocked();
+                wtoken.waitingToHide = false;
+                // Force the allDrawn flag, because we want to start
+                // this guy's animations regardless of whether it's
+                // gotten drawn.
+                wtoken.allDrawn = true;
+                wtoken.deferClearAllDrawn = false;
+            }
+
+            AppWindowAnimator appAnimator =
+                    topOpeningApp == null ? null : topOpeningApp.mAppAnimator;
+            Bitmap nextAppTransitionThumbnail = mAppTransition.getNextAppTransitionThumbnail();
+            if (nextAppTransitionThumbnail != null && appAnimator != null
+                    && appAnimator.animation != null) {
+                // This thumbnail animation is very special, we need to have
+                // an extra surface with the thumbnail included with the animation.
+                Rect dirty = new Rect(0, 0, nextAppTransitionThumbnail.getWidth(),
+                        nextAppTransitionThumbnail.getHeight());
+                try {
+                    // TODO(multi-display): support other displays
+                    final DisplayContent displayContent = getDefaultDisplayContentLocked();
+                    final Display display = displayContent.getDisplay();
+                    SurfaceControl surfaceControl = new SurfaceControl(mFxSession,
+                            "thumbnail anim",
+                            dirty.width(), dirty.height(),
+                            PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN);
+                    surfaceControl.setLayerStack(display.getLayerStack());
+                    appAnimator.thumbnail = surfaceControl;
+                    if (SHOW_TRANSACTIONS) Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
+                    Surface drawSurface = new Surface();
+                    drawSurface.copyFrom(surfaceControl);
+                    Canvas c = drawSurface.lockCanvas(dirty);
+                    c.drawBitmap(nextAppTransitionThumbnail, 0, 0, null);
+                    drawSurface.unlockCanvasAndPost(c);
+                    drawSurface.release();
+                    appAnimator.thumbnailLayer = topOpeningLayer;
+                    DisplayInfo displayInfo = getDefaultDisplayInfoLocked();
+                    Animation anim = mAppTransition.createThumbnailAnimationLocked(
+                            transit, true, true, displayInfo.appWidth, displayInfo.appHeight);
+                    appAnimator.thumbnailAnimation = anim;
+                    anim.restrictDuration(MAX_ANIMATION_DURATION);
+                    anim.scaleCurrentDuration(mTransitionAnimationScale);
+                    Point p = new Point();
+                    mAppTransition.getStartingPoint(p);
+                    appAnimator.thumbnailX = p.x;
+                    appAnimator.thumbnailY = p.y;
+                } catch (OutOfResourcesException e) {
+                    Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width()
+                            + " h=" + dirty.height(), e);
+                    appAnimator.clearThumbnail();
+                }
+            }
+
+            mAppTransition.postAnimationCallback();
+            mAppTransition.clear();
+
+            mOpeningApps.clear();
+            mClosingApps.clear();
+
+            // This has changed the visibility of windows, so perform
+            // a new layout to get them all up-to-date.
+            changes |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT
+                    | WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+            getDefaultDisplayContentLocked().layoutNeeded = true;
+
+            // TODO(multidisplay): IMEs are only supported on the default display.
+            if (windows == getDefaultWindowListLocked()
+                    && !moveInputMethodWindowsIfNeededLocked(true)) {
+                assignLayersLocked(windows);
+            }
+            updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES, false /*updateInputWindows*/);
+            mFocusMayChange = false;
+        }
+
+        return changes;
+    }
+
+    /**
+     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
+     * @return bitmap indicating if another pass through layout must be made.
+     */
+    private int handleAnimatingStoppedAndTransitionLocked() {
+        int changes = 0;
+
+        mAppTransition.setIdle();
+        // Restore window app tokens to the ActivityManager views
+        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                wtoken.sendingToBottom = false;
+            }
+        }
+        rebuildAppWindowListLocked();
+
+        changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT;
+        if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                "Wallpaper layer changed: assigning layers + relayout");
+        moveInputMethodWindowsIfNeededLocked(true);
+        mInnerFields.mWallpaperMayChange = true;
+        // Since the window list has been rebuilt, focus might
+        // have to be recomputed since the actual order of windows
+        // might have changed again.
+        mFocusMayChange = true;
+
+        return changes;
+    }
+
+    private void updateResizingWindows(final WindowState w) {
+        final WindowStateAnimator winAnimator = w.mWinAnimator;
+        if (w.mHasSurface && w.mLayoutSeq == mLayoutSeq) {
+            w.setInsetsChanged();
+            boolean configChanged = w.isConfigChanged();
+            if (DEBUG_CONFIGURATION && configChanged) {
+                Slog.v(TAG, "Win " + w + " config changed: "
+                        + mCurConfiguration);
+            }
+            if (localLOGV) Slog.v(TAG, "Resizing " + w
+                    + ": configChanged=" + configChanged
+                    + " last=" + w.mLastFrame + " frame=" + w.mFrame);
+            w.mLastFrame.set(w.mFrame);
+            if (w.mContentInsetsChanged
+                    || w.mVisibleInsetsChanged
+                    || winAnimator.mSurfaceResized
+                    || configChanged) {
+                if (DEBUG_RESIZE || DEBUG_ORIENTATION) {
+                    Slog.v(TAG, "Resize reasons for w=" + w + ": "
+                            + " contentInsetsChanged=" + w.mContentInsetsChanged
+                            + " " + w.mContentInsets.toShortString()
+                            + " visibleInsetsChanged=" + w.mVisibleInsetsChanged
+                            + " " + w.mVisibleInsets.toShortString()
+                            + " surfaceResized=" + winAnimator.mSurfaceResized
+                            + " configChanged=" + configChanged);
+                }
+
+                w.mLastOverscanInsets.set(w.mOverscanInsets);
+                w.mLastContentInsets.set(w.mContentInsets);
+                w.mLastVisibleInsets.set(w.mVisibleInsets);
+                makeWindowFreezingScreenIfNeededLocked(w);
+                // If the orientation is changing, then we need to
+                // hold off on unfreezing the display until this
+                // window has been redrawn; to do that, we need
+                // to go through the process of getting informed
+                // by the application when it has finished drawing.
+                if (w.mOrientationChanging) {
+                    if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Orientation start waiting for draw mDrawState=DRAW_PENDING in "
+                            + w + ", surface " + winAnimator.mSurfaceControl);
+                    winAnimator.mDrawState = WindowStateAnimator.DRAW_PENDING;
+                    if (w.mAppToken != null) {
+                        w.mAppToken.allDrawn = false;
+                        w.mAppToken.deferClearAllDrawn = false;
+                    }
+                }
+                if (!mResizingWindows.contains(w)) {
+                    if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Resizing window " + w + " to " + winAnimator.mSurfaceW
+                            + "x" + winAnimator.mSurfaceH);
+                    mResizingWindows.add(w);
+                }
+            } else if (w.mOrientationChanging) {
+                if (w.isDrawnLw()) {
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Orientation not waiting for draw in "
+                            + w + ", surface " + winAnimator.mSurfaceControl);
+                    w.mOrientationChanging = false;
+                    w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                            - mDisplayFreezeTime);
+                }
+            }
+        }
+    }
+
+    /**
+     * Extracted from {@link #performLayoutAndPlaceSurfacesLockedInner} to reduce size of method.
+     *
+     * @param w WindowState this method is applied to.
+     * @param currentTime The time which animations use for calculating transitions.
+     * @param innerDw Width of app window.
+     * @param innerDh Height of app window.
+     */
+    private void handleNotObscuredLocked(final WindowState w, final long currentTime,
+                                         final int innerDw, final int innerDh) {
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+        final int attrFlags = attrs.flags;
+        final boolean canBeSeen = w.isDisplayedLw();
+        final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
+
+        if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+            // This window completely covers everything behind it,
+            // so we want to leave all of them as undimmed (for
+            // performance reasons).
+            mInnerFields.mObscured = true;
+        }
+
+        if (w.mHasSurface) {
+            if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) {
+                mInnerFields.mHoldScreen = w.mSession;
+            }
+            if (!mInnerFields.mSyswin && w.mAttrs.screenBrightness >= 0
+                    && mInnerFields.mScreenBrightness < 0) {
+                mInnerFields.mScreenBrightness = w.mAttrs.screenBrightness;
+            }
+            if (!mInnerFields.mSyswin && w.mAttrs.buttonBrightness >= 0
+                    && mInnerFields.mButtonBrightness < 0) {
+                mInnerFields.mButtonBrightness = w.mAttrs.buttonBrightness;
+            }
+            if (!mInnerFields.mSyswin && w.mAttrs.userActivityTimeout >= 0
+                    && mInnerFields.mUserActivityTimeout < 0) {
+                mInnerFields.mUserActivityTimeout = w.mAttrs.userActivityTimeout;
+            }
+
+            final int type = attrs.type;
+            if (canBeSeen
+                    && (type == TYPE_SYSTEM_DIALOG
+                     || type == TYPE_RECENTS_OVERLAY
+                     || type == TYPE_KEYGUARD
+                     || type == TYPE_SYSTEM_ERROR)) {
+                mInnerFields.mSyswin = true;
+            }
+
+            if (canBeSeen) {
+                // This function assumes that the contents of the default display are
+                // processed first before secondary displays.
+                final DisplayContent displayContent = w.mDisplayContent;
+                if (displayContent.isDefaultDisplay) {
+                    // While a dream or keyguard is showing, obscure ordinary application
+                    // content on secondary displays (by forcibly enabling mirroring unless
+                    // there is other content we want to show) but still allow opaque
+                    // keyguard dialogs to be shown.
+                    if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
+                        mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true;
+                    }
+                    mInnerFields.mDisplayHasContent = true;
+                } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays
+                        || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) {
+                    // Allow full screen keyguard presentation dialogs to be seen.
+                    mInnerFields.mDisplayHasContent = true;
+                }
+            }
+        }
+    }
+
+    private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) {
+        final WindowManager.LayoutParams attrs = w.mAttrs;
+        if ((attrs.flags & FLAG_DIM_BEHIND) != 0
+                && w.isDisplayedLw()
+                && !w.mExiting) {
+            final WindowStateAnimator winAnimator = w.mWinAnimator;
+            final TaskStack stack = w.getStack();
+            stack.setDimmingTag();
+            if (!stack.isDimming(winAnimator)) {
+                if (localLOGV) Slog.v(TAG, "Win " + w + " start dimming.");
+                stack.startDimmingIfNeeded(winAnimator);
+            }
+        }
+    }
+
+    private void updateAllDrawnLocked(DisplayContent displayContent) {
+        // See if any windows have been drawn, so they (and others
+        // associated with them) can now be shown.
+        final ArrayList<Task> tasks = displayContent.getTasks();
+        final int numTasks = tasks.size();
+        for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) {
+            final AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+            final int numTokens = tokens.size();
+            for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
+                final AppWindowToken wtoken = tokens.get(tokenNdx);
+                if (!wtoken.allDrawn) {
+                    int numInteresting = wtoken.numInterestingWindows;
+                    if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) {
+                        if (DEBUG_VISIBILITY) Slog.v(TAG,
+                                "allDrawn: " + wtoken
+                                + " interesting=" + numInteresting
+                                + " drawn=" + wtoken.numDrawnWindows);
+                        wtoken.allDrawn = true;
+                        mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget();
+                    }
+                }
+            }
+        }
+    }
+
+    // "Something has changed!  Let's make it correct now."
+    private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
+        if (DEBUG_WINDOW_TRACE) {
+            Slog.v(TAG, "performLayoutAndPlaceSurfacesLockedInner: entry. Called by "
+                    + Debug.getCallers(3));
+        }
+
+        final long currentTime = SystemClock.uptimeMillis();
+
+        int i;
+
+        if (mFocusMayChange) {
+            mFocusMayChange = false;
+            updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
+                    false /*updateInputWindows*/);
+        }
+
+        // Initialize state of exiting tokens.
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+            for (i=displayContent.mExitingTokens.size()-1; i>=0; i--) {
+                displayContent.mExitingTokens.get(i).hasVisible = false;
+            }
+
+            // Initialize state of exiting applications.
+            for (i=displayContent.mExitingAppTokens.size()-1; i>=0; i--) {
+                displayContent.mExitingAppTokens.get(i).hasVisible = false;
+            }
+        }
+
+        mInnerFields.mHoldScreen = null;
+        mInnerFields.mScreenBrightness = -1;
+        mInnerFields.mButtonBrightness = -1;
+        mInnerFields.mUserActivityTimeout = -1;
+        mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;
+
+        mTransactionSequence++;
+
+        final DisplayContent defaultDisplay = getDefaultDisplayContentLocked();
+        final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
+        final int defaultDw = defaultInfo.logicalWidth;
+        final int defaultDh = defaultInfo.logicalHeight;
+
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
+        SurfaceControl.openTransaction();
+        try {
+
+            if (mWatermark != null) {
+                mWatermark.positionSurface(defaultDw, defaultDh);
+            }
+            if (mStrictModeFlash != null) {
+                mStrictModeFlash.positionSurface(defaultDw, defaultDh);
+            }
+
+            boolean focusDisplayed = false;
+
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                boolean updateAllDrawn = false;
+                WindowList windows = displayContent.getWindowList();
+                DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                final int displayId = displayContent.getDisplayId();
+                final int dw = displayInfo.logicalWidth;
+                final int dh = displayInfo.logicalHeight;
+                final int innerDw = displayInfo.appWidth;
+                final int innerDh = displayInfo.appHeight;
+                final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+
+                // Reset for each display.
+                mInnerFields.mDisplayHasContent = false;
+
+                int repeats = 0;
+                do {
+                    repeats++;
+                    if (repeats > 6) {
+                        Slog.w(TAG, "Animation repeat aborted after too many iterations");
+                        displayContent.layoutNeeded = false;
+                        break;
+                    }
+
+                    if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner",
+                        displayContent.pendingLayoutChanges);
+
+                    if ((displayContent.pendingLayoutChanges &
+                            WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
+                            (adjustWallpaperWindowsLocked() &
+                                    ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
+                        assignLayersLocked(windows);
+                        displayContent.layoutNeeded = true;
+                    }
+
+                    if (isDefaultDisplay && (displayContent.pendingLayoutChanges
+                            & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
+                        if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
+                        if (updateOrientationFromAppTokensLocked(true)) {
+                            displayContent.layoutNeeded = true;
+                            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+                        }
+                    }
+
+                    if ((displayContent.pendingLayoutChanges
+                            & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
+                        displayContent.layoutNeeded = true;
+                    }
+
+                    // FIRST LOOP: Perform a layout, if needed.
+                    if (repeats < 4) {
+                        performLayoutLockedInner(displayContent, repeats == 1,
+                                false /*updateInputWindows*/);
+                    } else {
+                        Slog.w(TAG, "Layout repeat skipped after too many iterations");
+                    }
+
+                    // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think
+                    // it is animating.
+                    displayContent.pendingLayoutChanges = 0;
+
+                    if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number "
+                            + mLayoutRepeatCount, displayContent.pendingLayoutChanges);
+
+                    if (isDefaultDisplay) {
+                        mPolicy.beginPostLayoutPolicyLw(dw, dh);
+                        for (i = windows.size() - 1; i >= 0; i--) {
+                            WindowState w = windows.get(i);
+                            if (w.mHasSurface) {
+                                mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs);
+                            }
+                        }
+                        displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw();
+                        if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats(
+                            "after finishPostLayoutPolicyLw", displayContent.pendingLayoutChanges);
+                    }
+                } while (displayContent.pendingLayoutChanges != 0);
+
+                mInnerFields.mObscured = false;
+                mInnerFields.mSyswin = false;
+                displayContent.resetDimming();
+
+                // Only used if default window
+                final boolean someoneLosingFocus = !mLosingFocus.isEmpty();
+
+                final int N = windows.size();
+                for (i=N-1; i>=0; i--) {
+                    WindowState w = windows.get(i);
+
+                    final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured;
+
+                    // Update effect.
+                    w.mObscured = mInnerFields.mObscured;
+                    if (!mInnerFields.mObscured) {
+                        handleNotObscuredLocked(w, currentTime, innerDw, innerDh);
+                    }
+
+                    if (!w.getStack().testDimmingTag()) {
+                        handleFlagDimBehind(w, innerDw, innerDh);
+                    }
+
+                    if (isDefaultDisplay && obscuredChanged && (mWallpaperTarget == w)
+                            && w.isVisibleLw()) {
+                        // This is the wallpaper target and its obscured state
+                        // changed... make sure the current wallaper's visibility
+                        // has been updated accordingly.
+                        updateWallpaperVisibilityLocked();
+                    }
+
+                    final WindowStateAnimator winAnimator = w.mWinAnimator;
+
+                    // If the window has moved due to its containing
+                    // content frame changing, then we'd like to animate
+                    // it.
+                    if (w.mHasSurface && w.shouldAnimateMove()) {
+                        // Frame has moved, containing content frame
+                        // has also moved, and we're not currently animating...
+                        // let's do something.
+                        Animation a = AnimationUtils.loadAnimation(mContext,
+                                com.android.internal.R.anim.window_move_from_decor);
+                        winAnimator.setAnimation(a);
+                        winAnimator.mAnimDw = w.mLastFrame.left - w.mFrame.left;
+                        winAnimator.mAnimDh = w.mLastFrame.top - w.mFrame.top;
+                        try {
+                            w.mClient.moved(w.mFrame.left, w.mFrame.top);
+                        } catch (RemoteException e) {
+                        }
+                    }
+
+                    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - done placing");
+                    w.mContentChanged = false;
+
+                    // Moved from updateWindowsAndWallpaperLocked().
+                    if (w.mHasSurface) {
+                        // Take care of the window being ready to display.
+                        final boolean committed =
+                                winAnimator.commitFinishDrawingLocked(currentTime);
+                        if (isDefaultDisplay && committed) {
+                            if (w.mAttrs.type == TYPE_DREAM) {
+                                // HACK: When a dream is shown, it may at that
+                                // point hide the lock screen.  So we need to
+                                // redo the layout to let the phone window manager
+                                // make this happen.
+                                displayContent.pendingLayoutChanges |=
+                                        WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+                                if (DEBUG_LAYOUT_REPEATS) {
+                                    debugLayoutRepeats(
+                                        "dream and commitFinishDrawingLocked true",
+                                        displayContent.pendingLayoutChanges);
+                                }
+                            }
+                            if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
+                                if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG,
+                                        "First draw done in potential wallpaper target " + w);
+                                mInnerFields.mWallpaperMayChange = true;
+                                displayContent.pendingLayoutChanges |=
+                                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+                                if (DEBUG_LAYOUT_REPEATS) {
+                                    debugLayoutRepeats(
+                                        "wallpaper and commitFinishDrawingLocked true",
+                                        displayContent.pendingLayoutChanges);
+                                }
+                            }
+                        }
+
+                        winAnimator.setSurfaceBoundariesLocked(recoveringMemory);
+
+                        final AppWindowToken atoken = w.mAppToken;
+                        if (DEBUG_STARTING_WINDOW && atoken != null
+                                && w == atoken.startingWindow) {
+                            Slog.d(TAG, "updateWindows: starting " + w + " isOnScreen="
+                                + w.isOnScreen() + " allDrawn=" + atoken.allDrawn
+                                + " freezingScreen=" + atoken.mAppAnimator.freezingScreen);
+                        }
+                        if (atoken != null
+                                && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) {
+                            if (atoken.lastTransactionSequence != mTransactionSequence) {
+                                atoken.lastTransactionSequence = mTransactionSequence;
+                                atoken.numInterestingWindows = atoken.numDrawnWindows = 0;
+                                atoken.startingDisplayed = false;
+                            }
+                            if ((w.isOnScreen() || winAnimator.mAttrType == TYPE_BASE_APPLICATION)
+                                    && !w.mExiting && !w.mDestroying) {
+                                if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) {
+                                    Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
+                                            + ", isAnimating=" + winAnimator.isAnimating());
+                                    if (!w.isDrawnLw()) {
+                                        Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceControl
+                                                + " pv=" + w.mPolicyVisibility
+                                                + " mDrawState=" + winAnimator.mDrawState
+                                                + " ah=" + w.mAttachedHidden
+                                                + " th=" + atoken.hiddenRequested
+                                                + " a=" + winAnimator.mAnimating);
+                                    }
+                                }
+                                if (w != atoken.startingWindow) {
+                                    if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) {
+                                        atoken.numInterestingWindows++;
+                                        if (w.isDrawnLw()) {
+                                            atoken.numDrawnWindows++;
+                                            if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG,
+                                                    "tokenMayBeDrawn: " + atoken
+                                                    + " freezingScreen=" + atoken.mAppAnimator.freezingScreen
+                                                    + " mAppFreezing=" + w.mAppFreezing);
+                                            updateAllDrawn = true;
+                                        }
+                                    }
+                                } else if (w.isDrawnLw()) {
+                                    atoken.startingDisplayed = true;
+                                }
+                            }
+                        }
+                    }
+
+                    if (isDefaultDisplay && someoneLosingFocus && (w == mCurrentFocus)
+                            && w.isDisplayedLw()) {
+                        focusDisplayed = true;
+                    }
+
+                    updateResizingWindows(w);
+                }
+
+                mDisplayManagerService.setDisplayHasContent(displayId,
+                        mInnerFields.mDisplayHasContent,
+                        true /* inTraversal, must call performTraversalInTrans... below */);
+
+                getDisplayContentLocked(displayId).stopDimmingIfNeeded();
+
+                if (updateAllDrawn) {
+                    updateAllDrawnLocked(displayContent);
+                }
+            }
+
+            if (focusDisplayed) {
+                mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
+            }
+
+            // Give the display manager a chance to adjust properties
+            // like display rotation if it needs to.
+            mDisplayManagerService.performTraversalInTransactionFromWindowManager();
+
+        } catch (RuntimeException e) {
+            Log.wtf(TAG, "Unhandled exception in Window Manager", e);
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
+        }
+
+        final WindowList defaultWindows = defaultDisplay.getWindowList();
+
+        // If we are ready to perform an app transition, check through
+        // all of the app tokens to be shown and see if they are ready
+        // to go.
+        if (mAppTransition.isReady()) {
+            defaultDisplay.pendingLayoutChanges |= handleAppTransitionReadyLocked(defaultWindows);
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked",
+                    defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (!mAnimator.mAnimating && mAppTransition.isRunning()) {
+            // We have finished the animation of an app transition.  To do
+            // this, we have delayed a lot of operations like showing and
+            // hiding apps, moving apps in Z-order, etc.  The app token list
+            // reflects the correct Z-order, but the window list may now
+            // be out of sync with it.  So here we will just rebuild the
+            // entire app window list.  Fun!
+            defaultDisplay.pendingLayoutChanges |= handleAnimatingStoppedAndTransitionLocked();
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock",
+                defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (mInnerFields.mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0
+                && !mAppTransition.isReady()) {
+            // At this point, there was a window with a wallpaper that
+            // was force hiding other windows behind it, but now it
+            // is going away.  This may be simple -- just animate
+            // away the wallpaper and its window -- or it may be
+            // hard -- the wallpaper now needs to be shown behind
+            // something that was hidden.
+            defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked",
+                defaultDisplay.pendingLayoutChanges);
+        }
+        mInnerFields.mWallpaperForceHidingChanged = false;
+
+        if (mInnerFields.mWallpaperMayChange) {
+            if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change!  Adjusting");
+            defaultDisplay.pendingLayoutChanges |=
+                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange",
+                    defaultDisplay.pendingLayoutChanges);
+        }
+
+        if (mFocusMayChange) {
+            mFocusMayChange = false;
+            if (updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
+                    false /*updateInputWindows*/)) {
+                defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+            }
+        }
+
+        if (needsLayout()) {
+            defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+            if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded",
+                    defaultDisplay.pendingLayoutChanges);
+        }
+
+        for (i = mResizingWindows.size() - 1; i >= 0; i--) {
+            WindowState win = mResizingWindows.get(i);
+            if (win.mAppFreezing) {
+                // Don't remove this window until rotation has completed.
+                continue;
+            }
+            final WindowStateAnimator winAnimator = win.mWinAnimator;
+            try {
+                if (DEBUG_RESIZE || DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Reporting new frame to " + win + ": " + win.mCompatFrame);
+                int diff = 0;
+                boolean configChanged = win.isConfigChanged();
+                if ((DEBUG_RESIZE || DEBUG_ORIENTATION || DEBUG_CONFIGURATION)
+                        && configChanged) {
+                    Slog.i(TAG, "Sending new config to window " + win + ": "
+                            + winAnimator.mSurfaceW + "x" + winAnimator.mSurfaceH
+                            + " / " + mCurConfiguration + " / 0x"
+                            + Integer.toHexString(diff));
+                }
+                win.setConfiguration(mCurConfiguration);
+                if (DEBUG_ORIENTATION &&
+                        winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING) Slog.i(
+                        TAG, "Resizing " + win + " WITH DRAW PENDING");
+                final IWindow client = win.mClient;
+                final Rect frame = win.mFrame;
+                final Rect overscanInsets = win.mLastOverscanInsets;
+                final Rect contentInsets = win.mLastContentInsets;
+                final Rect visibleInsets = win.mLastVisibleInsets;
+                final boolean reportDraw
+                        = winAnimator.mDrawState == WindowStateAnimator.DRAW_PENDING;
+                final Configuration newConfig = configChanged ? win.mConfiguration : null;
+                if (win.mClient instanceof IWindow.Stub) {
+                    // To prevent deadlock simulate one-way call if win.mClient is a local object.
+                    mH.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            try {
+                                client.resized(frame, overscanInsets, contentInsets,
+                                        visibleInsets, reportDraw, newConfig);
+                            } catch (RemoteException e) {
+                                // Not a remote call, RemoteException won't be raised.
+                            }
+                        }
+                    });
+                } else {
+                   client.resized(frame, overscanInsets, contentInsets, visibleInsets, reportDraw,
+                           newConfig);
+                }
+                win.mOverscanInsetsChanged = false;
+                win.mContentInsetsChanged = false;
+                win.mVisibleInsetsChanged = false;
+                winAnimator.mSurfaceResized = false;
+            } catch (RemoteException e) {
+                win.mOrientationChanging = false;
+                win.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
+                        - mDisplayFreezeTime);
+            }
+            mResizingWindows.remove(i);
+        }
+
+        if (DEBUG_ORIENTATION && mDisplayFrozen) Slog.v(TAG,
+                "With display frozen, orientationChangeComplete="
+                + mInnerFields.mOrientationChangeComplete);
+        if (mInnerFields.mOrientationChangeComplete) {
+            if (mWindowsFreezingScreen) {
+                mWindowsFreezingScreen = false;
+                mLastFinishedFreezeSource = mInnerFields.mLastWindowFreezeSource;
+                mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT);
+            }
+            stopFreezingDisplayLocked();
+        }
+
+        // Destroy the surface of any windows that are no longer visible.
+        boolean wallpaperDestroyed = false;
+        i = mDestroySurface.size();
+        if (i > 0) {
+            do {
+                i--;
+                WindowState win = mDestroySurface.get(i);
+                win.mDestroying = false;
+                if (mInputMethodWindow == win) {
+                    mInputMethodWindow = null;
+                }
+                if (win == mWallpaperTarget) {
+                    wallpaperDestroyed = true;
+                }
+                win.mWinAnimator.destroySurfaceLocked();
+            } while (i > 0);
+            mDestroySurface.clear();
+        }
+
+        // Time to remove any exiting tokens?
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+            ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens;
+            for (i = exitingTokens.size() - 1; i >= 0; i--) {
+                WindowToken token = exitingTokens.get(i);
+                if (!token.hasVisible) {
+                    exitingTokens.remove(i);
+                    if (token.windowType == TYPE_WALLPAPER) {
+                        mWallpaperTokens.remove(token);
+                    }
+                }
+            }
+
+            // Time to remove any exiting applications?
+            AppTokenList exitingAppTokens = displayContent.mExitingAppTokens;
+            for (i = exitingAppTokens.size() - 1; i >= 0; i--) {
+                AppWindowToken token = exitingAppTokens.get(i);
+                if (!token.hasVisible && !mClosingApps.contains(token)) {
+                    // Make sure there is no animation running on this token,
+                    // so any windows associated with it will be removed as
+                    // soon as their animations are complete
+                    token.mAppAnimator.clearAnimation();
+                    token.mAppAnimator.animating = false;
+                    if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG,
+                            "performLayout: App token exiting now removed" + token);
+                    final Task task = mTaskIdToTask.get(token.groupId);
+                    if (task != null && task.removeAppToken(token)) {
+                        mTaskIdToTask.delete(token.groupId);
+                    }
+                    exitingAppTokens.remove(i);
+                }
+            }
+        }
+
+        if (!mAnimator.mAnimating && mRelayoutWhileAnimating.size() > 0) {
+            for (int j=mRelayoutWhileAnimating.size()-1; j>=0; j--) {
+                try {
+                    mRelayoutWhileAnimating.get(j).mClient.doneAnimating();
+                } catch (RemoteException e) {
+                }
+            }
+            mRelayoutWhileAnimating.clear();
+        }
+
+        if (wallpaperDestroyed) {
+            defaultDisplay.pendingLayoutChanges |=
+                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+            defaultDisplay.layoutNeeded = true;
+        }
+
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+            if (displayContent.pendingLayoutChanges != 0) {
+                displayContent.layoutNeeded = true;
+            }
+        }
+
+        // Finally update all input windows now that the window changes have stabilized.
+        mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+        setHoldScreenLocked(mInnerFields.mHoldScreen);
+        if (!mDisplayFrozen) {
+            if (mInnerFields.mScreenBrightness < 0 || mInnerFields.mScreenBrightness > 1.0f) {
+                mPowerManager.setScreenBrightnessOverrideFromWindowManager(-1);
+            } else {
+                mPowerManager.setScreenBrightnessOverrideFromWindowManager(
+                        toBrightnessOverride(mInnerFields.mScreenBrightness));
+            }
+            if (mInnerFields.mButtonBrightness < 0 || mInnerFields.mButtonBrightness > 1.0f) {
+                mPowerManager.setButtonBrightnessOverrideFromWindowManager(-1);
+            } else {
+                mPowerManager.setButtonBrightnessOverrideFromWindowManager(
+                        toBrightnessOverride(mInnerFields.mButtonBrightness));
+            }
+            mPowerManager.setUserActivityTimeoutOverrideFromWindowManager(
+                    mInnerFields.mUserActivityTimeout);
+        }
+
+        if (mTurnOnScreen) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "Turning screen on after layout!");
+            mPowerManager.wakeUp(SystemClock.uptimeMillis());
+            mTurnOnScreen = false;
+        }
+
+        if (mInnerFields.mUpdateRotation) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
+            if (updateRotationUncheckedLocked(false)) {
+                mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+            } else {
+                mInnerFields.mUpdateRotation = false;
+            }
+        }
+
+        if (mInnerFields.mOrientationChangeComplete && !defaultDisplay.layoutNeeded
+                && !mInnerFields.mUpdateRotation) {
+            checkDrawnWindowsLocked();
+        }
+
+        final int N = mPendingRemove.size();
+        if (N > 0) {
+            if (mPendingRemoveTmp.length < N) {
+                mPendingRemoveTmp = new WindowState[N+10];
+            }
+            mPendingRemove.toArray(mPendingRemoveTmp);
+            mPendingRemove.clear();
+            DisplayContentList displayList = new DisplayContentList();
+            for (i = 0; i < N; i++) {
+                WindowState w = mPendingRemoveTmp[i];
+                removeWindowInnerLocked(w.mSession, w);
+                if (!displayList.contains(w.mDisplayContent)) {
+                    displayList.add(w.mDisplayContent);
+                }
+            }
+
+            for (DisplayContent displayContent : displayList) {
+                assignLayersLocked(displayContent.getWindowList());
+                displayContent.layoutNeeded = true;
+            }
+        }
+
+        setFocusedStackFrame();
+
+        // Check to see if we are now in a state where the screen should
+        // be enabled, because the window obscured flags have changed.
+        enableScreenIfNeededLocked();
+
+        scheduleAnimationLocked();
+
+        if (DEBUG_WINDOW_TRACE) {
+            Slog.e(TAG, "performLayoutAndPlaceSurfacesLockedInner exit: animating="
+                    + mAnimator.mAnimating);
+        }
+    }
+
+    private int toBrightnessOverride(float value) {
+        return (int)(value * PowerManager.BRIGHTNESS_ON);
+    }
+
+    void checkDrawnWindowsLocked() {
+        if (mWaitingForDrawn.size() > 0) {
+            for (int j=mWaitingForDrawn.size()-1; j>=0; j--) {
+                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(j);
+                WindowState win = pair.first;
+                //Slog.i(TAG, "Waiting for drawn " + win + ": removed="
+                //        + win.mRemoved + " visible=" + win.isVisibleLw()
+                //        + " shown=" + win.mSurfaceShown);
+                if (win.mRemoved) {
+                    // Window has been removed; no draw will now happen, so stop waiting.
+                    Slog.w(TAG, "Aborted waiting for drawn: " + pair.first);
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    mWaitingForDrawn.remove(pair);
+                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                } else if (win.mWinAnimator.mSurfaceShown) {
+                    // Window is now drawn (and shown).
+                    try {
+                        pair.second.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                    mWaitingForDrawn.remove(pair);
+                    mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                }
+            }
+        }
+    }
+
+    @Override
+    public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) {
+        if (token != null && callback != null) {
+            synchronized (mWindowMap) {
+                WindowState win = windowForClientLocked(null, token, true);
+                if (win != null) {
+                    Pair<WindowState, IRemoteCallback> pair =
+                            new Pair<WindowState, IRemoteCallback>(win, callback);
+                    Message m = mH.obtainMessage(H.WAITING_FOR_DRAWN_TIMEOUT, pair);
+                    mH.sendMessageDelayed(m, 2000);
+                    mWaitingForDrawn.add(pair);
+                    checkDrawnWindowsLocked();
+                    return true;
+                }
+                Slog.i(TAG, "waitForWindowDrawn: win null");
+            }
+        }
+        return false;
+    }
+
+    void setHoldScreenLocked(final Session newHoldScreen) {
+        final boolean hold = newHoldScreen != null;
+
+        if (hold && mHoldingScreenOn != newHoldScreen) {
+            mHoldingScreenWakeLock.setWorkSource(new WorkSource(newHoldScreen.mUid));
+        }
+        mHoldingScreenOn = newHoldScreen;
+
+        final boolean state = mHoldingScreenWakeLock.isHeld();
+        if (hold != state) {
+            if (hold) {
+                mHoldingScreenWakeLock.acquire();
+                mPolicy.keepScreenOnStartedLw();
+            } else {
+                mPolicy.keepScreenOnStoppedLw();
+                mHoldingScreenWakeLock.release();
+            }
+        }
+    }
+
+    @Override
+    public void requestTraversal() {
+        synchronized (mWindowMap) {
+            requestTraversalLocked();
+        }
+    }
+
+    void requestTraversalLocked() {
+        if (!mTraversalScheduled) {
+            mTraversalScheduled = true;
+            mH.sendEmptyMessage(H.DO_TRAVERSAL);
+        }
+    }
+
+    /** Note that Locked in this case is on mLayoutToAnim */
+    void scheduleAnimationLocked() {
+        if (!mAnimationScheduled) {
+            mAnimationScheduled = true;
+            mChoreographer.postCallback(
+                    Choreographer.CALLBACK_ANIMATION, mAnimator.mAnimationRunnable, null);
+        }
+    }
+
+    private boolean needsLayout() {
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+            if (displayContent.layoutNeeded) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean copyAnimToLayoutParamsLocked() {
+        boolean doRequest = false;
+
+        final int bulkUpdateParams = mAnimator.mBulkUpdateParams;
+        if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) {
+            mInnerFields.mUpdateRotation = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) {
+            mInnerFields.mWallpaperMayChange = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) {
+            mInnerFields.mWallpaperForceHidingChanged = true;
+            doRequest = true;
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE) == 0) {
+            mInnerFields.mOrientationChangeComplete = false;
+        } else {
+            mInnerFields.mOrientationChangeComplete = true;
+            mInnerFields.mLastWindowFreezeSource = mAnimator.mLastWindowFreezeSource;
+            if (mWindowsFreezingScreen) {
+                doRequest = true;
+            }
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_TURN_ON_SCREEN) != 0) {
+            mTurnOnScreen = true;
+        }
+        if ((bulkUpdateParams & LayoutFields.SET_WALLPAPER_ACTION_PENDING) != 0) {
+            mInnerFields.mWallpaperActionPending = true;
+        }
+
+        return doRequest;
+    }
+
+    /** If a window that has an animation specifying a colored background and the current wallpaper
+     * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to
+     * suddenly disappear. */
+    int adjustAnimationBackground(WindowStateAnimator winAnimator) {
+        WindowList windows = winAnimator.mWin.getWindowList();
+        for (int i = windows.size() - 1; i >= 0; --i) {
+            WindowState testWin = windows.get(i);
+            if (testWin.mIsWallpaper && testWin.isVisibleNow()) {
+                return testWin.mWinAnimator.mAnimLayer;
+            }
+        }
+        return winAnimator.mAnimLayer;
+    }
+
+    boolean reclaimSomeSurfaceMemoryLocked(WindowStateAnimator winAnimator, String operation,
+                                           boolean secure) {
+        final SurfaceControl surface = winAnimator.mSurfaceControl;
+        boolean leakedSurface = false;
+        boolean killedApps = false;
+
+        EventLog.writeEvent(EventLogTags.WM_NO_SURFACE_MEMORY, winAnimator.mWin.toString(),
+                winAnimator.mSession.mPid, operation);
+
+        if (mForceRemoves == null) {
+            mForceRemoves = new ArrayList<WindowState>();
+        }
+
+        long callingIdentity = Binder.clearCallingIdentity();
+        try {
+            // There was some problem...   first, do a sanity check of the
+            // window list to make sure we haven't left any dangling surfaces
+            // around.
+
+            Slog.i(TAG, "Out of memory for surface!  Looking for leaks...");
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                final int numWindows = windows.size();
+                for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                    final WindowState ws = windows.get(winNdx);
+                    WindowStateAnimator wsa = ws.mWinAnimator;
+                    if (wsa.mSurfaceControl != null) {
+                        if (!mSessions.contains(wsa.mSession)) {
+                            Slog.w(TAG, "LEAKED SURFACE (session doesn't exist): "
+                                    + ws + " surface=" + wsa.mSurfaceControl
+                                    + " token=" + ws.mToken
+                                    + " pid=" + ws.mSession.mPid
+                                    + " uid=" + ws.mSession.mUid);
+                            if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
+                            wsa.mSurfaceControl.destroy();
+                            wsa.mSurfaceShown = false;
+                            wsa.mSurfaceControl = null;
+                            ws.mHasSurface = false;
+                            mForceRemoves.add(ws);
+                            leakedSurface = true;
+                        } else if (ws.mAppToken != null && ws.mAppToken.clientHidden) {
+                            Slog.w(TAG, "LEAKED SURFACE (app token hidden): "
+                                    + ws + " surface=" + wsa.mSurfaceControl
+                                    + " token=" + ws.mAppToken);
+                            if (SHOW_TRANSACTIONS) logSurface(ws, "LEAK DESTROY", null);
+                            wsa.mSurfaceControl.destroy();
+                            wsa.mSurfaceShown = false;
+                            wsa.mSurfaceControl = null;
+                            ws.mHasSurface = false;
+                            leakedSurface = true;
+                        }
+                    }
+                }
+            }
+
+            if (!leakedSurface) {
+                Slog.w(TAG, "No leaked surfaces; killing applicatons!");
+                SparseIntArray pidCandidates = new SparseIntArray();
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList();
+                    final int numWindows = windows.size();
+                    for (int winNdx = 0; winNdx < numWindows; ++winNdx) {
+                        final WindowState ws = windows.get(winNdx);
+                        if (mForceRemoves.contains(ws)) {
+                            continue;
+                        }
+                        WindowStateAnimator wsa = ws.mWinAnimator;
+                        if (wsa.mSurfaceControl != null) {
+                            pidCandidates.append(wsa.mSession.mPid, wsa.mSession.mPid);
+                        }
+                    }
+                    if (pidCandidates.size() > 0) {
+                        int[] pids = new int[pidCandidates.size()];
+                        for (int i=0; i<pids.length; i++) {
+                            pids[i] = pidCandidates.keyAt(i);
+                        }
+                        try {
+                            if (mActivityManager.killPids(pids, "Free memory", secure)) {
+                                killedApps = true;
+                            }
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            }
+
+            if (leakedSurface || killedApps) {
+                // We managed to reclaim some memory, so get rid of the trouble
+                // surface and ask the app to request another one.
+                Slog.w(TAG, "Looks like we have reclaimed some memory, clearing surface for retry.");
+                if (surface != null) {
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) logSurface(winAnimator.mWin,
+                            "RECOVER DESTROY", null);
+                    surface.destroy();
+                    winAnimator.mSurfaceShown = false;
+                    winAnimator.mSurfaceControl = null;
+                    winAnimator.mWin.mHasSurface = false;
+                    scheduleRemoveStartingWindow(winAnimator.mWin.mAppToken);
+                }
+
+                try {
+                    winAnimator.mWin.mClient.dispatchGetNewSurface();
+                } catch (RemoteException e) {
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingIdentity);
+        }
+
+        return leakedSurface || killedApps;
+    }
+
+    private boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
+        WindowState newFocus = computeFocusedWindowLocked();
+        if (mCurrentFocus != newFocus) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
+            // This check makes sure that we don't already have the focus
+            // change message pending.
+            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
+            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
+            // TODO(multidisplay): Focused windows on default display only.
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final boolean imWindowChanged = moveInputMethodWindowsIfNeededLocked(
+                    mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
+                            && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES);
+            if (imWindowChanged) {
+                displayContent.layoutNeeded = true;
+                newFocus = computeFocusedWindowLocked();
+            }
+
+            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG, "Changing focus from " +
+                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
+            final WindowState oldFocus = mCurrentFocus;
+            mCurrentFocus = newFocus;
+            mLosingFocus.remove(newFocus);
+            int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
+
+            if (imWindowChanged && oldFocus != mInputMethodWindow) {
+                // Focus of the input method window changed. Perform layout if needed.
+                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                    performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows);
+                    focusChanged &= ~WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+                } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
+                    // Client will do the layout, but we need to assign layers
+                    // for handleNewWindowLocked() below.
+                    assignLayersLocked(displayContent.getWindowList());
+                }
+            }
+
+            if ((focusChanged & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
+                // The change in focus caused us to need to do a layout.  Okay.
+                displayContent.layoutNeeded = true;
+                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
+                    performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows);
+                }
+            }
+
+            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
+                // If we defer assigning layers, then the caller is responsible for
+                // doing this part.
+                finishUpdateFocusedWindowAfterAssignLayersLocked(updateInputWindows);
+            }
+
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+            return true;
+        }
+        return false;
+    }
+
+    private void finishUpdateFocusedWindowAfterAssignLayersLocked(boolean updateInputWindows) {
+        mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
+    }
+
+    private WindowState computeFocusedWindowLocked() {
+        if (mAnimator.mUniverseBackground != null
+                && mAnimator.mUniverseBackground.mWin.canReceiveKeys()) {
+            return mAnimator.mUniverseBackground.mWin;
+        }
+
+        final int displayCount = mDisplayContents.size();
+        for (int i = 0; i < displayCount; i++) {
+            final DisplayContent displayContent = mDisplayContents.valueAt(i);
+            WindowState win = findFocusedWindowLocked(displayContent);
+            if (win != null) {
+                return win;
+            }
+        }
+        return null;
+    }
+
+    private WindowState findFocusedWindowLocked(DisplayContent displayContent) {
+        final WindowList windows = displayContent.getWindowList();
+        for (int i = windows.size() - 1; i >= 0; i--) {
+            final WindowState win = windows.get(i);
+
+            if (localLOGV || DEBUG_FOCUS) Slog.v(
+                TAG, "Looking for focus: " + i
+                + " = " + win
+                + ", flags=" + win.mAttrs.flags
+                + ", canReceive=" + win.canReceiveKeys());
+
+            AppWindowToken wtoken = win.mAppToken;
+
+            // If this window's application has been removed, just skip it.
+            if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) {
+                if (DEBUG_FOCUS) Slog.v(TAG, "Skipping " + wtoken + " because "
+                        + (wtoken.removed ? "removed" : "sendingToBottom"));
+                continue;
+            }
+
+            if (!win.canReceiveKeys()) {
+                continue;
+            }
+
+            // Descend through all of the app tokens and find the first that either matches
+            // win.mAppToken (return win) or mFocusedApp (return null).
+            if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING &&
+                    mFocusedApp != null) {
+                ArrayList<Task> tasks = displayContent.getTasks();
+                for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+                    AppTokenList tokens = tasks.get(taskNdx).mAppTokens;
+                    int tokenNdx = tokens.size() - 1;
+                    for ( ; tokenNdx >= 0; --tokenNdx) {
+                        final AppWindowToken token = tokens.get(tokenNdx);
+                        if (wtoken == token) {
+                            break;
+                        }
+                        if (mFocusedApp == token) {
+                            // Whoops, we are below the focused app...  no focus for you!
+                            if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG,
+                                    "findFocusedWindow: Reached focused app=" + mFocusedApp);
+                            return null;
+                        }
+                    }
+                    if (tokenNdx >= 0) {
+                        // Early exit from loop, must have found the matching token.
+                        break;
+                    }
+                }
+            }
+
+            if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: Found new focus @ " + i +
+                        " = " + win);
+            return win;
+        }
+
+        if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: No focusable windows.");
+        return null;
+    }
+
+    private void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim) {
+        if (mDisplayFrozen) {
+            return;
+        }
+
+        if (!mDisplayReady || !mPolicy.isScreenOnFully()) {
+            // No need to freeze the screen before the system is ready or if
+            // the screen is off.
+            return;
+        }
+
+        mScreenFrozenLock.acquire();
+
+        mDisplayFrozen = true;
+        mDisplayFreezeTime = SystemClock.elapsedRealtime();
+        mLastFinishedFreezeSource = null;
+
+        mInputMonitor.freezeInputDispatchingLw();
+
+        // Clear the last input window -- that is just used for
+        // clean transitions between IMEs, and if we are freezing
+        // the screen then the whole world is changing behind the scenes.
+        mPolicy.setLastInputMethodWindowLw(null, null);
+
+        if (mAppTransition.isTransitionSet()) {
+            mAppTransition.freeze();
+        }
+
+        if (PROFILE_ORIENTATION) {
+            File file = new File("/data/system/frozen");
+            Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+        }
+
+        if (CUSTOM_SCREEN_ROTATION) {
+            mExitAnimId = exitAnim;
+            mEnterAnimId = enterAnim;
+            final DisplayContent displayContent = getDefaultDisplayContentLocked();
+            final int displayId = displayContent.getDisplayId();
+            ScreenRotationAnimation screenRotationAnimation =
+                    mAnimator.getScreenRotationAnimationLocked(displayId);
+            if (screenRotationAnimation != null) {
+                screenRotationAnimation.kill();
+            }
+
+            // TODO(multidisplay): rotation on main screen only.
+            screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
+                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced());
+            mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
+        }
+    }
+
+    private void stopFreezingDisplayLocked() {
+        if (!mDisplayFrozen) {
+            return;
+        }
+
+        if (mWaitingForConfig || mAppsFreezingScreen > 0 || mWindowsFreezingScreen
+                || mClientFreezingScreen) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG,
+                "stopFreezingDisplayLocked: Returning mWaitingForConfig=" + mWaitingForConfig
+                + ", mAppsFreezingScreen=" + mAppsFreezingScreen
+                + ", mWindowsFreezingScreen=" + mWindowsFreezingScreen
+                + ", mClientFreezingScreen=" + mClientFreezingScreen);
+            return;
+        }
+
+        mDisplayFrozen = false;
+        mLastDisplayFreezeDuration = (int)(SystemClock.elapsedRealtime() - mDisplayFreezeTime);
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("Screen frozen for ");
+        TimeUtils.formatDuration(mLastDisplayFreezeDuration, sb);
+        if (mLastFinishedFreezeSource != null) {
+            sb.append(" due to ");
+            sb.append(mLastFinishedFreezeSource);
+        }
+        Slog.i(TAG, sb.toString());
+        mH.removeMessages(H.APP_FREEZE_TIMEOUT);
+        mH.removeMessages(H.CLIENT_FREEZE_TIMEOUT);
+        if (PROFILE_ORIENTATION) {
+            Debug.stopMethodTracing();
+        }
+
+        boolean updateRotation = false;
+
+        final DisplayContent displayContent = getDefaultDisplayContentLocked();
+        final int displayId = displayContent.getDisplayId();
+        ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(displayId);
+        if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
+                && screenRotationAnimation.hasScreenshot()) {
+            if (DEBUG_ORIENTATION) Slog.i(TAG, "**** Dismissing screen rotation animation");
+            // TODO(multidisplay): rotation on main screen only.
+            DisplayInfo displayInfo = displayContent.getDisplayInfo();
+            // Get rotation animation again, with new top window
+            boolean isDimming = displayContent.isDimming();
+            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+                mExitAnimId = mEnterAnimId = 0;
+            }
+            if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+                    mTransitionAnimationScale, displayInfo.logicalWidth,
+                        displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
+                scheduleAnimationLocked();
+            } else {
+                screenRotationAnimation.kill();
+                screenRotationAnimation = null;
+                mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
+                updateRotation = true;
+            }
+        } else {
+            if (screenRotationAnimation != null) {
+                screenRotationAnimation.kill();
+                screenRotationAnimation = null;
+                mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation);
+            }
+            updateRotation = true;
+        }
+
+        mInputMonitor.thawInputDispatchingLw();
+
+        boolean configChanged;
+
+        // While the display is frozen we don't re-compute the orientation
+        // to avoid inconsistent states.  However, something interesting
+        // could have actually changed during that time so re-evaluate it
+        // now to catch that.
+        configChanged = updateOrientationFromAppTokensLocked(false);
+
+        // A little kludge: a lot could have happened while the
+        // display was frozen, so now that we are coming back we
+        // do a gc so that any remote references the system
+        // processes holds on others can be released if they are
+        // no longer needed.
+        mH.removeMessages(H.FORCE_GC);
+        mH.sendEmptyMessageDelayed(H.FORCE_GC, 2000);
+
+        mScreenFrozenLock.release();
+
+        if (updateRotation) {
+            if (DEBUG_ORIENTATION) Slog.d(TAG, "Performing post-rotate rotation");
+            configChanged |= updateRotationUncheckedLocked(false);
+        }
+
+        if (configChanged) {
+            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
+        }
+    }
+
+    static int getPropertyInt(String[] tokens, int index, int defUnits, int defDps,
+            DisplayMetrics dm) {
+        if (index < tokens.length) {
+            String str = tokens[index];
+            if (str != null && str.length() > 0) {
+                try {
+                    int val = Integer.parseInt(str);
+                    return val;
+                } catch (Exception e) {
+                }
+            }
+        }
+        if (defUnits == TypedValue.COMPLEX_UNIT_PX) {
+            return defDps;
+        }
+        int val = (int)TypedValue.applyDimension(defUnits, defDps, dm);
+        return val;
+    }
+
+    void createWatermarkInTransaction() {
+        if (mWatermark != null) {
+            return;
+        }
+
+        File file = new File("/system/etc/setup.conf");
+        FileInputStream in = null;
+        DataInputStream ind = null;
+        try {
+            in = new FileInputStream(file);
+            ind = new DataInputStream(in);
+            String line = ind.readLine();
+            if (line != null) {
+                String[] toks = line.split("%");
+                if (toks != null && toks.length > 0) {
+                    mWatermark = new Watermark(getDefaultDisplayContentLocked().getDisplay(),
+                            mRealDisplayMetrics, mFxSession, toks);
+                }
+            }
+        } catch (FileNotFoundException e) {
+        } catch (IOException e) {
+        } finally {
+            if (ind != null) {
+                try {
+                    ind.close();
+                } catch (IOException e) {
+                }
+            } else if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+    }
+
+    @Override
+    public void statusBarVisibilityChanged(int visibility) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Caller does not hold permission "
+                    + android.Manifest.permission.STATUS_BAR);
+        }
+
+        synchronized (mWindowMap) {
+            mLastStatusBarVisibility = visibility;
+            visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);
+            updateStatusBarVisibilityLocked(visibility);
+        }
+    }
+
+    // TOOD(multidisplay): StatusBar on multiple screens?
+    void updateStatusBarVisibilityLocked(int visibility) {
+        mInputManager.setSystemUiVisibility(visibility);
+        final WindowList windows = getDefaultWindowListLocked();
+        final int N = windows.size();
+        for (int i = 0; i < N; i++) {
+            WindowState ws = windows.get(i);
+            try {
+                int curValue = ws.mSystemUiVisibility;
+                int diff = curValue ^ visibility;
+                // We are only interested in differences of one of the
+                // clearable flags...
+                diff &= View.SYSTEM_UI_CLEARABLE_FLAGS;
+                // ...if it has actually been cleared.
+                diff &= ~visibility;
+                int newValue = (curValue&~diff) | (visibility&diff);
+                if (newValue != curValue) {
+                    ws.mSeq++;
+                    ws.mSystemUiVisibility = newValue;
+                }
+                if (newValue != curValue || ws.mAttrs.hasSystemUiListeners) {
+                    ws.mClient.dispatchSystemUiVisibilityChanged(ws.mSeq,
+                            visibility, newValue, diff);
+                }
+            } catch (RemoteException e) {
+                // so sorry
+            }
+        }
+    }
+
+    @Override
+    public void reevaluateStatusBarVisibility() {
+        synchronized (mWindowMap) {
+            int visibility = mPolicy.adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
+            updateStatusBarVisibilityLocked(visibility);
+            performLayoutAndPlaceSurfacesLocked();
+        }
+    }
+
+    @Override
+    public FakeWindow addFakeWindow(Looper looper,
+            InputEventReceiver.Factory inputEventReceiverFactory,
+            String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags,
+            boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) {
+        synchronized (mWindowMap) {
+            FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory,
+                    name, windowType,
+                    layoutParamsFlags, layoutParamsPrivateFlags, canReceiveKeys,
+                    hasFocus, touchFullscreen);
+            int i=0;
+            while (i<mFakeWindows.size()) {
+                if (mFakeWindows.get(i).mWindowLayer <= fw.mWindowLayer) {
+                    break;
+                }
+            }
+            mFakeWindows.add(i, fw);
+            mInputMonitor.updateInputWindowsLw(true);
+            return fw;
+        }
+    }
+
+    boolean removeFakeWindowLocked(FakeWindow window) {
+        synchronized (mWindowMap) {
+            if (mFakeWindows.remove(window)) {
+                mInputMonitor.updateInputWindowsLw(true);
+                return true;
+            }
+            return false;
+        }
+    }
+
+    // It is assumed that this method is called only by InputMethodManagerService.
+    public void saveLastInputMethodWindowForTransition() {
+        synchronized (mWindowMap) {
+            // TODO(multidisplay): Pass in the displayID.
+            DisplayContent displayContent = getDefaultDisplayContentLocked();
+            if (mInputMethodWindow != null) {
+                mPolicy.setLastInputMethodWindowLw(mInputMethodWindow, mInputMethodTarget);
+            }
+        }
+    }
+
+    @Override
+    public boolean hasNavigationBar() {
+        return mPolicy.hasNavigationBar();
+    }
+
+    @Override
+    public void lockNow(Bundle options) {
+        mPolicy.lockNow(options);
+    }
+
+    @Override
+    public boolean isSafeModeEnabled() {
+        return mSafeMode;
+    }
+
+    void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+        pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
+        mPolicy.dump("    ", pw, args);
+    }
+
+    void dumpAnimatorLocked(PrintWriter pw, String[] args, boolean dumpAll) {
+        pw.println("WINDOW MANAGER ANIMATOR STATE (dumpsys window animator)");
+        mAnimator.dumpLocked(pw, "    ", dumpAll);
+    }
+
+    void dumpTokensLocked(PrintWriter pw, boolean dumpAll) {
+        pw.println("WINDOW MANAGER TOKENS (dumpsys window tokens)");
+        if (mTokenMap.size() > 0) {
+            pw.println("  All tokens:");
+            Iterator<WindowToken> it = mTokenMap.values().iterator();
+            while (it.hasNext()) {
+                WindowToken token = it.next();
+                pw.print("  "); pw.print(token);
+                if (dumpAll) {
+                    pw.println(':');
+                    token.dump(pw, "    ");
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mWallpaperTokens.size() > 0) {
+            pw.println();
+            pw.println("  Wallpaper tokens:");
+            for (int i=mWallpaperTokens.size()-1; i>=0; i--) {
+                WindowToken token = mWallpaperTokens.get(i);
+                pw.print("  Wallpaper #"); pw.print(i);
+                        pw.print(' '); pw.print(token);
+                if (dumpAll) {
+                    pw.println(':');
+                    token.dump(pw, "    ");
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mFinishedStarting.size() > 0) {
+            pw.println();
+            pw.println("  Finishing start of application tokens:");
+            for (int i=mFinishedStarting.size()-1; i>=0; i--) {
+                WindowToken token = mFinishedStarting.get(i);
+                pw.print("  Finished Starting #"); pw.print(i);
+                        pw.print(' '); pw.print(token);
+                if (dumpAll) {
+                    pw.println(':');
+                    token.dump(pw, "    ");
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
+            pw.println();
+            if (mOpeningApps.size() > 0) {
+                pw.print("  mOpeningApps="); pw.println(mOpeningApps);
+            }
+            if (mClosingApps.size() > 0) {
+                pw.print("  mClosingApps="); pw.println(mClosingApps);
+            }
+        }
+    }
+
+    void dumpSessionsLocked(PrintWriter pw, boolean dumpAll) {
+        pw.println("WINDOW MANAGER SESSIONS (dumpsys window sessions)");
+        if (mSessions.size() > 0) {
+            Iterator<Session> it = mSessions.iterator();
+            while (it.hasNext()) {
+                Session s = it.next();
+                pw.print("  Session "); pw.print(s); pw.println(':');
+                s.dump(pw, "    ");
+            }
+        }
+    }
+
+    void dumpDisplayContentsLocked(PrintWriter pw, boolean dumpAll) {
+        pw.println("WINDOW MANAGER DISPLAY CONTENTS (dumpsys window displays)");
+        if (mDisplayReady) {
+            final int numDisplays = mDisplayContents.size();
+            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                displayContent.dump("  ", pw);
+            }
+        } else {
+            pw.println("  NO DISPLAY");
+        }
+    }
+
+    void dumpWindowsLocked(PrintWriter pw, boolean dumpAll,
+            ArrayList<WindowState> windows) {
+        pw.println("WINDOW MANAGER WINDOWS (dumpsys window windows)");
+        dumpWindowsNoHeaderLocked(pw, dumpAll, windows);
+    }
+
+    void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll,
+            ArrayList<WindowState> windows) {
+        final int numDisplays = mDisplayContents.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final WindowList windowList = mDisplayContents.valueAt(displayNdx).getWindowList();
+            for (int winNdx = windowList.size() - 1; winNdx >= 0; --winNdx) {
+                final WindowState w = windowList.get(winNdx);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Window #"); pw.print(winNdx); pw.print(' ');
+                            pw.print(w); pw.println(":");
+                    w.dump(pw, "    ", dumpAll || windows != null);
+                }
+            }
+        }
+        if (mInputMethodDialogs.size() > 0) {
+            pw.println();
+            pw.println("  Input method dialogs:");
+            for (int i=mInputMethodDialogs.size()-1; i>=0; i--) {
+                WindowState w = mInputMethodDialogs.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  IM Dialog #"); pw.print(i); pw.print(": "); pw.println(w);
+                }
+            }
+        }
+        if (mPendingRemove.size() > 0) {
+            pw.println();
+            pw.println("  Remove pending for:");
+            for (int i=mPendingRemove.size()-1; i>=0; i--) {
+                WindowState w = mPendingRemove.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Remove #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mForceRemoves != null && mForceRemoves.size() > 0) {
+            pw.println();
+            pw.println("  Windows force removing:");
+            for (int i=mForceRemoves.size()-1; i>=0; i--) {
+                WindowState w = mForceRemoves.get(i);
+                pw.print("  Removing #"); pw.print(i); pw.print(' ');
+                        pw.print(w);
+                if (dumpAll) {
+                    pw.println(":");
+                    w.dump(pw, "    ", true);
+                } else {
+                    pw.println();
+                }
+            }
+        }
+        if (mDestroySurface.size() > 0) {
+            pw.println();
+            pw.println("  Windows waiting to destroy their surface:");
+            for (int i=mDestroySurface.size()-1; i>=0; i--) {
+                WindowState w = mDestroySurface.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Destroy #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mLosingFocus.size() > 0) {
+            pw.println();
+            pw.println("  Windows losing focus:");
+            for (int i=mLosingFocus.size()-1; i>=0; i--) {
+                WindowState w = mLosingFocus.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Losing #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mResizingWindows.size() > 0) {
+            pw.println();
+            pw.println("  Windows waiting to resize:");
+            for (int i=mResizingWindows.size()-1; i>=0; i--) {
+                WindowState w = mResizingWindows.get(i);
+                if (windows == null || windows.contains(w)) {
+                    pw.print("  Resizing #"); pw.print(i); pw.print(' ');
+                            pw.print(w);
+                    if (dumpAll) {
+                        pw.println(":");
+                        w.dump(pw, "    ", true);
+                    } else {
+                        pw.println();
+                    }
+                }
+            }
+        }
+        if (mWaitingForDrawn.size() > 0) {
+            pw.println();
+            pw.println("  Clients waiting for these windows to be drawn:");
+            for (int i=mWaitingForDrawn.size()-1; i>=0; i--) {
+                Pair<WindowState, IRemoteCallback> pair = mWaitingForDrawn.get(i);
+                pw.print("  Waiting #"); pw.print(i); pw.print(' '); pw.print(pair.first);
+                        pw.print(": "); pw.println(pair.second);
+            }
+        }
+        pw.println();
+        pw.print("  mCurConfiguration="); pw.println(this.mCurConfiguration);
+        pw.print("  mCurrentFocus="); pw.println(mCurrentFocus);
+        if (mLastFocus != mCurrentFocus) {
+            pw.print("  mLastFocus="); pw.println(mLastFocus);
+        }
+        pw.print("  mFocusedApp="); pw.println(mFocusedApp);
+        if (mInputMethodTarget != null) {
+            pw.print("  mInputMethodTarget="); pw.println(mInputMethodTarget);
+        }
+        pw.print("  mInTouchMode="); pw.print(mInTouchMode);
+                pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+        pw.print("  mLastDisplayFreezeDuration=");
+                TimeUtils.formatDuration(mLastDisplayFreezeDuration, pw);
+                if ( mLastFinishedFreezeSource != null) {
+                    pw.print(" due to ");
+                    pw.print(mLastFinishedFreezeSource);
+                }
+                pw.println();
+        if (dumpAll) {
+            pw.print("  mSystemDecorLayer="); pw.print(mSystemDecorLayer);
+                    pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
+            if (mLastStatusBarVisibility != 0) {
+                pw.print("  mLastStatusBarVisibility=0x");
+                        pw.println(Integer.toHexString(mLastStatusBarVisibility));
+            }
+            if (mInputMethodWindow != null) {
+                pw.print("  mInputMethodWindow="); pw.println(mInputMethodWindow);
+            }
+            pw.print("  mWallpaperTarget="); pw.println(mWallpaperTarget);
+            if (mLowerWallpaperTarget != null || mUpperWallpaperTarget != null) {
+                pw.print("  mLowerWallpaperTarget="); pw.println(mLowerWallpaperTarget);
+                pw.print("  mUpperWallpaperTarget="); pw.println(mUpperWallpaperTarget);
+            }
+            pw.print("  mLastWallpaperX="); pw.print(mLastWallpaperX);
+                    pw.print(" mLastWallpaperY="); pw.println(mLastWallpaperY);
+            if (mInputMethodAnimLayerAdjustment != 0 ||
+                    mWallpaperAnimLayerAdjustment != 0) {
+                pw.print("  mInputMethodAnimLayerAdjustment=");
+                        pw.print(mInputMethodAnimLayerAdjustment);
+                        pw.print("  mWallpaperAnimLayerAdjustment=");
+                        pw.println(mWallpaperAnimLayerAdjustment);
+            }
+            pw.print("  mSystemBooted="); pw.print(mSystemBooted);
+                    pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled);
+            if (needsLayout()) {
+                pw.print("  layoutNeeded on displays=");
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
+                    if (displayContent.layoutNeeded) {
+                        pw.print(displayContent.getDisplayId());
+                    }
+                }
+                pw.println();
+            }
+            pw.print("  mTransactionSequence="); pw.println(mTransactionSequence);
+            pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
+                    pw.print(" windows="); pw.print(mWindowsFreezingScreen);
+                    pw.print(" client="); pw.print(mClientFreezingScreen);
+                    pw.print(" apps="); pw.print(mAppsFreezingScreen);
+                    pw.print(" waitingForConfig="); pw.println(mWaitingForConfig);
+            pw.print("  mRotation="); pw.print(mRotation);
+                    pw.print(" mAltOrientation="); pw.println(mAltOrientation);
+            pw.print("  mLastWindowForcedOrientation="); pw.print(mLastWindowForcedOrientation);
+                    pw.print(" mForcedAppOrientation="); pw.println(mForcedAppOrientation);
+            pw.print("  mDeferredRotationPauseCount="); pw.println(mDeferredRotationPauseCount);
+            pw.print("  mWindowAnimationScale="); pw.print(mWindowAnimationScale);
+                    pw.print(" mTransitionWindowAnimationScale="); pw.print(mTransitionAnimationScale);
+                    pw.print(" mAnimatorDurationScale="); pw.println(mAnimatorDurationScale);
+            pw.print("  mTraversalScheduled="); pw.println(mTraversalScheduled);
+            pw.print("  mStartingIconInTransition="); pw.print(mStartingIconInTransition);
+                    pw.print(" mSkipAppTransitionAnimation="); pw.println(mSkipAppTransitionAnimation);
+            pw.println("  mLayoutToAnim:");
+            mAppTransition.dump(pw);
+        }
+    }
+
+    boolean dumpWindows(PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        WindowList windows = new WindowList();
+        if ("visible".equals(name)) {
+            synchronized(mWindowMap) {
+                final int numDisplays = mDisplayContents.size();
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    final WindowList windowList =
+                            mDisplayContents.valueAt(displayNdx).getWindowList();
+                    for (int winNdx = windowList.size() - 1; winNdx >= 0; --winNdx) {
+                        final WindowState w = windowList.get(winNdx);
+                        if (w.mWinAnimator.mSurfaceShown) {
+                            windows.add(w);
+                        }
+                    }
+                }
+            }
+        } else {
+            int objectId = 0;
+            // See if this is an object ID.
+            try {
+                objectId = Integer.parseInt(name, 16);
+                name = null;
+            } catch (RuntimeException e) {
+            }
+            synchronized(mWindowMap) {
+                final int numDisplays = mDisplayContents.size();
+                for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+                    final WindowList windowList =
+                            mDisplayContents.valueAt(displayNdx).getWindowList();
+                    for (int winNdx = windowList.size() - 1; winNdx >= 0; --winNdx) {
+                        final WindowState w = windowList.get(winNdx);
+                        if (name != null) {
+                            if (w.mAttrs.getTitle().toString().contains(name)) {
+                                windows.add(w);
+                            }
+                        } else if (System.identityHashCode(w) == objectId) {
+                            windows.add(w);
+                        }
+                    }
+                }
+            }
+        }
+
+        if (windows.size() <= 0) {
+            return false;
+        }
+
+        synchronized(mWindowMap) {
+            dumpWindowsLocked(pw, dumpAll, windows);
+        }
+        return true;
+    }
+
+    void dumpLastANRLocked(PrintWriter pw) {
+        pw.println("WINDOW MANAGER LAST ANR (dumpsys window lastanr)");
+        if (mLastANRState == null) {
+            pw.println("  <no ANR has occurred since boot>");
+        } else {
+            pw.println(mLastANRState);
+        }
+    }
+
+    /**
+     * Saves information about the state of the window manager at
+     * the time an ANR occurred before anything else in the system changes
+     * in response.
+     *
+     * @param appWindowToken The application that ANR'd, may be null.
+     * @param windowState The window that ANR'd, may be null.
+     * @param reason The reason for the ANR, may be null.
+     */
+    public void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState,
+            String reason) {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new FastPrintWriter(sw, false, 1024);
+        pw.println("  ANR time: " + DateFormat.getInstance().format(new Date()));
+        if (appWindowToken != null) {
+            pw.println("  Application at fault: " + appWindowToken.stringName);
+        }
+        if (windowState != null) {
+            pw.println("  Window at fault: " + windowState.mAttrs.getTitle());
+        }
+        if (reason != null) {
+            pw.println("  Reason: " + reason);
+        }
+        pw.println();
+        dumpWindowsNoHeaderLocked(pw, true, null);
+        pw.close();
+        mLastANRState = sw.toString();
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+                != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump WindowManager from from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+
+        boolean dumpAll = false;
+
+        int opti = 0;
+        while (opti < args.length) {
+            String opt = args[opti];
+            if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
+                break;
+            }
+            opti++;
+            if ("-a".equals(opt)) {
+                dumpAll = true;
+            } else if ("-h".equals(opt)) {
+                pw.println("Window manager dump options:");
+                pw.println("  [-a] [-h] [cmd] ...");
+                pw.println("  cmd may be one of:");
+                pw.println("    l[astanr]: last ANR information");
+                pw.println("    p[policy]: policy state");
+                pw.println("    a[animator]: animator state");
+                pw.println("    s[essions]: active sessions");
+                pw.println("    d[isplays]: active display contents");
+                pw.println("    t[okens]: token list");
+                pw.println("    w[indows]: window list");
+                pw.println("  cmd may also be a NAME to dump windows.  NAME may");
+                pw.println("    be a partial substring in a window name, a");
+                pw.println("    Window hex object identifier, or");
+                pw.println("    \"all\" for all windows, or");
+                pw.println("    \"visible\" for the visible windows.");
+                pw.println("  -a: include all available server state.");
+                return;
+            } else {
+                pw.println("Unknown argument: " + opt + "; use -h for help");
+            }
+        }
+
+        // Is the caller requesting to dump a particular piece of data?
+        if (opti < args.length) {
+            String cmd = args[opti];
+            opti++;
+            if ("lastanr".equals(cmd) || "l".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpLastANRLocked(pw);
+                }
+                return;
+            } else if ("policy".equals(cmd) || "p".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpPolicyLocked(pw, args, true);
+                }
+                return;
+            } else if ("animator".equals(cmd) || "a".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpAnimatorLocked(pw, args, true);
+                }
+                return;
+            } else if ("sessions".equals(cmd) || "s".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpSessionsLocked(pw, true);
+                }
+                return;
+            } else if ("displays".equals(cmd) || "d".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpDisplayContentsLocked(pw, true);
+                }
+                return;
+            } else if ("tokens".equals(cmd) || "t".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpTokensLocked(pw, true);
+                }
+                return;
+            } else if ("windows".equals(cmd) || "w".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpWindowsLocked(pw, true, null);
+                }
+                return;
+            } else if ("all".equals(cmd) || "a".equals(cmd)) {
+                synchronized(mWindowMap) {
+                    dumpWindowsLocked(pw, true, null);
+                }
+                return;
+            } else {
+                // Dumping a single name?
+                if (!dumpWindows(pw, cmd, args, opti, dumpAll)) {
+                    pw.println("Bad window command, or no windows match: " + cmd);
+                    pw.println("Use -h for help.");
+                }
+                return;
+            }
+        }
+
+        synchronized(mWindowMap) {
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpLastANRLocked(pw);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpPolicyLocked(pw, args, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpAnimatorLocked(pw, args, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpSessionsLocked(pw, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpDisplayContentsLocked(pw, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpTokensLocked(pw, dumpAll);
+            pw.println();
+            if (dumpAll) {
+                pw.println("-------------------------------------------------------------------------------");
+            }
+            dumpWindowsLocked(pw, dumpAll, null);
+        }
+    }
+
+    // Called by the heartbeat to ensure locks are not held indefnitely (for deadlock detection).
+    @Override
+    public void monitor() {
+        synchronized (mWindowMap) { }
+    }
+
+    public interface OnHardKeyboardStatusChangeListener {
+        public void onHardKeyboardStatusChange(boolean available, boolean enabled);
+    }
+
+    void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
+        if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) {
+            Slog.v(TAG, "Layouts looping: " + msg + ", mPendingLayoutChanges = 0x" +
+                    Integer.toHexString(pendingLayoutChanges));
+        }
+    }
+
+    private DisplayContent newDisplayContentLocked(final Display display) {
+        DisplayContent displayContent = new DisplayContent(display, this);
+        final int displayId = display.getDisplayId();
+        mDisplayContents.put(displayId, displayContent);
+
+        DisplayInfo displayInfo = displayContent.getDisplayInfo();
+        final Rect rect = new Rect();
+        mDisplaySettings.getOverscanLocked(displayInfo.name, rect);
+        synchronized (displayContent.mDisplaySizeLock) {
+            displayInfo.overscanLeft = rect.left;
+            displayInfo.overscanTop = rect.top;
+            displayInfo.overscanRight = rect.right;
+            displayInfo.overscanBottom = rect.bottom;
+            mDisplayManagerService.setDisplayInfoOverrideFromWindowManager(
+                    displayId, displayInfo);
+        }
+        configureDisplayPolicyLocked(displayContent);
+
+        // TODO: Create an input channel for each display with touch capability.
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            displayContent.mTapDetector = new StackTapPointerEventListener(this, displayContent);
+            registerPointerEventListener(displayContent.mTapDetector);
+        }
+
+        return displayContent;
+    }
+
+    public void createDisplayContentLocked(final Display display) {
+        if (display == null) {
+            throw new IllegalArgumentException("getDisplayContent: display must not be null");
+        }
+        getDisplayContentLocked(display.getDisplayId());
+    }
+
+    /**
+     * Retrieve the DisplayContent for the specified displayId. Will create a new DisplayContent if
+     * there is a Display for the displayId.
+     * @param displayId The display the caller is interested in.
+     * @return The DisplayContent associated with displayId or null if there is no Display for it.
+     */
+    public DisplayContent getDisplayContentLocked(final int displayId) {
+        DisplayContent displayContent = mDisplayContents.get(displayId);
+        if (displayContent == null) {
+            final Display display = mDisplayManager.getDisplay(displayId);
+            if (display != null) {
+                displayContent = newDisplayContentLocked(display);
+            }
+        }
+        return displayContent;
+    }
+
+    // There is an inherent assumption that this will never return null.
+    public DisplayContent getDefaultDisplayContentLocked() {
+        return getDisplayContentLocked(Display.DEFAULT_DISPLAY);
+    }
+
+    public WindowList getDefaultWindowListLocked() {
+        return getDefaultDisplayContentLocked().getWindowList();
+    }
+
+    public DisplayInfo getDefaultDisplayInfoLocked() {
+        return getDefaultDisplayContentLocked().getDisplayInfo();
+    }
+
+    /**
+     * Return the list of WindowStates associated on the passed display.
+     * @param display The screen to return windows from.
+     * @return The list of WindowStates on the screen, or null if the there is no screen.
+     */
+    public WindowList getWindowListLocked(final Display display) {
+        return getWindowListLocked(display.getDisplayId());
+    }
+
+    /**
+     * Return the list of WindowStates associated on the passed display.
+     * @param displayId The screen to return windows from.
+     * @return The list of WindowStates on the screen, or null if the there is no screen.
+     */
+    public WindowList getWindowListLocked(final int displayId) {
+        final DisplayContent displayContent = getDisplayContentLocked(displayId);
+        return displayContent != null ? displayContent.getWindowList() : null;
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
+    }
+
+    private void handleDisplayAddedLocked(int displayId) {
+        final Display display = mDisplayManager.getDisplay(displayId);
+        if (display != null) {
+            createDisplayContentLocked(display);
+            displayReady(displayId);
+        }
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0));
+    }
+
+    private void handleDisplayRemovedLocked(int displayId) {
+        final DisplayContent displayContent = getDisplayContentLocked(displayId);
+        if (displayContent != null) {
+            mDisplayContents.delete(displayId);
+            displayContent.close();
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                unregisterPointerEventListener(displayContent.mTapDetector);
+            }
+            WindowList windows = displayContent.getWindowList();
+            while (!windows.isEmpty()) {
+                final WindowState win = windows.get(windows.size() - 1);
+                removeWindowLocked(win.mSession, win);
+            }
+        }
+        mAnimator.removeDisplayLocked(displayId);
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0));
+    }
+
+    private void handleDisplayChangedLocked(int displayId) {
+        final DisplayContent displayContent = getDisplayContentLocked(displayId);
+        if (displayContent != null) {
+            displayContent.updateDisplayInfo();
+        }
+    }
+
+    @Override
+    public Object getWindowManagerLock() {
+        return mWindowMap;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
new file mode 100644
index 0000000..e6c1e98
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -0,0 +1,1482 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT;
+
+import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import android.app.AppOpsManager;
+import android.os.RemoteCallbackList;
+import android.util.TimeUtils;
+import android.view.IWindowFocusObserver;
+import android.view.IWindowId;
+import com.android.server.input.InputWindowHandle;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.view.DisplayInfo;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.IWindow;
+import android.view.InputChannel;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class WindowList extends ArrayList<WindowState> {
+}
+
+/**
+ * A window in the window manager.
+ */
+final class WindowState implements WindowManagerPolicy.WindowState {
+    static final String TAG = "WindowState";
+
+    final WindowManagerService mService;
+    final WindowManagerPolicy mPolicy;
+    final Context mContext;
+    final Session mSession;
+    final IWindow mClient;
+    final int mAppOp;
+    // UserId and appId of the owner. Don't display windows of non-current user.
+    final int mOwnerUid;
+    final IWindowId mWindowId;
+    WindowToken mToken;
+    WindowToken mRootToken;
+    AppWindowToken mAppToken;
+    AppWindowToken mTargetAppToken;
+
+    // mAttrs.flags is tested in animation without being locked. If the bits tested are ever
+    // modified they will need to be locked.
+    final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
+    final DeathRecipient mDeathRecipient;
+    final WindowState mAttachedWindow;
+    final WindowList mChildWindows = new WindowList();
+    final int mBaseLayer;
+    final int mSubLayer;
+    final boolean mLayoutAttached;
+    final boolean mIsImWindow;
+    final boolean mIsWallpaper;
+    final boolean mIsFloatingLayer;
+    int mSeq;
+    boolean mEnforceSizeCompat;
+    int mViewVisibility;
+    int mSystemUiVisibility;
+    boolean mPolicyVisibility = true;
+    boolean mPolicyVisibilityAfterAnim = true;
+    boolean mAppOpVisibility = true;
+    boolean mAppFreezing;
+    boolean mAttachedHidden;    // is our parent window hidden?
+    boolean mWallpaperVisible;  // for wallpaper, what was last vis report?
+
+    RemoteCallbackList<IWindowFocusObserver> mFocusCallbacks;
+
+    /**
+     * The window size that was requested by the application.  These are in
+     * the application's coordinate space (without compatibility scale applied).
+     */
+    int mRequestedWidth;
+    int mRequestedHeight;
+    int mLastRequestedWidth;
+    int mLastRequestedHeight;
+
+    int mLayer;
+    boolean mHaveFrame;
+    boolean mObscured;
+    boolean mTurnOnScreen;
+
+    int mLayoutSeq = -1;
+
+    Configuration mConfiguration = null;
+    // Sticky answer to isConfigChanged(), remains true until new Configuration is assigned.
+    // Used only on {@link #TYPE_KEYGUARD}.
+    private boolean mConfigHasChanged;
+
+    /**
+     * Actual frame shown on-screen (may be modified by animation).  These
+     * are in the screen's coordinate space (WITH the compatibility scale
+     * applied).
+     */
+    final RectF mShownFrame = new RectF();
+
+    /**
+     * Insets that determine the actually visible area.  These are in the application's
+     * coordinate space (without compatibility scale applied).
+     */
+    final Rect mVisibleInsets = new Rect();
+    final Rect mLastVisibleInsets = new Rect();
+    boolean mVisibleInsetsChanged;
+
+    /**
+     * Insets that are covered by system windows (such as the status bar) and
+     * transient docking windows (such as the IME).  These are in the application's
+     * coordinate space (without compatibility scale applied).
+     */
+    final Rect mContentInsets = new Rect();
+    final Rect mLastContentInsets = new Rect();
+    boolean mContentInsetsChanged;
+
+    /**
+     * Insets that determine the area covered by the display overscan region.  These are in the
+     * application's coordinate space (without compatibility scale applied).
+     */
+    final Rect mOverscanInsets = new Rect();
+    final Rect mLastOverscanInsets = new Rect();
+    boolean mOverscanInsetsChanged;
+
+    /**
+     * Set to true if we are waiting for this window to receive its
+     * given internal insets before laying out other windows based on it.
+     */
+    boolean mGivenInsetsPending;
+
+    /**
+     * These are the content insets that were given during layout for
+     * this window, to be applied to windows behind it.
+     */
+    final Rect mGivenContentInsets = new Rect();
+
+    /**
+     * These are the visible insets that were given during layout for
+     * this window, to be applied to windows behind it.
+     */
+    final Rect mGivenVisibleInsets = new Rect();
+
+    /**
+     * This is the given touchable area relative to the window frame, or null if none.
+     */
+    final Region mGivenTouchableRegion = new Region();
+
+    /**
+     * Flag indicating whether the touchable region should be adjusted by
+     * the visible insets; if false the area outside the visible insets is
+     * NOT touchable, so we must use those to adjust the frame during hit
+     * tests.
+     */
+    int mTouchableInsets = ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
+
+    /**
+     * This is rectangle of the window's surface that is not covered by
+     * system decorations.
+     */
+    final Rect mSystemDecorRect = new Rect();
+    final Rect mLastSystemDecorRect = new Rect();
+
+    // Current transformation being applied.
+    float mGlobalScale=1;
+    float mInvGlobalScale=1;
+    float mHScale=1, mVScale=1;
+    float mLastHScale=1, mLastVScale=1;
+    final Matrix mTmpMatrix = new Matrix();
+
+    // "Real" frame that the application sees, in display coordinate space.
+    final Rect mFrame = new Rect();
+    final Rect mLastFrame = new Rect();
+    // Frame that is scaled to the application's coordinate space when in
+    // screen size compatibility mode.
+    final Rect mCompatFrame = new Rect();
+
+    final Rect mContainingFrame = new Rect();
+    final Rect mDisplayFrame = new Rect();
+    final Rect mOverscanFrame = new Rect();
+    final Rect mContentFrame = new Rect();
+    final Rect mParentFrame = new Rect();
+    final Rect mVisibleFrame = new Rect();
+    final Rect mDecorFrame = new Rect();
+
+    boolean mContentChanged;
+
+    // If a window showing a wallpaper: the requested offset for the
+    // wallpaper; if a wallpaper window: the currently applied offset.
+    float mWallpaperX = -1;
+    float mWallpaperY = -1;
+
+    // If a window showing a wallpaper: what fraction of the offset
+    // range corresponds to a full virtual screen.
+    float mWallpaperXStep = -1;
+    float mWallpaperYStep = -1;
+
+    // Wallpaper windows: pixels offset based on above variables.
+    int mXOffset;
+    int mYOffset;
+
+    /**
+     * This is set after IWindowSession.relayout() has been called at
+     * least once for the window.  It allows us to detect the situation
+     * where we don't yet have a surface, but should have one soon, so
+     * we can give the window focus before waiting for the relayout.
+     */
+    boolean mRelayoutCalled;
+
+    /**
+     * If the application has called relayout() with changes that can
+     * impact its window's size, we need to perform a layout pass on it
+     * even if it is not currently visible for layout.  This is set
+     * when in that case until the layout is done.
+     */
+    boolean mLayoutNeeded;
+
+    /** Currently running an exit animation? */
+    boolean mExiting;
+
+    /** Currently on the mDestroySurface list? */
+    boolean mDestroying;
+
+    /** Completely remove from window manager after exit animation? */
+    boolean mRemoveOnExit;
+
+    /**
+     * Set when the orientation is changing and this window has not yet
+     * been updated for the new orientation.
+     */
+    boolean mOrientationChanging;
+
+    /**
+     * How long we last kept the screen frozen.
+     */
+    int mLastFreezeDuration;
+
+    /** Is this window now (or just being) removed? */
+    boolean mRemoved;
+
+    /**
+     * Temp for keeping track of windows that have been removed when
+     * rebuilding window list.
+     */
+    boolean mRebuilding;
+
+    // Input channel and input window handle used by the input dispatcher.
+    final InputWindowHandle mInputWindowHandle;
+    InputChannel mInputChannel;
+
+    // Used to improve performance of toString()
+    String mStringNameCache;
+    CharSequence mLastTitle;
+    boolean mWasExiting;
+
+    final WindowStateAnimator mWinAnimator;
+
+    boolean mHasSurface = false;
+
+    DisplayContent  mDisplayContent;
+
+    /** When true this window can be displayed on screens owther than mOwnerUid's */
+    private boolean mShowToOwnerOnly;
+
+    /** When true this window is at the top of the screen and should be layed out to extend under
+     * the status bar */
+    boolean mUnderStatusBar = true;
+
+    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
+           WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
+           int viewVisibility, final DisplayContent displayContent) {
+        mService = service;
+        mSession = s;
+        mClient = c;
+        mAppOp = appOp;
+        mToken = token;
+        mOwnerUid = s.mUid;
+        mWindowId = new IWindowId.Stub() {
+            @Override
+            public void registerFocusObserver(IWindowFocusObserver observer) {
+                WindowState.this.registerFocusObserver(observer);
+            }
+            @Override
+            public void unregisterFocusObserver(IWindowFocusObserver observer) {
+                WindowState.this.unregisterFocusObserver(observer);
+            }
+            @Override
+            public boolean isFocused() {
+                return WindowState.this.isFocused();
+            }
+        };
+        mAttrs.copyFrom(a);
+        mViewVisibility = viewVisibility;
+        mDisplayContent = displayContent;
+        mPolicy = mService.mPolicy;
+        mContext = mService.mContext;
+        DeathRecipient deathRecipient = new DeathRecipient();
+        mSeq = seq;
+        mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0;
+        if (WindowManagerService.localLOGV) Slog.v(
+            TAG, "Window " + this + " client=" + c.asBinder()
+            + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
+        try {
+            c.asBinder().linkToDeath(deathRecipient, 0);
+        } catch (RemoteException e) {
+            mDeathRecipient = null;
+            mAttachedWindow = null;
+            mLayoutAttached = false;
+            mIsImWindow = false;
+            mIsWallpaper = false;
+            mIsFloatingLayer = false;
+            mBaseLayer = 0;
+            mSubLayer = 0;
+            mInputWindowHandle = null;
+            mWinAnimator = null;
+            return;
+        }
+        mDeathRecipient = deathRecipient;
+
+        if ((mAttrs.type >= FIRST_SUB_WINDOW &&
+                mAttrs.type <= LAST_SUB_WINDOW)) {
+            // The multiplier here is to reserve space for multiple
+            // windows in the same type layer.
+            mBaseLayer = mPolicy.windowTypeToLayerLw(
+                    attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                    + WindowManagerService.TYPE_LAYER_OFFSET;
+            mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);
+            mAttachedWindow = attachedWindow;
+            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + mAttachedWindow);
+
+            int children_size = mAttachedWindow.mChildWindows.size();
+            if (children_size == 0) {
+                mAttachedWindow.mChildWindows.add(this);
+            } else {
+                for (int i = 0; i < children_size; i++) {
+                    WindowState child = (WindowState)mAttachedWindow.mChildWindows.get(i);
+                    if (this.mSubLayer < child.mSubLayer) {
+                        mAttachedWindow.mChildWindows.add(i, this);
+                        break;
+                    } else if (this.mSubLayer > child.mSubLayer) {
+                        continue;
+                    }
+
+                    if (this.mBaseLayer <= child.mBaseLayer) {
+                        mAttachedWindow.mChildWindows.add(i, this);
+                        break;
+                    } else {
+                        continue;
+                    }
+                }
+                if (children_size == mAttachedWindow.mChildWindows.size()) {
+                    mAttachedWindow.mChildWindows.add(this);
+                }
+            }
+
+            mLayoutAttached = mAttrs.type !=
+                    WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+            mIsImWindow = attachedWindow.mAttrs.type == TYPE_INPUT_METHOD
+                    || attachedWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
+            mIsWallpaper = attachedWindow.mAttrs.type == TYPE_WALLPAPER;
+            mIsFloatingLayer = mIsImWindow || mIsWallpaper;
+        } else {
+            // The multiplier here is to reserve space for multiple
+            // windows in the same type layer.
+            mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
+                    * WindowManagerService.TYPE_LAYER_MULTIPLIER
+                    + WindowManagerService.TYPE_LAYER_OFFSET;
+            mSubLayer = 0;
+            mAttachedWindow = null;
+            mLayoutAttached = false;
+            mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
+                    || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
+            mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
+            mIsFloatingLayer = mIsImWindow || mIsWallpaper;
+        }
+
+        WindowState appWin = this;
+        while (appWin.mAttachedWindow != null) {
+            appWin = appWin.mAttachedWindow;
+        }
+        WindowToken appToken = appWin.mToken;
+        while (appToken.appWindowToken == null) {
+            WindowToken parent = mService.mTokenMap.get(appToken.token);
+            if (parent == null || appToken == parent) {
+                break;
+            }
+            appToken = parent;
+        }
+        mRootToken = appToken;
+        mAppToken = appToken.appWindowToken;
+
+        mWinAnimator = new WindowStateAnimator(this);
+        mWinAnimator.mAlpha = a.alpha;
+
+        mRequestedWidth = 0;
+        mRequestedHeight = 0;
+        mLastRequestedWidth = 0;
+        mLastRequestedHeight = 0;
+        mXOffset = 0;
+        mYOffset = 0;
+        mLayer = 0;
+        mInputWindowHandle = new InputWindowHandle(
+                mAppToken != null ? mAppToken.mInputApplicationHandle : null, this,
+                displayContent.getDisplayId());
+    }
+
+    void attach() {
+        if (WindowManagerService.localLOGV) Slog.v(
+            TAG, "Attaching " + this + " token=" + mToken
+            + ", list=" + mToken.windows);
+        mSession.windowAddedLocked();
+    }
+
+    @Override
+    public int getOwningUid() {
+        return mOwnerUid;
+    }
+
+    @Override
+    public String getOwningPackage() {
+        return mAttrs.packageName;
+    }
+
+    @Override
+    public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf) {
+        mHaveFrame = true;
+
+        TaskStack stack = mAppToken != null ? getStack() : null;
+        if (stack != null && !stack.isFullscreen()) {
+            getStackBounds(stack, mContainingFrame);
+            if (mUnderStatusBar) {
+                mContainingFrame.top = pf.top;
+            }
+        } else {
+            mContainingFrame.set(pf);
+        }
+
+        mDisplayFrame.set(df);
+
+        final int pw = mContainingFrame.width();
+        final int ph = mContainingFrame.height();
+
+        int w,h;
+        if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
+            if (mAttrs.width < 0) {
+                w = pw;
+            } else if (mEnforceSizeCompat) {
+                w = (int)(mAttrs.width * mGlobalScale + .5f);
+            } else {
+                w = mAttrs.width;
+            }
+            if (mAttrs.height < 0) {
+                h = ph;
+            } else if (mEnforceSizeCompat) {
+                h = (int)(mAttrs.height * mGlobalScale + .5f);
+            } else {
+                h = mAttrs.height;
+            }
+        } else {
+            if (mAttrs.width == WindowManager.LayoutParams.MATCH_PARENT) {
+                w = pw;
+            } else if (mEnforceSizeCompat) {
+                w = (int)(mRequestedWidth * mGlobalScale + .5f);
+            } else {
+                w = mRequestedWidth;
+            }
+            if (mAttrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
+                h = ph;
+            } else if (mEnforceSizeCompat) {
+                h = (int)(mRequestedHeight * mGlobalScale + .5f);
+            } else {
+                h = mRequestedHeight;
+            }
+        }
+
+        if (!mParentFrame.equals(pf)) {
+            //Slog.i(TAG, "Window " + this + " content frame from " + mParentFrame
+            //        + " to " + pf);
+            mParentFrame.set(pf);
+            mContentChanged = true;
+        }
+        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
+            mLastRequestedWidth = mRequestedWidth;
+            mLastRequestedHeight = mRequestedHeight;
+            mContentChanged = true;
+        }
+
+        mOverscanFrame.set(of);
+        mContentFrame.set(cf);
+        mVisibleFrame.set(vf);
+        mDecorFrame.set(dcf);
+
+        final int fw = mFrame.width();
+        final int fh = mFrame.height();
+
+        //System.out.println("In: w=" + w + " h=" + h + " container=" +
+        //                   container + " x=" + mAttrs.x + " y=" + mAttrs.y);
+
+        float x, y;
+        if (mEnforceSizeCompat) {
+            x = mAttrs.x * mGlobalScale;
+            y = mAttrs.y * mGlobalScale;
+        } else {
+            x = mAttrs.x;
+            y = mAttrs.y;
+        }
+
+        Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
+                (int) (x + mAttrs.horizontalMargin * pw),
+                (int) (y + mAttrs.verticalMargin * ph), mFrame);
+
+        //System.out.println("Out: " + mFrame);
+
+        // Now make sure the window fits in the overall display.
+        Gravity.applyDisplay(mAttrs.gravity, df, mFrame);
+
+        // Make sure the content and visible frames are inside of the
+        // final window frame.
+        mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
+                Math.max(mContentFrame.top, mFrame.top),
+                Math.min(mContentFrame.right, mFrame.right),
+                Math.min(mContentFrame.bottom, mFrame.bottom));
+
+        mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
+                Math.max(mVisibleFrame.top, mFrame.top),
+                Math.min(mVisibleFrame.right, mFrame.right),
+                Math.min(mVisibleFrame.bottom, mFrame.bottom));
+
+        mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
+                Math.max(mOverscanFrame.top - mFrame.top, 0),
+                Math.max(mFrame.right - mOverscanFrame.right, 0),
+                Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));
+
+        mContentInsets.set(mContentFrame.left - mFrame.left,
+                mContentFrame.top - mFrame.top,
+                mFrame.right - mContentFrame.right,
+                mFrame.bottom - mContentFrame.bottom);
+
+        mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
+                mVisibleFrame.top - mFrame.top,
+                mFrame.right - mVisibleFrame.right,
+                mFrame.bottom - mVisibleFrame.bottom);
+
+        mCompatFrame.set(mFrame);
+        if (mEnforceSizeCompat) {
+            // If there is a size compatibility scale being applied to the
+            // window, we need to apply this to its insets so that they are
+            // reported to the app in its coordinate space.
+            mOverscanInsets.scale(mInvGlobalScale);
+            mContentInsets.scale(mInvGlobalScale);
+            mVisibleInsets.scale(mInvGlobalScale);
+
+            // Also the scaled frame that we report to the app needs to be
+            // adjusted to be in its coordinate space.
+            mCompatFrame.scale(mInvGlobalScale);
+        }
+
+        if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
+            final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
+            mService.updateWallpaperOffsetLocked(this,
+                    displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+        }
+
+        if (DEBUG_LAYOUT || WindowManagerService.localLOGV) Slog.v(TAG,
+                "Resolving (mRequestedWidth="
+                + mRequestedWidth + ", mRequestedheight="
+                + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+                + "): frame=" + mFrame.toShortString()
+                + " ci=" + mContentInsets.toShortString()
+                + " vi=" + mVisibleInsets.toShortString());
+    }
+
+    @Override
+    public Rect getFrameLw() {
+        return mFrame;
+    }
+
+    @Override
+    public RectF getShownFrameLw() {
+        return mShownFrame;
+    }
+
+    @Override
+    public Rect getDisplayFrameLw() {
+        return mDisplayFrame;
+    }
+
+    @Override
+    public Rect getOverscanFrameLw() {
+        return mOverscanFrame;
+    }
+
+    @Override
+    public Rect getContentFrameLw() {
+        return mContentFrame;
+    }
+
+    @Override
+    public Rect getVisibleFrameLw() {
+        return mVisibleFrame;
+    }
+
+    @Override
+    public boolean getGivenInsetsPendingLw() {
+        return mGivenInsetsPending;
+    }
+
+    @Override
+    public Rect getGivenContentInsetsLw() {
+        return mGivenContentInsets;
+    }
+
+    @Override
+    public Rect getGivenVisibleInsetsLw() {
+        return mGivenVisibleInsets;
+    }
+
+    @Override
+    public WindowManager.LayoutParams getAttrs() {
+        return mAttrs;
+    }
+
+    @Override
+    public boolean getNeedsMenuLw(WindowManagerPolicy.WindowState bottom) {
+        int index = -1;
+        WindowState ws = this;
+        WindowList windows = getWindowList();
+        while (true) {
+            if ((ws.mAttrs.privateFlags
+                    & WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY) != 0) {
+                return (ws.mAttrs.flags & WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY) != 0;
+            }
+            // If we reached the bottom of the range of windows we are considering,
+            // assume no menu is needed.
+            if (ws == bottom) {
+                return false;
+            }
+            // The current window hasn't specified whether menu key is needed;
+            // look behind it.
+            // First, we may need to determine the starting position.
+            if (index < 0) {
+                index = windows.indexOf(ws);
+            }
+            index--;
+            if (index < 0) {
+                return false;
+            }
+            ws = windows.get(index);
+        }
+    }
+
+    @Override
+    public int getSystemUiVisibility() {
+        return mSystemUiVisibility;
+    }
+
+    @Override
+    public int getSurfaceLayer() {
+        return mLayer;
+    }
+
+    @Override
+    public IApplicationToken getAppToken() {
+        return mAppToken != null ? mAppToken.appToken : null;
+    }
+
+    boolean setInsetsChanged() {
+        mOverscanInsetsChanged |= !mLastOverscanInsets.equals(mOverscanInsets);
+        mContentInsetsChanged |= !mLastContentInsets.equals(mContentInsets);
+        mVisibleInsetsChanged |= !mLastVisibleInsets.equals(mVisibleInsets);
+        return mOverscanInsetsChanged || mContentInsetsChanged || mVisibleInsetsChanged;
+    }
+
+    public int getDisplayId() {
+        return mDisplayContent.getDisplayId();
+    }
+
+    TaskStack getStack() {
+        AppWindowToken wtoken = mAppToken == null ? mService.mFocusedApp : mAppToken;
+        if (wtoken != null) {
+            Task task = mService.mTaskIdToTask.get(wtoken.groupId);
+            if (task != null) {
+                return task.mStack;
+            }
+        }
+        return mDisplayContent.getHomeStack();
+    }
+
+    void getStackBounds(Rect bounds) {
+        getStackBounds(getStack(), bounds);
+    }
+
+    private void getStackBounds(TaskStack stack, Rect bounds) {
+        if (stack != null) {
+            stack.getBounds(bounds);
+            return;
+        }
+        bounds.set(mFrame);
+    }
+
+    public long getInputDispatchingTimeoutNanos() {
+        return mAppToken != null
+                ? mAppToken.inputDispatchingTimeoutNanos
+                : WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
+    }
+
+    @Override
+    public boolean hasAppShownWindows() {
+        return mAppToken != null && (mAppToken.firstWindowDrawn || mAppToken.startingDisplayed);
+    }
+
+    boolean isIdentityMatrix(float dsdx, float dtdx, float dsdy, float dtdy) {
+        if (dsdx < .99999f || dsdx > 1.00001f) return false;
+        if (dtdy < .99999f || dtdy > 1.00001f) return false;
+        if (dtdx < -.000001f || dtdx > .000001f) return false;
+        if (dsdy < -.000001f || dsdy > .000001f) return false;
+        return true;
+    }
+
+    void prelayout() {
+        if (mEnforceSizeCompat) {
+            mGlobalScale = mService.mCompatibleScreenScale;
+            mInvGlobalScale = 1/mGlobalScale;
+        } else {
+            mGlobalScale = mInvGlobalScale = 1;
+        }
+    }
+
+    /**
+     * Is this window visible?  It is not visible if there is no
+     * surface, or we are in the process of running an exit animation
+     * that will remove the surface, or its app token has been hidden.
+     */
+    @Override
+    public boolean isVisibleLw() {
+        final AppWindowToken atoken = mAppToken;
+        return mHasSurface && mPolicyVisibility && !mAttachedHidden
+                && (atoken == null || !atoken.hiddenRequested)
+                && !mExiting && !mDestroying;
+    }
+
+    /**
+     * Like {@link #isVisibleLw}, but also counts a window that is currently
+     * "hidden" behind the keyguard as visible.  This allows us to apply
+     * things like window flags that impact the keyguard.
+     * XXX I am starting to think we need to have ANOTHER visibility flag
+     * for this "hidden behind keyguard" state rather than overloading
+     * mPolicyVisibility.  Ungh.
+     */
+    @Override
+    public boolean isVisibleOrBehindKeyguardLw() {
+        if (mRootToken.waitingToShow &&
+                mService.mAppTransition.isTransitionSet()) {
+            return false;
+        }
+        final AppWindowToken atoken = mAppToken;
+        final boolean animating = atoken != null
+                ? (atoken.mAppAnimator.animation != null) : false;
+        return mHasSurface && !mDestroying && !mExiting
+                && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested)
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
+                                && !mRootToken.hidden)
+                        || mWinAnimator.mAnimation != null || animating);
+    }
+
+    /**
+     * Is this window visible, ignoring its app token?  It is not visible
+     * if there is no surface, or we are in the process of running an exit animation
+     * that will remove the surface.
+     */
+    public boolean isWinVisibleLw() {
+        final AppWindowToken atoken = mAppToken;
+        return mHasSurface && mPolicyVisibility && !mAttachedHidden
+                && (atoken == null || !atoken.hiddenRequested || atoken.mAppAnimator.animating)
+                && !mExiting && !mDestroying;
+    }
+
+    /**
+     * The same as isVisible(), but follows the current hidden state of
+     * the associated app token, not the pending requested hidden state.
+     */
+    boolean isVisibleNow() {
+        return mHasSurface && mPolicyVisibility && !mAttachedHidden
+                && !mRootToken.hidden && !mExiting && !mDestroying;
+    }
+
+    /**
+     * Can this window possibly be a drag/drop target?  The test here is
+     * a combination of the above "visible now" with the check that the
+     * Input Manager uses when discarding windows from input consideration.
+     */
+    boolean isPotentialDragTarget() {
+        return isVisibleNow() && !mRemoved
+                && mInputChannel != null && mInputWindowHandle != null;
+    }
+
+    /**
+     * Same as isVisible(), but we also count it as visible between the
+     * call to IWindowSession.add() and the first relayout().
+     */
+    boolean isVisibleOrAdding() {
+        final AppWindowToken atoken = mAppToken;
+        return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
+                && mPolicyVisibility && !mAttachedHidden
+                && (atoken == null || !atoken.hiddenRequested)
+                && !mExiting && !mDestroying;
+    }
+
+    /**
+     * Is this window currently on-screen?  It is on-screen either if it
+     * is visible or it is currently running an animation before no longer
+     * being visible.
+     */
+    boolean isOnScreen() {
+        if (!mHasSurface || !mPolicyVisibility || mDestroying) {
+            return false;
+        }
+        final AppWindowToken atoken = mAppToken;
+        if (atoken != null) {
+            return ((!mAttachedHidden && !atoken.hiddenRequested)
+                    || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null);
+        }
+        return !mAttachedHidden || mWinAnimator.mAnimation != null;
+    }
+
+    /**
+     * Like isOnScreen(), but we don't return true if the window is part
+     * of a transition that has not yet been started.
+     */
+    boolean isReadyForDisplay() {
+        if (mRootToken.waitingToShow &&
+                mService.mAppTransition.isTransitionSet()) {
+            return false;
+        }
+        return mHasSurface && mPolicyVisibility && !mDestroying
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
+                                && !mRootToken.hidden)
+                        || mWinAnimator.mAnimation != null
+                        || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null)));
+    }
+
+    /**
+     * Like isReadyForDisplay(), but ignores any force hiding of the window due
+     * to the keyguard.
+     */
+    boolean isReadyForDisplayIgnoringKeyguard() {
+        if (mRootToken.waitingToShow && mService.mAppTransition.isTransitionSet()) {
+            return false;
+        }
+        final AppWindowToken atoken = mAppToken;
+        if (atoken == null && !mPolicyVisibility) {
+            // If this is not an app window, and the policy has asked to force
+            // hide, then we really do want to hide.
+            return false;
+        }
+        return mHasSurface && !mDestroying
+                && ((!mAttachedHidden && mViewVisibility == View.VISIBLE
+                                && !mRootToken.hidden)
+                        || mWinAnimator.mAnimation != null
+                        || ((atoken != null) && (atoken.mAppAnimator.animation != null)
+                                && !mWinAnimator.isDummyAnimation()));
+    }
+
+    /**
+     * Like isOnScreen, but returns false if the surface hasn't yet
+     * been drawn.
+     */
+    @Override
+    public boolean isDisplayedLw() {
+        final AppWindowToken atoken = mAppToken;
+        return isDrawnLw() && mPolicyVisibility
+            && ((!mAttachedHidden &&
+                    (atoken == null || !atoken.hiddenRequested))
+                        || mWinAnimator.mAnimating
+                        || (atoken != null && atoken.mAppAnimator.animation != null));
+    }
+
+    /**
+     * Return true if this window or its app token is currently animating.
+     */
+    @Override
+    public boolean isAnimatingLw() {
+        return mWinAnimator.mAnimation != null
+                || (mAppToken != null && mAppToken.mAppAnimator.animation != null);
+    }
+
+    @Override
+    public boolean isGoneForLayoutLw() {
+        final AppWindowToken atoken = mAppToken;
+        return mViewVisibility == View.GONE
+                || !mRelayoutCalled
+                || (atoken == null && mRootToken.hidden)
+                || (atoken != null && (atoken.hiddenRequested || atoken.hidden))
+                || mAttachedHidden
+                || (mExiting && !isAnimatingLw())
+                || mDestroying;
+    }
+
+    /**
+     * Returns true if the window has a surface that it has drawn a
+     * complete UI in to.
+     */
+    public boolean isDrawFinishedLw() {
+        return mHasSurface && !mDestroying &&
+                (mWinAnimator.mDrawState == WindowStateAnimator.COMMIT_DRAW_PENDING
+                || mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
+                || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
+    }
+
+    /**
+     * Returns true if the window has a surface that it has drawn a
+     * complete UI in to.
+     */
+    public boolean isDrawnLw() {
+        return mHasSurface && !mDestroying &&
+                (mWinAnimator.mDrawState == WindowStateAnimator.READY_TO_SHOW
+                || mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN);
+    }
+
+    /**
+     * Return true if the window is opaque and fully drawn.  This indicates
+     * it may obscure windows behind it.
+     */
+    boolean isOpaqueDrawn() {
+        return (mAttrs.format == PixelFormat.OPAQUE
+                        || mAttrs.type == TYPE_WALLPAPER)
+                && isDrawnLw() && mWinAnimator.mAnimation == null
+                && (mAppToken == null || mAppToken.mAppAnimator.animation == null);
+    }
+
+    /**
+     * Return whether this window is wanting to have a translation
+     * animation applied to it for an in-progress move.  (Only makes
+     * sense to call from performLayoutAndPlaceSurfacesLockedInner().)
+     */
+    boolean shouldAnimateMove() {
+        return mContentChanged && !mExiting && !mWinAnimator.mLastHidden && mService.okToDisplay()
+                && (mFrame.top != mLastFrame.top
+                        || mFrame.left != mLastFrame.left)
+                && (mAttrs.privateFlags&PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
+                && (mAttachedWindow == null || !mAttachedWindow.shouldAnimateMove());
+    }
+
+    boolean isFullscreen(int screenWidth, int screenHeight) {
+        return mFrame.left <= 0 && mFrame.top <= 0 &&
+                mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
+    }
+
+    boolean isConfigChanged() {
+        boolean configChanged = mConfiguration != mService.mCurConfiguration
+                && (mConfiguration == null
+                        || (mConfiguration.diff(mService.mCurConfiguration) != 0));
+
+        if (mAttrs.type == TYPE_KEYGUARD) {
+            // Retain configuration changed status until resetConfiguration called.
+            mConfigHasChanged |= configChanged;
+            configChanged = mConfigHasChanged;
+        }
+
+        return configChanged;
+    }
+
+    void removeLocked() {
+        disposeInputChannel();
+
+        if (mAttachedWindow != null) {
+            if (WindowManagerService.DEBUG_ADD_REMOVE) Slog.v(TAG, "Removing " + this + " from " + mAttachedWindow);
+            mAttachedWindow.mChildWindows.remove(this);
+        }
+        mWinAnimator.destroyDeferredSurfaceLocked();
+        mWinAnimator.destroySurfaceLocked();
+        mSession.windowRemovedLocked();
+        try {
+            mClient.asBinder().unlinkToDeath(mDeathRecipient, 0);
+        } catch (RuntimeException e) {
+            // Ignore if it has already been removed (usually because
+            // we are doing this as part of processing a death note.)
+        }
+    }
+
+    void setConfiguration(final Configuration newConfig) {
+        mConfiguration = newConfig;
+        mConfigHasChanged = false;
+    }
+
+    void setInputChannel(InputChannel inputChannel) {
+        if (mInputChannel != null) {
+            throw new IllegalStateException("Window already has an input channel.");
+        }
+
+        mInputChannel = inputChannel;
+        mInputWindowHandle.inputChannel = inputChannel;
+    }
+
+    void disposeInputChannel() {
+        if (mInputChannel != null) {
+            mService.mInputManager.unregisterInputChannel(mInputChannel);
+
+            mInputChannel.dispose();
+            mInputChannel = null;
+        }
+
+        mInputWindowHandle.inputChannel = null;
+    }
+
+    private class DeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            try {
+                synchronized(mService.mWindowMap) {
+                    WindowState win = mService.windowForClientLocked(mSession, mClient, false);
+                    Slog.i(TAG, "WIN DEATH: " + win);
+                    if (win != null) {
+                        mService.removeWindowLocked(mSession, win);
+                    } else if (mHasSurface) {
+                        Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid.");
+                        mService.removeWindowLocked(mSession, WindowState.this);
+                    }
+                }
+            } catch (IllegalArgumentException ex) {
+                // This will happen if the window has already been
+                // removed.
+            }
+        }
+    }
+
+    /**
+     * @return true if this window desires key events.
+     */
+    public final boolean canReceiveKeys() {
+        return isVisibleOrAdding()
+                && (mViewVisibility == View.VISIBLE)
+                && ((mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0);
+    }
+
+    @Override
+    public boolean hasDrawnLw() {
+        return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
+    }
+
+    @Override
+    public boolean showLw(boolean doAnimation) {
+        return showLw(doAnimation, true);
+    }
+
+    boolean showLw(boolean doAnimation, boolean requestAnim) {
+        if (isHiddenFromUserLocked()) {
+            Slog.w(TAG, "current user violation " + mService.mCurrentUserId + " trying to display "
+                    + this + ", type " + mAttrs.type + ", belonging to " + mOwnerUid);
+            return false;
+        }
+        if (!mAppOpVisibility) {
+            // Being hidden due to app op request.
+            return false;
+        }
+        if (mPolicyVisibility && mPolicyVisibilityAfterAnim) {
+            // Already showing.
+            return false;
+        }
+        if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility true: " + this);
+        if (doAnimation) {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "doAnimation: mPolicyVisibility="
+                    + mPolicyVisibility + " mAnimation=" + mWinAnimator.mAnimation);
+            if (!mService.okToDisplay()) {
+                doAnimation = false;
+            } else if (mPolicyVisibility && mWinAnimator.mAnimation == null) {
+                // Check for the case where we are currently visible and
+                // not animating; we do not want to do animation at such a
+                // point to become visible when we already are.
+                doAnimation = false;
+            }
+        }
+        mPolicyVisibility = true;
+        mPolicyVisibilityAfterAnim = true;
+        if (doAnimation) {
+            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
+        }
+        if (requestAnim) {
+            mService.scheduleAnimationLocked();
+        }
+        return true;
+    }
+
+    @Override
+    public boolean hideLw(boolean doAnimation) {
+        return hideLw(doAnimation, true);
+    }
+
+    boolean hideLw(boolean doAnimation, boolean requestAnim) {
+        if (doAnimation) {
+            if (!mService.okToDisplay()) {
+                doAnimation = false;
+            }
+        }
+        boolean current = doAnimation ? mPolicyVisibilityAfterAnim
+                : mPolicyVisibility;
+        if (!current) {
+            // Already hiding.
+            return false;
+        }
+        if (doAnimation) {
+            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false);
+            if (mWinAnimator.mAnimation == null) {
+                doAnimation = false;
+            }
+        }
+        if (doAnimation) {
+            mPolicyVisibilityAfterAnim = false;
+        } else {
+            if (DEBUG_VISIBILITY) Slog.v(TAG, "Policy visibility false: " + this);
+            mPolicyVisibilityAfterAnim = false;
+            mPolicyVisibility = false;
+            // Window is no longer visible -- make sure if we were waiting
+            // for it to be displayed before enabling the display, that
+            // we allow the display to be enabled now.
+            mService.enableScreenIfNeededLocked();
+            if (mService.mCurrentFocus == this) {
+                if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.i(TAG,
+                        "WindowState.hideLw: setting mFocusMayChange true");
+                mService.mFocusMayChange = true;
+            }
+        }
+        if (requestAnim) {
+            mService.scheduleAnimationLocked();
+        }
+        return true;
+    }
+
+    public void setAppOpVisibilityLw(boolean state) {
+        if (mAppOpVisibility != state) {
+            mAppOpVisibility = state;
+            if (state) {
+                // If the policy visibility had last been to hide, then this
+                // will incorrectly show at this point since we lost that
+                // information.  Not a big deal -- for the windows that have app
+                // ops modifies they should only be hidden by policy due to the
+                // lock screen, and the user won't be changing this if locked.
+                // Plus it will quickly be fixed the next time we do a layout.
+                showLw(true, true);
+            } else {
+                hideLw(true, true);
+            }
+        }
+    }
+
+    @Override
+    public boolean isAlive() {
+        return mClient.asBinder().isBinderAlive();
+    }
+
+    boolean isClosing() {
+        return mExiting || (mService.mClosingApps.contains(mAppToken));
+    }
+
+    @Override
+    public boolean isDefaultDisplay() {
+        return mDisplayContent.isDefaultDisplay;
+    }
+
+    public void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
+        mShowToOwnerOnly = showToOwnerOnly;
+    }
+
+    boolean isHiddenFromUserLocked() {
+        // Attached windows are evaluated based on the window that they are attached to.
+        WindowState win = this;
+        while (win.mAttachedWindow != null) {
+            win = win.mAttachedWindow;
+        }
+        if (win.mAttrs.type < WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW
+                && win.mAppToken != null && win.mAppToken.showWhenLocked) {
+            // Save some cycles by not calling getDisplayInfo unless it is an application
+            // window intended for all users.
+            final DisplayInfo displayInfo = win.mDisplayContent.getDisplayInfo();
+            if (win.mFrame.left <= 0 && win.mFrame.top <= 0
+                    && win.mFrame.right >= displayInfo.appWidth
+                    && win.mFrame.bottom >= displayInfo.appHeight) {
+                // Is a fullscreen window, like the clock alarm. Show to everyone.
+                return false;
+            }
+        }
+
+        return win.mShowToOwnerOnly
+                && UserHandle.getUserId(win.mOwnerUid) != mService.mCurrentUserId;
+    }
+
+    private static void applyInsets(Region outRegion, Rect frame, Rect inset) {
+        outRegion.set(
+                frame.left + inset.left, frame.top + inset.top,
+                frame.right - inset.right, frame.bottom - inset.bottom);
+    }
+
+    public void getTouchableRegion(Region outRegion) {
+        final Rect frame = mFrame;
+        switch (mTouchableInsets) {
+            default:
+            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME:
+                outRegion.set(frame);
+                break;
+            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT:
+                applyInsets(outRegion, frame, mGivenContentInsets);
+                break;
+            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE:
+                applyInsets(outRegion, frame, mGivenVisibleInsets);
+                break;
+            case ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION: {
+                final Region givenTouchableRegion = mGivenTouchableRegion;
+                outRegion.set(givenTouchableRegion);
+                outRegion.translate(frame.left, frame.top);
+                break;
+            }
+        }
+    }
+
+    WindowList getWindowList() {
+        return mDisplayContent.getWindowList();
+    }
+
+    /**
+     * Report a focus change.  Must be called with no locks held, and consistently
+     * from the same serialized thread (such as dispatched from a handler).
+     */
+    public void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
+        try {
+            mClient.windowFocusChanged(focused, inTouchMode);
+        } catch (RemoteException e) {
+        }
+        if (mFocusCallbacks != null) {
+            final int N = mFocusCallbacks.beginBroadcast();
+            for (int i=0; i<N; i++) {
+                IWindowFocusObserver obs = mFocusCallbacks.getBroadcastItem(i);
+                try {
+                    if (focused) {
+                        obs.focusGained(mWindowId.asBinder());
+                    } else {
+                        obs.focusLost(mWindowId.asBinder());
+                    }
+                } catch (RemoteException e) {
+                }
+            }
+            mFocusCallbacks.finishBroadcast();
+        }
+    }
+
+    public void registerFocusObserver(IWindowFocusObserver observer) {
+        synchronized(mService.mWindowMap) {
+            if (mFocusCallbacks == null) {
+                mFocusCallbacks = new RemoteCallbackList<IWindowFocusObserver>();
+            }
+            mFocusCallbacks.register(observer);
+        }
+    }
+
+    public void unregisterFocusObserver(IWindowFocusObserver observer) {
+        synchronized(mService.mWindowMap) {
+            if (mFocusCallbacks != null) {
+                mFocusCallbacks.unregister(observer);
+            }
+        }
+    }
+
+    public boolean isFocused() {
+        synchronized(mService.mWindowMap) {
+            return mService.mCurrentFocus == this;
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        pw.print(prefix); pw.print("mDisplayId="); pw.print(mDisplayContent.getDisplayId());
+                pw.print(" mSession="); pw.print(mSession);
+                pw.print(" mClient="); pw.println(mClient.asBinder());
+        pw.print(prefix); pw.print("mOwnerUid="); pw.print(mOwnerUid);
+                pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
+                pw.print(" package="); pw.print(mAttrs.packageName);
+                pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
+        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+        pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
+                pw.print(" h="); pw.print(mRequestedHeight);
+                pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
+        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
+            pw.print(prefix); pw.print("LastRequested w="); pw.print(mLastRequestedWidth);
+                    pw.print(" h="); pw.println(mLastRequestedHeight);
+        }
+        if (mAttachedWindow != null || mLayoutAttached) {
+            pw.print(prefix); pw.print("mAttachedWindow="); pw.print(mAttachedWindow);
+                    pw.print(" mLayoutAttached="); pw.println(mLayoutAttached);
+        }
+        if (mIsImWindow || mIsWallpaper || mIsFloatingLayer) {
+            pw.print(prefix); pw.print("mIsImWindow="); pw.print(mIsImWindow);
+                    pw.print(" mIsWallpaper="); pw.print(mIsWallpaper);
+                    pw.print(" mIsFloatingLayer="); pw.print(mIsFloatingLayer);
+                    pw.print(" mWallpaperVisible="); pw.println(mWallpaperVisible);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer);
+                    pw.print(" mSubLayer="); pw.print(mSubLayer);
+                    pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+");
+                    pw.print((mTargetAppToken != null ?
+                            mTargetAppToken.mAppAnimator.animLayerAdjustment
+                          : (mAppToken != null ? mAppToken.mAppAnimator.animLayerAdjustment : 0)));
+                    pw.print("="); pw.print(mWinAnimator.mAnimLayer);
+                    pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mToken="); pw.println(mToken);
+            pw.print(prefix); pw.print("mRootToken="); pw.println(mRootToken);
+            if (mAppToken != null) {
+                pw.print(prefix); pw.print("mAppToken="); pw.println(mAppToken);
+            }
+            if (mTargetAppToken != null) {
+                pw.print(prefix); pw.print("mTargetAppToken="); pw.println(mTargetAppToken);
+            }
+            pw.print(prefix); pw.print("mViewVisibility=0x");
+            pw.print(Integer.toHexString(mViewVisibility));
+            pw.print(" mHaveFrame="); pw.print(mHaveFrame);
+            pw.print(" mObscured="); pw.println(mObscured);
+            pw.print(prefix); pw.print("mSeq="); pw.print(mSeq);
+            pw.print(" mSystemUiVisibility=0x");
+            pw.println(Integer.toHexString(mSystemUiVisibility));
+        }
+        if (!mPolicyVisibility || !mPolicyVisibilityAfterAnim || !mAppOpVisibility
+                || mAttachedHidden) {
+            pw.print(prefix); pw.print("mPolicyVisibility=");
+                    pw.print(mPolicyVisibility);
+                    pw.print(" mPolicyVisibilityAfterAnim=");
+                    pw.print(mPolicyVisibilityAfterAnim);
+                    pw.print(" mAppOpVisibility=");
+                    pw.print(mAppOpVisibility);
+                    pw.print(" mAttachedHidden="); pw.println(mAttachedHidden);
+        }
+        if (!mRelayoutCalled || mLayoutNeeded) {
+            pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
+                    pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
+        }
+        if (mXOffset != 0 || mYOffset != 0) {
+            pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
+                    pw.print(" y="); pw.println(mYOffset);
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mGivenContentInsets=");
+                    mGivenContentInsets.printShortString(pw);
+                    pw.print(" mGivenVisibleInsets=");
+                    mGivenVisibleInsets.printShortString(pw);
+                    pw.println();
+            if (mTouchableInsets != 0 || mGivenInsetsPending) {
+                pw.print(prefix); pw.print("mTouchableInsets="); pw.print(mTouchableInsets);
+                        pw.print(" mGivenInsetsPending="); pw.println(mGivenInsetsPending);
+                Region region = new Region();
+                getTouchableRegion(region);
+                pw.print(prefix); pw.print("touchable region="); pw.println(region);
+            }
+            pw.print(prefix); pw.print("mConfiguration="); pw.println(mConfiguration);
+        }
+        pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
+                pw.print(" mShownFrame="); mShownFrame.printShortString(pw);
+                pw.print(" isReadyForDisplay()="); pw.println(isReadyForDisplay());
+        if (dumpAll) {
+            pw.print(prefix); pw.print("mFrame="); mFrame.printShortString(pw);
+                    pw.print(" last="); mLastFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
+                    pw.print(" last="); mLastSystemDecorRect.printShortString(pw);
+                    pw.println();
+        }
+        if (mEnforceSizeCompat) {
+            pw.print(prefix); pw.print("mCompatFrame="); mCompatFrame.printShortString(pw);
+                    pw.println();
+        }
+        if (dumpAll) {
+            pw.print(prefix); pw.print("Frames: containing=");
+                    mContainingFrame.printShortString(pw);
+                    pw.print(" parent="); mParentFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    display="); mDisplayFrame.printShortString(pw);
+                    pw.print(" overscan="); mOverscanFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    content="); mContentFrame.printShortString(pw);
+                    pw.print(" visible="); mVisibleFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("    decor="); mDecorFrame.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("Cur insets: overscan=");
+                    mOverscanInsets.printShortString(pw);
+                    pw.print(" content="); mContentInsets.printShortString(pw);
+                    pw.print(" visible="); mVisibleInsets.printShortString(pw);
+                    pw.println();
+            pw.print(prefix); pw.print("Lst insets: overscan=");
+                    mLastOverscanInsets.printShortString(pw);
+                    pw.print(" content="); mLastContentInsets.printShortString(pw);
+                    pw.print(" visible="); mLastVisibleInsets.printShortString(pw);
+                    pw.println();
+        }
+        pw.print(prefix); pw.print(mWinAnimator); pw.println(":");
+        mWinAnimator.dump(pw, prefix + "  ", dumpAll);
+        if (mExiting || mRemoveOnExit || mDestroying || mRemoved) {
+            pw.print(prefix); pw.print("mExiting="); pw.print(mExiting);
+                    pw.print(" mRemoveOnExit="); pw.print(mRemoveOnExit);
+                    pw.print(" mDestroying="); pw.print(mDestroying);
+                    pw.print(" mRemoved="); pw.println(mRemoved);
+        }
+        if (mOrientationChanging || mAppFreezing || mTurnOnScreen) {
+            pw.print(prefix); pw.print("mOrientationChanging=");
+                    pw.print(mOrientationChanging);
+                    pw.print(" mAppFreezing="); pw.print(mAppFreezing);
+                    pw.print(" mTurnOnScreen="); pw.println(mTurnOnScreen);
+        }
+        if (mLastFreezeDuration != 0) {
+            pw.print(prefix); pw.print("mLastFreezeDuration=");
+                    TimeUtils.formatDuration(mLastFreezeDuration, pw); pw.println();
+        }
+        if (mHScale != 1 || mVScale != 1) {
+            pw.print(prefix); pw.print("mHScale="); pw.print(mHScale);
+                    pw.print(" mVScale="); pw.println(mVScale);
+        }
+        if (mWallpaperX != -1 || mWallpaperY != -1) {
+            pw.print(prefix); pw.print("mWallpaperX="); pw.print(mWallpaperX);
+                    pw.print(" mWallpaperY="); pw.println(mWallpaperY);
+        }
+        if (mWallpaperXStep != -1 || mWallpaperYStep != -1) {
+            pw.print(prefix); pw.print("mWallpaperXStep="); pw.print(mWallpaperXStep);
+                    pw.print(" mWallpaperYStep="); pw.println(mWallpaperYStep);
+        }
+    }
+
+    String makeInputChannelName() {
+        return Integer.toHexString(System.identityHashCode(this))
+            + " " + mAttrs.getTitle();
+    }
+
+    @Override
+    public String toString() {
+        CharSequence title = mAttrs.getTitle();
+        if (title == null || title.length() <= 0) {
+            title = mAttrs.packageName;
+        }
+        if (mStringNameCache == null || mLastTitle != title || mWasExiting != mExiting) {
+            mLastTitle = title;
+            mWasExiting = mExiting;
+            mStringNameCache = "Window{" + Integer.toHexString(System.identityHashCode(this))
+                    + " u" + UserHandle.getUserId(mSession.mUid)
+                    + " " + mLastTitle + (mExiting ? " EXITING}" : "}");
+        }
+        return mStringNameCache;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
new file mode 100644
index 0000000..c405170
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -0,0 +1,1660 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static com.android.server.wm.WindowManagerService.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerService.DEBUG_LAYERS;
+import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION;
+import static com.android.server.wm.WindowManagerService.DEBUG_STARTING_WINDOW;
+import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE;
+import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS;
+import static com.android.server.wm.WindowManagerService.SHOW_SURFACE_ALLOC;
+import static com.android.server.wm.WindowManagerService.localLOGV;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE;
+import static com.android.server.wm.WindowManagerService.LayoutFields.SET_TURN_ON_SCREEN;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.os.Debug;
+import android.util.Slog;
+import android.view.Display;
+import android.view.DisplayInfo;
+import android.view.MagnificationSpec;
+import android.view.Surface.OutOfResourcesException;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+import android.view.WindowManager;
+import android.view.WindowManagerPolicy;
+import android.view.WindowManager.LayoutParams;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Transformation;
+
+import com.android.server.wm.WindowManagerService.H;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+class WinAnimatorList extends ArrayList<WindowStateAnimator> {
+}
+
+/**
+ * Keep track of animations and surface operations for a single WindowState.
+ **/
+class WindowStateAnimator {
+    static final String TAG = "WindowStateAnimator";
+
+    // Unchanging local convenience fields.
+    final WindowManagerService mService;
+    final WindowState mWin;
+    final WindowStateAnimator mAttachedWinAnimator;
+    final WindowAnimator mAnimator;
+    AppWindowAnimator mAppAnimator;
+    final Session mSession;
+    final WindowManagerPolicy mPolicy;
+    final Context mContext;
+    final boolean mIsWallpaper;
+
+    // If this is a universe background window, this is the transformation
+    // it is applying to the rest of the universe.
+    final Transformation mUniverseTransform = new Transformation();
+
+    // Currently running animation.
+    boolean mAnimating;
+    boolean mLocalAnimating;
+    Animation mAnimation;
+    boolean mAnimationIsEntrance;
+    boolean mHasTransformation;
+    boolean mHasLocalTransformation;
+    final Transformation mTransformation = new Transformation();
+    boolean mWasAnimating;      // Were we animating going into the most recent animation step?
+    int mAnimLayer;
+    int mLastLayer;
+
+    SurfaceControl mSurfaceControl;
+    SurfaceControl mPendingDestroySurface;
+
+    /**
+     * Set when we have changed the size of the surface, to know that
+     * we must tell them application to resize (and thus redraw itself).
+     */
+    boolean mSurfaceResized;
+
+    /**
+     * Set if the client has asked that the destroy of its surface be delayed
+     * until it explicitly says it is okay.
+     */
+    boolean mSurfaceDestroyDeferred;
+
+    float mShownAlpha = 0;
+    float mAlpha = 0;
+    float mLastAlpha = 0;
+
+    // Used to save animation distances between the time they are calculated and when they are
+    // used.
+    int mAnimDw;
+    int mAnimDh;
+    float mDsDx=1, mDtDx=0, mDsDy=0, mDtDy=1;
+    float mLastDsDx=1, mLastDtDx=0, mLastDsDy=0, mLastDtDy=1;
+
+    boolean mHaveMatrix;
+
+    // For debugging, this is the last information given to the surface flinger.
+    boolean mSurfaceShown;
+    float mSurfaceX, mSurfaceY, mSurfaceW, mSurfaceH;
+    int mSurfaceLayer;
+    float mSurfaceAlpha;
+
+    // Set to true if, when the window gets displayed, it should perform
+    // an enter animation.
+    boolean mEnterAnimationPending;
+
+    /** This is set when there is no Surface */
+    static final int NO_SURFACE = 0;
+    /** This is set after the Surface has been created but before the window has been drawn. During
+     * this time the surface is hidden. */
+    static final int DRAW_PENDING = 1;
+    /** This is set after the window has finished drawing for the first time but before its surface
+     * is shown.  The surface will be displayed when the next layout is run. */
+    static final int COMMIT_DRAW_PENDING = 2;
+    /** This is set during the time after the window's drawing has been committed, and before its
+     * surface is actually shown.  It is used to delay showing the surface until all windows in a
+     * token are ready to be shown. */
+    static final int READY_TO_SHOW = 3;
+    /** Set when the window has been shown in the screen the first time. */
+    static final int HAS_DRAWN = 4;
+    static String drawStateToString(int state) {
+        switch (state) {
+            case NO_SURFACE: return "NO_SURFACE";
+            case DRAW_PENDING: return "DRAW_PENDING";
+            case COMMIT_DRAW_PENDING: return "COMMIT_DRAW_PENDING";
+            case READY_TO_SHOW: return "READY_TO_SHOW";
+            case HAS_DRAWN: return "HAS_DRAWN";
+            default: return Integer.toString(state);
+        }
+    }
+    int mDrawState;
+
+    /** Was this window last hidden? */
+    boolean mLastHidden;
+
+    int mAttrFlags;
+    int mAttrType;
+
+    final int mLayerStack;
+
+    public WindowStateAnimator(final WindowState win) {
+        final WindowManagerService service = win.mService;
+
+        mService = service;
+        mAnimator = service.mAnimator;
+        mPolicy = service.mPolicy;
+        mContext = service.mContext;
+        final DisplayInfo displayInfo = win.mDisplayContent.getDisplayInfo();
+        mAnimDw = displayInfo.appWidth;
+        mAnimDh = displayInfo.appHeight;
+
+        mWin = win;
+        mAttachedWinAnimator = win.mAttachedWindow == null
+                ? null : win.mAttachedWindow.mWinAnimator;
+        mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
+        mSession = win.mSession;
+        mAttrFlags = win.mAttrs.flags;
+        mAttrType = win.mAttrs.type;
+        mIsWallpaper = win.mIsWallpaper;
+        mLayerStack = win.mDisplayContent.getDisplay().getLayerStack();
+    }
+
+    public void setAnimation(Animation anim) {
+        if (localLOGV) Slog.v(TAG, "Setting animation in " + this + ": " + anim);
+        mAnimating = false;
+        mLocalAnimating = false;
+        mAnimation = anim;
+        mAnimation.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION);
+        mAnimation.scaleCurrentDuration(mService.mWindowAnimationScale);
+        // Start out animation gone if window is gone, or visible if window is visible.
+        mTransformation.clear();
+        mTransformation.setAlpha(mLastHidden ? 0 : 1);
+        mHasLocalTransformation = true;
+    }
+
+    public void clearAnimation() {
+        if (mAnimation != null) {
+            mAnimating = true;
+            mLocalAnimating = false;
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+    }
+
+    /** Is the window or its container currently animating? */
+    boolean isAnimating() {
+        return mAnimation != null
+                || (mAttachedWinAnimator != null && mAttachedWinAnimator.mAnimation != null)
+                || (mAppAnimator != null &&
+                        (mAppAnimator.animation != null
+                                || mAppAnimator.mAppToken.inPendingTransaction));
+    }
+
+    /** Is the window animating the DummyAnimation? */
+    boolean isDummyAnimation() {
+        return mAppAnimator != null
+                && mAppAnimator.animation == AppWindowAnimator.sDummyAnimation;
+    }
+
+    /** Is this window currently animating? */
+    boolean isWindowAnimating() {
+        return mAnimation != null;
+    }
+
+    void cancelExitAnimationForNextAnimationLocked() {
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+            mLocalAnimating = false;
+            destroySurfaceLocked();
+        }
+    }
+
+    private boolean stepAnimation(long currentTime) {
+        if ((mAnimation == null) || !mLocalAnimating) {
+            return false;
+        }
+        mTransformation.clear();
+        final boolean more = mAnimation.getTransformation(currentTime, mTransformation);
+        if (false && DEBUG_ANIM) Slog.v(
+            TAG, "Stepped animation in " + this +
+            ": more=" + more + ", xform=" + mTransformation);
+        return more;
+    }
+
+    // This must be called while inside a transaction.  Returns true if
+    // there is more animation to run.
+    boolean stepAnimationLocked(long currentTime) {
+        // Save the animation state as it was before this step so WindowManagerService can tell if
+        // we just started or just stopped animating by comparing mWasAnimating with isAnimating().
+        mWasAnimating = mAnimating;
+        if (mService.okToDisplay()) {
+            // We will run animations as long as the display isn't frozen.
+
+            if (mWin.isDrawnLw() && mAnimation != null) {
+                mHasTransformation = true;
+                mHasLocalTransformation = true;
+                if (!mLocalAnimating) {
+                    if (DEBUG_ANIM) Slog.v(
+                        TAG, "Starting animation in " + this +
+                        " @ " + currentTime + ": ww=" + mWin.mFrame.width() +
+                        " wh=" + mWin.mFrame.height() +
+                        " dw=" + mAnimDw + " dh=" + mAnimDh +
+                        " scale=" + mService.mWindowAnimationScale);
+                    mAnimation.initialize(mWin.mFrame.width(), mWin.mFrame.height(),
+                            mAnimDw, mAnimDh);
+                    final DisplayInfo displayInfo = mWin.mDisplayContent.getDisplayInfo();
+                    mAnimDw = displayInfo.appWidth;
+                    mAnimDh = displayInfo.appHeight;
+                    mAnimation.setStartTime(currentTime);
+                    mLocalAnimating = true;
+                    mAnimating = true;
+                }
+                if ((mAnimation != null) && mLocalAnimating) {
+                    if (stepAnimation(currentTime)) {
+                        return true;
+                    }
+                }
+                if (DEBUG_ANIM) Slog.v(
+                    TAG, "Finished animation in " + this +
+                    " @ " + currentTime);
+                //WindowManagerService.this.dump();
+            }
+            mHasLocalTransformation = false;
+            if ((!mLocalAnimating || mAnimationIsEntrance) && mAppAnimator != null
+                    && mAppAnimator.animation != null) {
+                // When our app token is animating, we kind-of pretend like
+                // we are as well.  Note the mLocalAnimating mAnimationIsEntrance
+                // part of this check means that we will only do this if
+                // our window is not currently exiting, or it is not
+                // locally animating itself.  The idea being that one that
+                // is exiting and doing a local animation should be removed
+                // once that animation is done.
+                mAnimating = true;
+                mHasTransformation = true;
+                mTransformation.clear();
+                return false;
+            } else if (mHasTransformation) {
+                // Little trick to get through the path below to act like
+                // we have finished an animation.
+                mAnimating = true;
+            } else if (isAnimating()) {
+                mAnimating = true;
+            }
+        } else if (mAnimation != null) {
+            // If the display is frozen, and there is a pending animation,
+            // clear it and make sure we run the cleanup code.
+            mAnimating = true;
+        }
+
+        if (!mAnimating && !mLocalAnimating) {
+            return false;
+        }
+
+        // Done animating, clean up.
+        if (DEBUG_ANIM) Slog.v(
+            TAG, "Animation done in " + this + ": exiting=" + mWin.mExiting
+            + ", reportedVisible="
+            + (mWin.mAppToken != null ? mWin.mAppToken.reportedVisible : false));
+
+        mAnimating = false;
+        mLocalAnimating = false;
+        if (mAnimation != null) {
+            mAnimation.cancel();
+            mAnimation = null;
+        }
+        if (mAnimator.mWindowDetachedWallpaper == mWin) {
+            mAnimator.mWindowDetachedWallpaper = null;
+        }
+        mAnimLayer = mWin.mLayer;
+        if (mWin.mIsImWindow) {
+            mAnimLayer += mService.mInputMethodAnimLayerAdjustment;
+        } else if (mIsWallpaper) {
+            mAnimLayer += mService.mWallpaperAnimLayerAdjustment;
+        }
+        if (DEBUG_LAYERS) Slog.v(TAG, "Stepping win " + this
+                + " anim layer: " + mAnimLayer);
+        mHasTransformation = false;
+        mHasLocalTransformation = false;
+        if (mWin.mPolicyVisibility != mWin.mPolicyVisibilityAfterAnim) {
+            if (DEBUG_VISIBILITY) {
+                Slog.v(TAG, "Policy visibility changing after anim in " + this + ": "
+                        + mWin.mPolicyVisibilityAfterAnim);
+            }
+            mWin.mPolicyVisibility = mWin.mPolicyVisibilityAfterAnim;
+            mWin.mDisplayContent.layoutNeeded = true;
+            if (!mWin.mPolicyVisibility) {
+                if (mService.mCurrentFocus == mWin) {
+                    if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.i(TAG,
+                            "setAnimationLocked: setting mFocusMayChange true");
+                    mService.mFocusMayChange = true;
+                }
+                // Window is no longer visible -- make sure if we were waiting
+                // for it to be displayed before enabling the display, that
+                // we allow the display to be enabled now.
+                mService.enableScreenIfNeededLocked();
+            }
+        }
+        mTransformation.clear();
+        if (mDrawState == HAS_DRAWN
+                && mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING
+                && mWin.mAppToken != null
+                && mWin.mAppToken.firstWindowDrawn
+                && mWin.mAppToken.startingData != null) {
+            if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Finish starting "
+                    + mWin.mToken + ": first real window done animating");
+            mService.mFinishedStarting.add(mWin.mAppToken);
+            mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
+        } else if (mAttrType == LayoutParams.TYPE_STATUS_BAR && mWin.mPolicyVisibility) {
+            // Upon completion of a not-visible to visible status bar animation a relayout is
+            // required.
+            mWin.mDisplayContent.layoutNeeded = true;
+        }
+
+        finishExit();
+        final int displayId = mWin.mDisplayContent.getDisplayId();
+        mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
+        if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats(
+                "WindowStateAnimator", mAnimator.getPendingLayoutChanges(displayId));
+
+        if (mWin.mAppToken != null) {
+            mWin.mAppToken.updateReportedVisibilityLocked();
+        }
+
+        return false;
+    }
+
+    void finishExit() {
+        if (WindowManagerService.DEBUG_ANIM) Slog.v(
+                TAG, "finishExit in " + this
+                + ": exiting=" + mWin.mExiting
+                + " remove=" + mWin.mRemoveOnExit
+                + " windowAnimating=" + isWindowAnimating());
+
+        final int N = mWin.mChildWindows.size();
+        for (int i=0; i<N; i++) {
+            mWin.mChildWindows.get(i).mWinAnimator.finishExit();
+        }
+
+        if (!mWin.mExiting) {
+            return;
+        }
+
+        if (isWindowAnimating()) {
+            return;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(
+                TAG, "Exit animation finished in " + this
+                + ": remove=" + mWin.mRemoveOnExit);
+        if (mSurfaceControl != null) {
+            mService.mDestroySurface.add(mWin);
+            mWin.mDestroying = true;
+            if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(
+                mWin, "HIDE (finishExit)", null);
+            hide();
+        }
+        mWin.mExiting = false;
+        if (mWin.mRemoveOnExit) {
+            mService.mPendingRemove.add(mWin);
+            mWin.mRemoveOnExit = false;
+        }
+        mAnimator.hideWallpapersLocked(mWin);
+    }
+
+    void hide() {
+        if (!mLastHidden) {
+            //dump();
+            mLastHidden = true;
+            if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
+                    "HIDE (performLayout)", null);
+            if (mSurfaceControl != null) {
+                mSurfaceShown = false;
+                try {
+                    mSurfaceControl.hide();
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Exception hiding surface in " + mWin);
+                }
+            }
+        }
+    }
+
+    boolean finishDrawingLocked() {
+        if (DEBUG_STARTING_WINDOW &&
+                mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+            Slog.v(TAG, "Finishing drawing window " + mWin + ": mDrawState="
+                    + drawStateToString(mDrawState));
+        }
+        if (mDrawState == DRAW_PENDING) {
+            if (DEBUG_SURFACE_TRACE || DEBUG_ANIM || SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+                Slog.v(TAG, "finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING " + this + " in "
+                        + mSurfaceControl);
+            if (DEBUG_STARTING_WINDOW &&
+                    mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+                Slog.v(TAG, "Draw state now committed in " + mWin);
+            }
+            mDrawState = COMMIT_DRAW_PENDING;
+            return true;
+        }
+        return false;
+    }
+
+    // This must be called while inside a transaction.
+    boolean commitFinishDrawingLocked(long currentTime) {
+        if (DEBUG_STARTING_WINDOW &&
+                mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING) {
+            Slog.i(TAG, "commitFinishDrawingLocked: " + mWin + " cur mDrawState="
+                    + drawStateToString(mDrawState));
+        }
+        if (mDrawState != COMMIT_DRAW_PENDING) {
+            return false;
+        }
+        if (DEBUG_SURFACE_TRACE || DEBUG_ANIM) {
+            Slog.i(TAG, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW " + mSurfaceControl);
+        }
+        mDrawState = READY_TO_SHOW;
+        final boolean starting = mWin.mAttrs.type == TYPE_APPLICATION_STARTING;
+        final AppWindowToken atoken = mWin.mAppToken;
+        if (atoken == null || atoken.allDrawn || starting) {
+            performShowLocked();
+        }
+        return true;
+    }
+
+    static class SurfaceTrace extends SurfaceControl {
+        private final static String SURFACE_TAG = "SurfaceTrace";
+        final static ArrayList<SurfaceTrace> sSurfaces = new ArrayList<SurfaceTrace>();
+
+        private float mSurfaceTraceAlpha = 0;
+        private int mLayer;
+        private final PointF mPosition = new PointF();
+        private final Point mSize = new Point();
+        private final Rect mWindowCrop = new Rect();
+        private boolean mShown = false;
+        private int mLayerStack;
+        private final String mName;
+
+        public SurfaceTrace(SurfaceSession s,
+                       String name, int w, int h, int format, int flags)
+                   throws OutOfResourcesException {
+            super(s, name, w, h, format, flags);
+            mName = name != null ? name : "Not named";
+            mSize.set(w, h);
+            Slog.v(SURFACE_TAG, "ctor: " + this + ". Called by "
+                    + Debug.getCallers(3));
+        }
+
+        @Override
+        public void setAlpha(float alpha) {
+            if (mSurfaceTraceAlpha != alpha) {
+                Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mSurfaceTraceAlpha = alpha;
+            }
+            super.setAlpha(alpha);
+        }
+
+        @Override
+        public void setLayer(int zorder) {
+            if (zorder != mLayer) {
+                Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mLayer = zorder;
+            }
+            super.setLayer(zorder);
+
+            sSurfaces.remove(this);
+            int i;
+            for (i = sSurfaces.size() - 1; i >= 0; i--) {
+                SurfaceTrace s = sSurfaces.get(i);
+                if (s.mLayer < zorder) {
+                    break;
+                }
+            }
+            sSurfaces.add(i + 1, this);
+        }
+
+        @Override
+        public void setPosition(float x, float y) {
+            if (x != mPosition.x || y != mPosition.y) {
+                Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:" + this
+                        + ". Called by " + Debug.getCallers(3));
+                mPosition.set(x, y);
+            }
+            super.setPosition(x, y);
+        }
+
+        @Override
+        public void setSize(int w, int h) {
+            if (w != mSize.x || h != mSize.y) {
+                Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:" + this + ". Called by "
+                        + Debug.getCallers(3));
+                mSize.set(w, h);
+            }
+            super.setSize(w, h);
+        }
+
+        @Override
+        public void setWindowCrop(Rect crop) {
+            if (crop != null) {
+                if (!crop.equals(mWindowCrop)) {
+                    Slog.v(SURFACE_TAG, "setWindowCrop(" + crop.toShortString() + "): OLD:" + this
+                            + ". Called by " + Debug.getCallers(3));
+                    mWindowCrop.set(crop);
+                }
+            }
+            super.setWindowCrop(crop);
+        }
+
+        @Override
+        public void setLayerStack(int layerStack) {
+            if (layerStack != mLayerStack) {
+                Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:" + this
+                        + ". Called by " + Debug.getCallers(3));
+                mLayerStack = layerStack;
+            }
+            super.setLayerStack(layerStack);
+        }
+
+        @Override
+        public void hide() {
+            if (mShown) {
+                Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by " + Debug.getCallers(3));
+                mShown = false;
+            }
+            super.hide();
+        }
+
+        @Override
+        public void show() {
+            if (!mShown) {
+                Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by " + Debug.getCallers(3));
+                mShown = true;
+            }
+            super.show();
+        }
+
+        @Override
+        public void destroy() {
+            super.destroy();
+            Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by " + Debug.getCallers(3));
+            sSurfaces.remove(this);
+        }
+
+        @Override
+        public void release() {
+            super.release();
+            Slog.v(SURFACE_TAG, "release: " + this + ". Called by "
+                    + Debug.getCallers(3));
+            sSurfaces.remove(this);
+        }
+
+        static void dumpAllSurfaces() {
+            final int N = sSurfaces.size();
+            for (int i = 0; i < N; i++) {
+                Slog.i(TAG, "SurfaceDump: " + sSurfaces.get(i));
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "Surface " + Integer.toHexString(System.identityHashCode(this)) + " "
+                    + mName + " (" + mLayerStack + "): shown=" + mShown + " layer=" + mLayer
+                    + " alpha=" + mSurfaceTraceAlpha + " " + mPosition.x + "," + mPosition.y
+                    + " " + mSize.x + "x" + mSize.y
+                    + " crop=" + mWindowCrop.toShortString();
+        }
+    }
+
+    SurfaceControl createSurfaceLocked() {
+        if (mSurfaceControl == null) {
+            if (DEBUG_ANIM || DEBUG_ORIENTATION) Slog.i(TAG,
+                    "createSurface " + this + ": mDrawState=DRAW_PENDING");
+            mDrawState = DRAW_PENDING;
+            if (mWin.mAppToken != null) {
+                if (mWin.mAppToken.mAppAnimator.animation == null) {
+                    mWin.mAppToken.allDrawn = false;
+                    mWin.mAppToken.deferClearAllDrawn = false;
+                } else {
+                    // Currently animating, persist current state of allDrawn until animation
+                    // is complete.
+                    mWin.mAppToken.deferClearAllDrawn = true;
+                }
+            }
+
+            mService.makeWindowFreezingScreenIfNeededLocked(mWin);
+
+            int flags = SurfaceControl.HIDDEN;
+            final WindowManager.LayoutParams attrs = mWin.mAttrs;
+
+            if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) {
+                flags |= SurfaceControl.SECURE;
+            }
+            if (DEBUG_VISIBILITY) Slog.v(
+                TAG, "Creating surface in session "
+                + mSession.mSurfaceSession + " window " + this
+                + " w=" + mWin.mCompatFrame.width()
+                + " h=" + mWin.mCompatFrame.height() + " format="
+                + attrs.format + " flags=" + flags);
+
+            int w = mWin.mCompatFrame.width();
+            int h = mWin.mCompatFrame.height();
+            if ((attrs.flags & LayoutParams.FLAG_SCALED) != 0) {
+                // for a scaled surface, we always want the requested
+                // size.
+                w = mWin.mRequestedWidth;
+                h = mWin.mRequestedHeight;
+            }
+
+            // Something is wrong and SurfaceFlinger will not like this,
+            // try to revert to sane values
+            if (w <= 0) w = 1;
+            if (h <= 0) h = 1;
+
+            mSurfaceShown = false;
+            mSurfaceLayer = 0;
+            mSurfaceAlpha = 0;
+            mSurfaceX = 0;
+            mSurfaceY = 0;
+            mSurfaceW = w;
+            mSurfaceH = h;
+            mWin.mLastSystemDecorRect.set(0, 0, 0, 0);
+            try {
+                final boolean isHwAccelerated = (attrs.flags &
+                        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
+                final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format;
+                if (!PixelFormat.formatHasAlpha(attrs.format)) {
+                    flags |= SurfaceControl.OPAQUE;
+                }
+                if (DEBUG_SURFACE_TRACE) {
+                    mSurfaceControl = new SurfaceTrace(
+                            mSession.mSurfaceSession,
+                            attrs.getTitle().toString(),
+                            w, h, format, flags);
+                } else {
+                    mSurfaceControl = new SurfaceControl(
+                        mSession.mSurfaceSession,
+                        attrs.getTitle().toString(),
+                        w, h, format, flags);
+                }
+                mWin.mHasSurface = true;
+                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
+                        "  CREATE SURFACE "
+                        + mSurfaceControl + " IN SESSION "
+                        + mSession.mSurfaceSession
+                        + ": pid=" + mSession.mPid + " format="
+                        + attrs.format + " flags=0x"
+                        + Integer.toHexString(flags)
+                        + " / " + this);
+            } catch (OutOfResourcesException e) {
+                mWin.mHasSurface = false;
+                Slog.w(TAG, "OutOfResourcesException creating surface");
+                mService.reclaimSomeSurfaceMemoryLocked(this, "create", true);
+                mDrawState = NO_SURFACE;
+                return null;
+            } catch (Exception e) {
+                mWin.mHasSurface = false;
+                Slog.e(TAG, "Exception creating surface", e);
+                mDrawState = NO_SURFACE;
+                return null;
+            }
+
+            if (WindowManagerService.localLOGV) Slog.v(
+                TAG, "Got surface: " + mSurfaceControl
+                + ", set left=" + mWin.mFrame.left + " top=" + mWin.mFrame.top
+                + ", animLayer=" + mAnimLayer);
+            if (SHOW_LIGHT_TRANSACTIONS) {
+                Slog.i(TAG, ">>> OPEN TRANSACTION createSurfaceLocked");
+                WindowManagerService.logSurface(mWin, "CREATE pos=("
+                        + mWin.mFrame.left + "," + mWin.mFrame.top + ") ("
+                        + mWin.mCompatFrame.width() + "x" + mWin.mCompatFrame.height()
+                        + "), layer=" + mAnimLayer + " HIDE", null);
+            }
+            SurfaceControl.openTransaction();
+            try {
+                try {
+                    mSurfaceX = mWin.mFrame.left + mWin.mXOffset;
+                    mSurfaceY = mWin.mFrame.top + mWin.mYOffset;
+                    mSurfaceControl.setPosition(mSurfaceX, mSurfaceY);
+                    mSurfaceLayer = mAnimLayer;
+                    mSurfaceControl.setLayerStack(mLayerStack);
+                    mSurfaceControl.setLayer(mAnimLayer);
+                    mSurfaceControl.setAlpha(0);
+                    mSurfaceShown = false;
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Error creating surface in " + w, e);
+                    mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true);
+                }
+                mLastHidden = true;
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION createSurfaceLocked");
+            }
+            if (WindowManagerService.localLOGV) Slog.v(
+                    TAG, "Created surface " + this);
+        }
+        return mSurfaceControl;
+    }
+
+    void destroySurfaceLocked() {
+        if (mWin.mAppToken != null && mWin == mWin.mAppToken.startingWindow) {
+            mWin.mAppToken.startingDisplayed = false;
+        }
+
+        if (mSurfaceControl != null) {
+
+            int i = mWin.mChildWindows.size();
+            while (i > 0) {
+                i--;
+                WindowState c = mWin.mChildWindows.get(i);
+                c.mAttachedHidden = true;
+            }
+
+            try {
+                if (DEBUG_VISIBILITY) {
+                    RuntimeException e = null;
+                    if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                        e = new RuntimeException();
+                        e.fillInStackTrace();
+                    }
+                    Slog.w(TAG, "Window " + this + " destroying surface "
+                            + mSurfaceControl + ", session " + mSession, e);
+                }
+                if (mSurfaceDestroyDeferred) {
+                    if (mSurfaceControl != null && mPendingDestroySurface != mSurfaceControl) {
+                        if (mPendingDestroySurface != null) {
+                            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                                RuntimeException e = null;
+                                if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                                    e = new RuntimeException();
+                                    e.fillInStackTrace();
+                                }
+                                WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
+                            }
+                            mPendingDestroySurface.destroy();
+                        }
+                        mPendingDestroySurface = mSurfaceControl;
+                    }
+                } else {
+                    if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                        RuntimeException e = null;
+                        if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                            e = new RuntimeException();
+                            e.fillInStackTrace();
+                        }
+                        WindowManagerService.logSurface(mWin, "DESTROY", e);
+                    }
+                    mSurfaceControl.destroy();
+                }
+                mAnimator.hideWallpapersLocked(mWin);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Exception thrown when destroying Window " + this
+                    + " surface " + mSurfaceControl + " session " + mSession
+                    + ": " + e.toString());
+            }
+
+            mSurfaceShown = false;
+            mSurfaceControl = null;
+            mWin.mHasSurface = false;
+            mDrawState = NO_SURFACE;
+        }
+    }
+
+    void destroyDeferredSurfaceLocked() {
+        try {
+            if (mPendingDestroySurface != null) {
+                if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+                    RuntimeException e = null;
+                    if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                        e = new RuntimeException();
+                        e.fillInStackTrace();
+                    }
+                    WindowManagerService.logSurface(mWin, "DESTROY PENDING", e);
+                }
+                mPendingDestroySurface.destroy();
+                mAnimator.hideWallpapersLocked(mWin);
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Exception thrown when destroying Window "
+                    + this + " surface " + mPendingDestroySurface
+                    + " session " + mSession + ": " + e.toString());
+        }
+        mSurfaceDestroyDeferred = false;
+        mPendingDestroySurface = null;
+    }
+
+    void computeShownFrameLocked() {
+        final boolean selfTransformation = mHasLocalTransformation;
+        Transformation attachedTransformation =
+                (mAttachedWinAnimator != null && mAttachedWinAnimator.mHasLocalTransformation)
+                ? mAttachedWinAnimator.mTransformation : null;
+        Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
+                ? mAppAnimator.transformation : null;
+
+        // Wallpapers are animated based on the "real" window they
+        // are currently targeting.
+        final WindowState wallpaperTarget = mService.mWallpaperTarget;
+        if (mIsWallpaper && wallpaperTarget != null && mService.mAnimateWallpaperWithTarget) {
+            final WindowStateAnimator wallpaperAnimator = wallpaperTarget.mWinAnimator;
+            if (wallpaperAnimator.mHasLocalTransformation &&
+                    wallpaperAnimator.mAnimation != null &&
+                    !wallpaperAnimator.mAnimation.getDetachWallpaper()) {
+                attachedTransformation = wallpaperAnimator.mTransformation;
+                if (WindowManagerService.DEBUG_WALLPAPER && attachedTransformation != null) {
+                    Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+                }
+            }
+            final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
+                    null : wallpaperTarget.mAppToken.mAppAnimator;
+                if (wpAppAnimator != null && wpAppAnimator.hasTransformation
+                    && wpAppAnimator.animation != null
+                    && !wpAppAnimator.animation.getDetachWallpaper()) {
+                appTransformation = wpAppAnimator.transformation;
+                if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) {
+                    Slog.v(TAG, "WP target app xform: " + appTransformation);
+                }
+            }
+        }
+
+        final int displayId = mWin.getDisplayId();
+        final ScreenRotationAnimation screenRotationAnimation =
+                mAnimator.getScreenRotationAnimationLocked(displayId);
+        final boolean screenAnimation =
+                screenRotationAnimation != null && screenRotationAnimation.isAnimating();
+        if (selfTransformation || attachedTransformation != null
+                || appTransformation != null || screenAnimation) {
+            // cache often used attributes locally
+            final Rect frame = mWin.mFrame;
+            final float tmpFloats[] = mService.mTmpFloats;
+            final Matrix tmpMatrix = mWin.mTmpMatrix;
+
+            // Compute the desired transformation.
+            if (screenAnimation && screenRotationAnimation.isRotating()) {
+                // If we are doing a screen animation, the global rotation
+                // applied to windows can result in windows that are carefully
+                // aligned with each other to slightly separate, allowing you
+                // to see what is behind them.  An unsightly mess.  This...
+                // thing...  magically makes it call good: scale each window
+                // slightly (two pixels larger in each dimension, from the
+                // window's center).
+                final float w = frame.width();
+                final float h = frame.height();
+                if (w>=1 && h>=1) {
+                    tmpMatrix.setScale(1 + 2/w, 1 + 2/h, w/2, h/2);
+                } else {
+                    tmpMatrix.reset();
+                }
+            } else {
+                tmpMatrix.reset();
+            }
+            tmpMatrix.postScale(mWin.mGlobalScale, mWin.mGlobalScale);
+            if (selfTransformation) {
+                tmpMatrix.postConcat(mTransformation.getMatrix());
+            }
+            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+            if (attachedTransformation != null) {
+                tmpMatrix.postConcat(attachedTransformation.getMatrix());
+            }
+            if (appTransformation != null) {
+                tmpMatrix.postConcat(appTransformation.getMatrix());
+            }
+            if (mAnimator.mUniverseBackground != null) {
+                tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
+            }
+            if (screenAnimation) {
+                tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
+            }
+            //TODO (multidisplay): Magnification is supported only for the default display.
+            if (mService.mDisplayMagnifier != null
+                    && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+                MagnificationSpec spec = mService.mDisplayMagnifier
+                        .getMagnificationSpecForWindowLocked(mWin);
+                if (spec != null && !spec.isNop()) {
+                    tmpMatrix.postScale(spec.scale, spec.scale);
+                    tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+                }
+            }
+
+            // "convert" it into SurfaceFlinger's format
+            // (a 2x2 matrix + an offset)
+            // Here we must not transform the position of the surface
+            // since it is already included in the transformation.
+            //Slog.i(TAG, "Transform: " + matrix);
+
+            mHaveMatrix = true;
+            tmpMatrix.getValues(tmpFloats);
+            mDsDx = tmpFloats[Matrix.MSCALE_X];
+            mDtDx = tmpFloats[Matrix.MSKEW_Y];
+            mDsDy = tmpFloats[Matrix.MSKEW_X];
+            mDtDy = tmpFloats[Matrix.MSCALE_Y];
+            float x = tmpFloats[Matrix.MTRANS_X];
+            float y = tmpFloats[Matrix.MTRANS_Y];
+            int w = frame.width();
+            int h = frame.height();
+            mWin.mShownFrame.set(x, y, x+w, y+h);
+
+            // Now set the alpha...  but because our current hardware
+            // can't do alpha transformation on a non-opaque surface,
+            // turn it off if we are running an animation that is also
+            // transforming since it is more important to have that
+            // animation be smooth.
+            mShownAlpha = mAlpha;
+            if (!mService.mLimitedAlphaCompositing
+                    || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
+                    || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDsDy, mDtDy)
+                            && x == frame.left && y == frame.top))) {
+                //Slog.i(TAG, "Applying alpha transform");
+                if (selfTransformation) {
+                    mShownAlpha *= mTransformation.getAlpha();
+                }
+                if (attachedTransformation != null) {
+                    mShownAlpha *= attachedTransformation.getAlpha();
+                }
+                if (appTransformation != null) {
+                    mShownAlpha *= appTransformation.getAlpha();
+                }
+                if (mAnimator.mUniverseBackground != null) {
+                    mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
+                }
+                if (screenAnimation) {
+                    mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
+                }
+            } else {
+                //Slog.i(TAG, "Not applying alpha transform");
+            }
+
+            if ((DEBUG_SURFACE_TRACE || WindowManagerService.localLOGV)
+                    && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
+                    TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
+                    + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
+                    + " attached=" + (attachedTransformation == null ?
+                            "null" : attachedTransformation.getAlpha())
+                    + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
+                    + " screen=" + (screenAnimation ?
+                            screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
+            return;
+        } else if (mIsWallpaper && mService.mInnerFields.mWallpaperActionPending) {
+            return;
+        }
+
+        if (WindowManagerService.localLOGV) Slog.v(
+                TAG, "computeShownFrameLocked: " + this +
+                " not attached, mAlpha=" + mAlpha);
+
+        final boolean applyUniverseTransformation = (mAnimator.mUniverseBackground != null
+                && mWin.mAttrs.type != WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
+                && mWin.mBaseLayer < mAnimator.mAboveUniverseLayer);
+        MagnificationSpec spec = null;
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mService.mDisplayMagnifier != null && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            spec = mService.mDisplayMagnifier.getMagnificationSpecForWindowLocked(mWin);
+        }
+        if (applyUniverseTransformation || spec != null) {
+            final Rect frame = mWin.mFrame;
+            final float tmpFloats[] = mService.mTmpFloats;
+            final Matrix tmpMatrix = mWin.mTmpMatrix;
+
+            tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
+            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+
+            if (applyUniverseTransformation) {
+                tmpMatrix.postConcat(mAnimator.mUniverseBackground.mUniverseTransform.getMatrix());
+            }
+
+            if (spec != null && !spec.isNop()) {
+                tmpMatrix.postScale(spec.scale, spec.scale);
+                tmpMatrix.postTranslate(spec.offsetX, spec.offsetY);
+            }
+
+            tmpMatrix.getValues(tmpFloats);
+
+            mHaveMatrix = true;
+            mDsDx = tmpFloats[Matrix.MSCALE_X];
+            mDtDx = tmpFloats[Matrix.MSKEW_Y];
+            mDsDy = tmpFloats[Matrix.MSKEW_X];
+            mDtDy = tmpFloats[Matrix.MSCALE_Y];
+            float x = tmpFloats[Matrix.MTRANS_X];
+            float y = tmpFloats[Matrix.MTRANS_Y];
+            int w = frame.width();
+            int h = frame.height();
+            mWin.mShownFrame.set(x, y, x + w, y + h);
+
+            mShownAlpha = mAlpha;
+            if (applyUniverseTransformation) {
+                mShownAlpha *= mAnimator.mUniverseBackground.mUniverseTransform.getAlpha();
+            }
+        } else {
+            mWin.mShownFrame.set(mWin.mFrame);
+            if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+                mWin.mShownFrame.offset(mWin.mXOffset, mWin.mYOffset);
+            }
+            mShownAlpha = mAlpha;
+            mHaveMatrix = false;
+            mDsDx = mWin.mGlobalScale;
+            mDtDx = 0;
+            mDsDy = 0;
+            mDtDy = mWin.mGlobalScale;
+        }
+    }
+
+    void applyDecorRect(final Rect decorRect) {
+        final WindowState w = mWin;
+        // Compute the offset of the window in relation to the decor rect.
+        final int offX = w.mXOffset + w.mFrame.left;
+        final int offY = w.mYOffset + w.mFrame.top;
+        // Initialize the decor rect to the entire frame.
+        w.mSystemDecorRect.set(0, 0, w.mFrame.width(), w.mFrame.height());
+        // Intersect with the decor rect, offsetted by window position.
+        w.mSystemDecorRect.intersect(decorRect.left-offX, decorRect.top-offY,
+                decorRect.right-offX, decorRect.bottom-offY);
+        // If size compatibility is being applied to the window, the
+        // surface is scaled relative to the screen.  Also apply this
+        // scaling to the crop rect.  We aren't using the standard rect
+        // scale function because we want to round things to make the crop
+        // always round to a larger rect to ensure we don't crop too
+        // much and hide part of the window that should be seen.
+        if (w.mEnforceSizeCompat && w.mInvGlobalScale != 1.0f) {
+            final float scale = w.mInvGlobalScale;
+            w.mSystemDecorRect.left = (int) (w.mSystemDecorRect.left * scale - 0.5f);
+            w.mSystemDecorRect.top = (int) (w.mSystemDecorRect.top * scale - 0.5f);
+            w.mSystemDecorRect.right = (int) ((w.mSystemDecorRect.right+1) * scale - 0.5f);
+            w.mSystemDecorRect.bottom = (int) ((w.mSystemDecorRect.bottom+1) * scale - 0.5f);
+        }
+    }
+
+    void updateSurfaceWindowCrop(final boolean recoveringMemory) {
+        final WindowState w = mWin;
+        DisplayInfo displayInfo = w.mDisplayContent.getDisplayInfo();
+
+        // Need to recompute a new system decor rect each time.
+        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
+            // Currently can't do this cropping for scaled windows.  We'll
+            // just keep the crop rect the same as the source surface.
+            w.mSystemDecorRect.set(0, 0, w.mRequestedWidth, w.mRequestedHeight);
+        } else if (!w.isDefaultDisplay()) {
+            // On a different display there is no system decor.  Crop the window
+            // by the screen boundaries.
+            w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height());
+            w.mSystemDecorRect.intersect(-w.mCompatFrame.left, -w.mCompatFrame.top,
+                    displayInfo.logicalWidth - w.mCompatFrame.left,
+                    displayInfo.logicalHeight - w.mCompatFrame.top);
+        } else if (w.mLayer >= mService.mSystemDecorLayer) {
+            // Above the decor layer is easy, just use the entire window.
+            // Unless we have a universe background...  in which case all the
+            // windows need to be cropped by the screen, so they don't cover
+            // the universe background.
+            if (mAnimator.mUniverseBackground == null) {
+                w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
+                        w.mCompatFrame.height());
+            } else {
+                applyDecorRect(mService.mScreenRect);
+            }
+        } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND
+                || w.mDecorFrame.isEmpty()) {
+            // The universe background isn't cropped, nor windows without policy decor.
+            w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(),
+                    w.mCompatFrame.height());
+        } else {
+            // Crop to the system decor specified by policy.
+            applyDecorRect(w.mDecorFrame);
+        }
+
+        if (!w.mSystemDecorRect.equals(w.mLastSystemDecorRect)) {
+            w.mLastSystemDecorRect.set(w.mSystemDecorRect);
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "CROP " + w.mSystemDecorRect.toShortString(), null);
+                mSurfaceControl.setWindowCrop(w.mSystemDecorRect);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error setting crop surface of " + w
+                        + " crop=" + w.mSystemDecorRect.toShortString(), e);
+                if (!recoveringMemory) {
+                    mService.reclaimSomeSurfaceMemoryLocked(this, "crop", true);
+                }
+            }
+        }
+    }
+
+    void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
+        final WindowState w = mWin;
+        int width, height;
+        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
+            // for a scaled surface, we just want to use
+            // the requested size.
+            width  = w.mRequestedWidth;
+            height = w.mRequestedHeight;
+        } else {
+            width = w.mCompatFrame.width();
+            height = w.mCompatFrame.height();
+        }
+
+        if (width < 1) {
+            width = 1;
+        }
+        if (height < 1) {
+            height = 1;
+        }
+        final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
+        if (surfaceResized) {
+            mSurfaceW = width;
+            mSurfaceH = height;
+        }
+
+        final float left = w.mShownFrame.left;
+        final float top = w.mShownFrame.top;
+        if (mSurfaceX != left || mSurfaceY != top) {
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "POS " + left + ", " + top, null);
+                mSurfaceX = left;
+                mSurfaceY = top;
+                mSurfaceControl.setPosition(left, top);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error positioning surface of " + w
+                        + " pos=(" + left
+                        + "," + top + ")", e);
+                if (!recoveringMemory) {
+                    mService.reclaimSomeSurfaceMemoryLocked(this, "position", true);
+                }
+            }
+        }
+
+        if (surfaceResized) {
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                        "SIZE " + width + "x" + height, null);
+                mSurfaceResized = true;
+                mSurfaceControl.setSize(width, height);
+                final int displayId = w.mDisplayContent.getDisplayId();
+                mAnimator.setPendingLayoutChanges(displayId,
+                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
+                if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
+                    w.getStack().startDimmingIfNeeded(this);
+                }
+            } catch (RuntimeException e) {
+                // If something goes wrong with the surface (such
+                // as running out of memory), don't take down the
+                // entire system.
+                Slog.e(TAG, "Error resizing surface of " + w
+                        + " size=(" + width + "x" + height + ")", e);
+                if (!recoveringMemory) {
+                    mService.reclaimSomeSurfaceMemoryLocked(this, "size", true);
+                }
+            }
+        }
+
+        updateSurfaceWindowCrop(recoveringMemory);
+    }
+
+    public void prepareSurfaceLocked(final boolean recoveringMemory) {
+        final WindowState w = mWin;
+        if (mSurfaceControl == null) {
+            if (w.mOrientationChanging) {
+                if (DEBUG_ORIENTATION) {
+                    Slog.v(TAG, "Orientation change skips hidden " + w);
+                }
+                w.mOrientationChanging = false;
+            }
+            return;
+        }
+
+        boolean displayed = false;
+
+        computeShownFrameLocked();
+
+        setSurfaceBoundariesLocked(recoveringMemory);
+
+        if (mIsWallpaper && !mWin.mWallpaperVisible) {
+            // Wallpaper is no longer visible and there is no wp target => hide it.
+            hide();
+        } else if (w.mAttachedHidden || !w.isOnScreen()) {
+            hide();
+            mAnimator.hideWallpapersLocked(w);
+
+            // If we are waiting for this window to handle an
+            // orientation change, well, it is hidden, so
+            // doesn't really matter.  Note that this does
+            // introduce a potential glitch if the window
+            // becomes unhidden before it has drawn for the
+            // new orientation.
+            if (w.mOrientationChanging) {
+                w.mOrientationChanging = false;
+                if (DEBUG_ORIENTATION) Slog.v(TAG,
+                        "Orientation change skips hidden " + w);
+            }
+        } else if (mLastLayer != mAnimLayer
+                || mLastAlpha != mShownAlpha
+                || mLastDsDx != mDsDx
+                || mLastDtDx != mDtDx
+                || mLastDsDy != mDsDy
+                || mLastDtDy != mDtDy
+                || w.mLastHScale != w.mHScale
+                || w.mLastVScale != w.mVScale
+                || mLastHidden) {
+            displayed = true;
+            mLastAlpha = mShownAlpha;
+            mLastLayer = mAnimLayer;
+            mLastDsDx = mDsDx;
+            mLastDtDx = mDtDx;
+            mLastDsDy = mDsDy;
+            mLastDtDy = mDtDy;
+            w.mLastHScale = w.mHScale;
+            w.mLastVScale = w.mVScale;
+            if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                    "alpha=" + mShownAlpha + " layer=" + mAnimLayer
+                    + " matrix=[" + (mDsDx*w.mHScale)
+                    + "," + (mDtDx*w.mVScale)
+                    + "][" + (mDsDy*w.mHScale)
+                    + "," + (mDtDy*w.mVScale) + "]", null);
+            if (mSurfaceControl != null) {
+                try {
+                    mSurfaceAlpha = mShownAlpha;
+                    mSurfaceControl.setAlpha(mShownAlpha);
+                    mSurfaceLayer = mAnimLayer;
+                    mSurfaceControl.setLayer(mAnimLayer);
+                    mSurfaceControl.setMatrix(
+                        mDsDx*w.mHScale, mDtDx*w.mVScale,
+                        mDsDy*w.mHScale, mDtDy*w.mVScale);
+
+                    if (mLastHidden && mDrawState == HAS_DRAWN) {
+                        if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
+                                "SHOW (performLayout)", null);
+                        if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Showing " + w
+                                + " during relayout");
+                        if (showSurfaceRobustlyLocked()) {
+                            mLastHidden = false;
+                            if (mIsWallpaper) {
+                                mService.dispatchWallpaperVisibility(w, true);
+                            }
+                            // This draw means the difference between unique content and mirroring.
+                            // Run another pass through performLayout to set mHasContent in the
+                            // LogicalDisplay.
+                            mAnimator.setPendingLayoutChanges(w.getDisplayId(),
+                                    WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
+                        } else {
+                            w.mOrientationChanging = false;
+                        }
+                    }
+                    if (mSurfaceControl != null) {
+                        w.mToken.hasVisible = true;
+                    }
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Error updating surface in " + w, e);
+                    if (!recoveringMemory) {
+                        mService.reclaimSomeSurfaceMemoryLocked(this, "update", true);
+                    }
+                }
+            }
+        } else {
+            if (DEBUG_ANIM && isAnimating()) {
+                Slog.v(TAG, "prepareSurface: No changes in animation for " + this);
+            }
+            displayed = true;
+        }
+
+        if (displayed) {
+            if (w.mOrientationChanging) {
+                if (!w.isDrawnLw()) {
+                    mAnimator.mBulkUpdateParams &= ~SET_ORIENTATION_CHANGE_COMPLETE;
+                    mAnimator.mLastWindowFreezeSource = w;
+                    if (DEBUG_ORIENTATION) Slog.v(TAG,
+                            "Orientation continue waiting for draw in " + w);
+                } else {
+                    w.mOrientationChanging = false;
+                    if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation change complete in " + w);
+                }
+            }
+            w.mToken.hasVisible = true;
+        }
+    }
+
+    void setTransparentRegionHintLocked(final Region region) {
+        if (mSurfaceControl == null) {
+            Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true");
+            return;
+        }
+        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+            ">>> OPEN TRANSACTION setTransparentRegion");
+        SurfaceControl.openTransaction();
+        try {
+            if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
+                    "transparentRegionHint=" + region, null);
+            mSurfaceControl.setTransparentRegionHint(region);
+        } finally {
+            SurfaceControl.closeTransaction();
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    "<<< CLOSE TRANSACTION setTransparentRegion");
+        }
+    }
+
+    void setWallpaperOffset(RectF shownFrame) {
+        final int left = (int) shownFrame.left;
+        final int top = (int) shownFrame.top;
+        if (mSurfaceX != left || mSurfaceY != top) {
+            mSurfaceX = left;
+            mSurfaceY = top;
+            if (mAnimating) {
+                // If this window (or its app token) is animating, then the position
+                // of the surface will be re-computed on the next animation frame.
+                // We can't poke it directly here because it depends on whatever
+                // transformation is being applied by the animation.
+                return;
+            }
+            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                    ">>> OPEN TRANSACTION setWallpaperOffset");
+            SurfaceControl.openTransaction();
+            try {
+                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin,
+                        "POS " + left + ", " + top, null);
+                mSurfaceControl.setPosition(mWin.mFrame.left + left, mWin.mFrame.top + top);
+                updateSurfaceWindowCrop(false);
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Error positioning surface of " + mWin
+                        + " pos=(" + left + "," + top + ")", e);
+            } finally {
+                SurfaceControl.closeTransaction();
+                if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
+                        "<<< CLOSE TRANSACTION setWallpaperOffset");
+            }
+        }
+    }
+
+    // This must be called while inside a transaction.
+    boolean performShowLocked() {
+        if (mWin.isHiddenFromUserLocked()) {
+            Slog.w(TAG, "current user violation " + mService.mCurrentUserId + " trying to display "
+                    + this + ", type " + mWin.mAttrs.type + ", belonging to " + mWin.mOwnerUid);
+            return false;
+        }
+        if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
+                mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
+            RuntimeException e = null;
+            if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                e = new RuntimeException();
+                e.fillInStackTrace();
+            }
+            Slog.v(TAG, "performShow on " + this
+                    + ": mDrawState=" + mDrawState + " readyForDisplay="
+                    + mWin.isReadyForDisplayIgnoringKeyguard()
+                    + " starting=" + (mWin.mAttrs.type == TYPE_APPLICATION_STARTING)
+                    + " during animation: policyVis=" + mWin.mPolicyVisibility
+                    + " attHidden=" + mWin.mAttachedHidden
+                    + " tok.hiddenRequested="
+                    + (mWin.mAppToken != null ? mWin.mAppToken.hiddenRequested : false)
+                    + " tok.hidden="
+                    + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false)
+                    + " animating=" + mAnimating
+                    + " tok animating="
+                    + (mAppAnimator != null ? mAppAnimator.animating : false), e);
+        }
+        if (mDrawState == READY_TO_SHOW && mWin.isReadyForDisplayIgnoringKeyguard()) {
+            if (SHOW_TRANSACTIONS || DEBUG_ORIENTATION)
+                WindowManagerService.logSurface(mWin, "SHOW (performShowLocked)", null);
+            if (DEBUG_VISIBILITY || (DEBUG_STARTING_WINDOW &&
+                    mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_STARTING)) {
+                Slog.v(TAG, "Showing " + this
+                        + " during animation: policyVis=" + mWin.mPolicyVisibility
+                        + " attHidden=" + mWin.mAttachedHidden
+                        + " tok.hiddenRequested="
+                        + (mWin.mAppToken != null ? mWin.mAppToken.hiddenRequested : false)
+                        + " tok.hidden="
+                        + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false)
+                        + " animating=" + mAnimating
+                        + " tok animating="
+                        + (mAppAnimator != null ? mAppAnimator.animating : false));
+            }
+
+            mService.enableScreenIfNeededLocked();
+
+            applyEnterAnimationLocked();
+
+            // Force the show in the next prepareSurfaceLocked() call.
+            mLastAlpha = -1;
+            if (DEBUG_SURFACE_TRACE || DEBUG_ANIM)
+                Slog.v(TAG, "performShowLocked: mDrawState=HAS_DRAWN in " + this);
+            mDrawState = HAS_DRAWN;
+            mService.scheduleAnimationLocked();
+
+            int i = mWin.mChildWindows.size();
+            while (i > 0) {
+                i--;
+                WindowState c = mWin.mChildWindows.get(i);
+                if (c.mAttachedHidden) {
+                    c.mAttachedHidden = false;
+                    if (c.mWinAnimator.mSurfaceControl != null) {
+                        c.mWinAnimator.performShowLocked();
+                        // It hadn't been shown, which means layout not
+                        // performed on it, so now we want to make sure to
+                        // do a layout.  If called from within the transaction
+                        // loop, this will cause it to restart with a new
+                        // layout.
+                        c.mDisplayContent.layoutNeeded = true;
+                    }
+                }
+            }
+
+            if (mWin.mAttrs.type != TYPE_APPLICATION_STARTING
+                    && mWin.mAppToken != null) {
+                mWin.mAppToken.firstWindowDrawn = true;
+
+                if (mWin.mAppToken.startingData != null) {
+                    if (WindowManagerService.DEBUG_STARTING_WINDOW ||
+                            WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                            "Finish starting " + mWin.mToken
+                            + ": first real window is shown, no animation");
+                    // If this initial window is animating, stop it -- we
+                    // will do an animation to reveal it from behind the
+                    // starting window, so there is no need for it to also
+                    // be doing its own stuff.
+                    clearAnimation();
+                    mService.mFinishedStarting.add(mWin.mAppToken);
+                    mService.mH.sendEmptyMessage(H.FINISHED_STARTING);
+                }
+                mWin.mAppToken.updateReportedVisibilityLocked();
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Have the surface flinger show a surface, robustly dealing with
+     * error conditions.  In particular, if there is not enough memory
+     * to show the surface, then we will try to get rid of other surfaces
+     * in order to succeed.
+     *
+     * @return Returns true if the surface was successfully shown.
+     */
+    boolean showSurfaceRobustlyLocked() {
+        try {
+            if (mSurfaceControl != null) {
+                mSurfaceShown = true;
+                mSurfaceControl.show();
+                if (mWin.mTurnOnScreen) {
+                    if (DEBUG_VISIBILITY) Slog.v(TAG,
+                            "Show surface turning screen on: " + mWin);
+                    mWin.mTurnOnScreen = false;
+                    mAnimator.mBulkUpdateParams |= SET_TURN_ON_SCREEN;
+                }
+            }
+            return true;
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Failure showing surface " + mSurfaceControl + " in " + mWin, e);
+        }
+
+        mService.reclaimSomeSurfaceMemoryLocked(this, "show", true);
+
+        return false;
+    }
+
+    void applyEnterAnimationLocked() {
+        final int transit;
+        if (mEnterAnimationPending) {
+            mEnterAnimationPending = false;
+            transit = WindowManagerPolicy.TRANSIT_ENTER;
+        } else {
+            transit = WindowManagerPolicy.TRANSIT_SHOW;
+        }
+        applyAnimationLocked(transit, true);
+        //TODO (multidisplay): Magnification is supported only for the default display.
+        if (mService.mDisplayMagnifier != null
+                && mWin.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            mService.mDisplayMagnifier.onWindowTransitionLocked(mWin, transit);
+        }
+    }
+
+    /**
+     * Choose the correct animation and set it to the passed WindowState.
+     * @param transit If AppTransition.TRANSIT_PREVIEW_DONE and the app window has been drawn
+     *      then the animation will be app_starting_exit. Any other value loads the animation from
+     *      the switch statement below.
+     * @param isEntrance The animation type the last time this was called. Used to keep from
+     *      loading the same animation twice.
+     * @return true if an animation has been loaded.
+     */
+    boolean applyAnimationLocked(int transit, boolean isEntrance) {
+        if (mLocalAnimating && mAnimationIsEntrance == isEntrance) {
+            // If we are trying to apply an animation, but already running
+            // an animation of the same type, then just leave that one alone.
+            return true;
+        }
+
+        // Only apply an animation if the display isn't frozen.  If it is
+        // frozen, there is no reason to animate and it can cause strange
+        // artifacts when we unfreeze the display if some different animation
+        // is running.
+        if (mService.okToDisplay()) {
+            int anim = mPolicy.selectAnimationLw(mWin, transit);
+            int attr = -1;
+            Animation a = null;
+            if (anim != 0) {
+                a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
+            } else {
+                switch (transit) {
+                    case WindowManagerPolicy.TRANSIT_ENTER:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_EXIT:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_SHOW:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
+                        break;
+                    case WindowManagerPolicy.TRANSIT_HIDE:
+                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
+                        break;
+                }
+                if (attr >= 0) {
+                    a = mService.mAppTransition.loadAnimation(mWin.mAttrs, attr);
+                }
+            }
+            if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG,
+                    "applyAnimation: win=" + this
+                    + " anim=" + anim + " attr=0x" + Integer.toHexString(attr)
+                    + " a=" + a
+                    + " transit=" + transit
+                    + " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
+            if (a != null) {
+                if (WindowManagerService.DEBUG_ANIM) {
+                    RuntimeException e = null;
+                    if (!WindowManagerService.HIDE_STACK_CRAWLS) {
+                        e = new RuntimeException();
+                        e.fillInStackTrace();
+                    }
+                    Slog.v(TAG, "Loaded animation " + a + " for " + this, e);
+                }
+                setAnimation(a);
+                mAnimationIsEntrance = isEntrance;
+            }
+        } else {
+            clearAnimation();
+        }
+
+        return mAnimation != null;
+    }
+
+    public void dump(PrintWriter pw, String prefix, boolean dumpAll) {
+        if (mAnimating || mLocalAnimating || mAnimationIsEntrance
+                || mAnimation != null) {
+            pw.print(prefix); pw.print("mAnimating="); pw.print(mAnimating);
+                    pw.print(" mLocalAnimating="); pw.print(mLocalAnimating);
+                    pw.print(" mAnimationIsEntrance="); pw.print(mAnimationIsEntrance);
+                    pw.print(" mAnimation="); pw.println(mAnimation);
+        }
+        if (mHasTransformation || mHasLocalTransformation) {
+            pw.print(prefix); pw.print("XForm: has=");
+                    pw.print(mHasTransformation);
+                    pw.print(" hasLocal="); pw.print(mHasLocalTransformation);
+                    pw.print(" "); mTransformation.printShortString(pw);
+                    pw.println();
+        }
+        if (mSurfaceControl != null) {
+            if (dumpAll) {
+                pw.print(prefix); pw.print("mSurface="); pw.println(mSurfaceControl);
+                pw.print(prefix); pw.print("mDrawState=");
+                pw.print(drawStateToString(mDrawState));
+                pw.print(" mLastHidden="); pw.println(mLastHidden);
+            }
+            pw.print(prefix); pw.print("Surface: shown="); pw.print(mSurfaceShown);
+                    pw.print(" layer="); pw.print(mSurfaceLayer);
+                    pw.print(" alpha="); pw.print(mSurfaceAlpha);
+                    pw.print(" rect=("); pw.print(mSurfaceX);
+                    pw.print(","); pw.print(mSurfaceY);
+                    pw.print(") "); pw.print(mSurfaceW);
+                    pw.print(" x "); pw.println(mSurfaceH);
+        }
+        if (mPendingDestroySurface != null) {
+            pw.print(prefix); pw.print("mPendingDestroySurface=");
+                    pw.println(mPendingDestroySurface);
+        }
+        if (mSurfaceResized || mSurfaceDestroyDeferred) {
+            pw.print(prefix); pw.print("mSurfaceResized="); pw.print(mSurfaceResized);
+                    pw.print(" mSurfaceDestroyDeferred="); pw.println(mSurfaceDestroyDeferred);
+        }
+        if (mWin.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) {
+            pw.print(prefix); pw.print("mUniverseTransform=");
+                    mUniverseTransform.printShortString(pw);
+                    pw.println();
+        }
+        if (mShownAlpha != 1 || mAlpha != 1 || mLastAlpha != 1) {
+            pw.print(prefix); pw.print("mShownAlpha="); pw.print(mShownAlpha);
+                    pw.print(" mAlpha="); pw.print(mAlpha);
+                    pw.print(" mLastAlpha="); pw.println(mLastAlpha);
+        }
+        if (mHaveMatrix || mWin.mGlobalScale != 1) {
+            pw.print(prefix); pw.print("mGlobalScale="); pw.print(mWin.mGlobalScale);
+                    pw.print(" mDsDx="); pw.print(mDsDx);
+                    pw.print(" mDtDx="); pw.print(mDtDx);
+                    pw.print(" mDsDy="); pw.print(mDsDy);
+                    pw.print(" mDtDy="); pw.println(mDtDy);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer("WindowStateAnimator{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append(' ');
+        sb.append(mWin.mAttrs.getTitle());
+        sb.append('}');
+        return sb.toString();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
new file mode 100644
index 0000000..2267123
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.os.IBinder;
+
+import java.io.PrintWriter;
+
+/**
+ * Container of a set of related windows in the window manager.  Often this
+ * is an AppWindowToken, which is the handle for an Activity that it uses
+ * to display windows.  For nested windows, there is a WindowToken created for
+ * the parent window to manage its children.
+ */
+class WindowToken {
+    // The window manager!
+    final WindowManagerService service;
+    
+    // The actual token.
+    final IBinder token;
+
+    // The type of window this token is for, as per WindowManager.LayoutParams.
+    final int windowType;
+
+    // Set if this token was explicitly added by a client, so should
+    // not be removed when all windows are removed.
+    final boolean explicit;
+
+    // For printing.
+    String stringName;
+
+    // If this is an AppWindowToken, this is non-null.
+    AppWindowToken appWindowToken;
+
+    // All of the windows associated with this token.
+    final WindowList windows = new WindowList();
+
+    // Is key dispatching paused for this token?
+    boolean paused = false;
+
+    // Should this token's windows be hidden?
+    boolean hidden;
+
+    // Temporary for finding which tokens no longer have visible windows.
+    boolean hasVisible;
+
+    // Set to true when this token is in a pending transaction where it
+    // will be shown.
+    boolean waitingToShow;
+
+    // Set to true when this token is in a pending transaction where it
+    // will be hidden.
+    boolean waitingToHide;
+
+    // Set to true when this token is in a pending transaction where its
+    // windows will be put to the bottom of the list.
+    boolean sendingToBottom;
+
+    WindowToken(WindowManagerService _service, IBinder _token, int type, boolean _explicit) {
+        service = _service;
+        token = _token;
+        windowType = type;
+        explicit = _explicit;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.print(prefix); pw.print("windows="); pw.println(windows);
+        pw.print(prefix); pw.print("windowType="); pw.print(windowType);
+                pw.print(" hidden="); pw.print(hidden);
+                pw.print(" hasVisible="); pw.println(hasVisible);
+        if (waitingToShow || waitingToHide || sendingToBottom) {
+            pw.print(prefix); pw.print("waitingToShow="); pw.print(waitingToShow);
+                    pw.print(" waitingToHide="); pw.print(waitingToHide);
+                    pw.print(" sendingToBottom="); pw.print(sendingToBottom);
+        }
+    }
+
+    @Override
+    public String toString() {
+        if (stringName == null) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("WindowToken{");
+            sb.append(Integer.toHexString(System.identityHashCode(this)));
+            sb.append(" "); sb.append(token); sb.append('}');
+            stringName = sb.toString();
+        }
+        return stringName;
+    }
+}
\ No newline at end of file