OSDN Git Service

Merge "Put dither in GradientDrawable constant state"
authorAlan Viverette <alanv@google.com>
Fri, 20 Jun 2014 00:14:27 +0000 (00:14 +0000)
committerAndroid (Google) Code Review <android-gerrit@google.com>
Fri, 20 Jun 2014 00:06:01 +0000 (00:06 +0000)
46 files changed:
api/current.txt
core/java/android/app/backup/BackupTransport.java
core/java/android/content/pm/ActivityInfo.java
core/java/android/content/pm/PackageManager.java
core/java/android/content/pm/PackageParser.java
core/java/android/hardware/Camera.java
core/java/android/hardware/ICameraService.aidl
core/java/android/provider/ContactsContract.java
core/java/com/android/internal/app/ProcessStats.java
core/java/com/android/internal/backup/LocalTransport.java
core/java/com/android/internal/os/BatteryStatsImpl.java
core/jni/android_hardware_Camera.cpp
core/res/AndroidManifest.xml
core/res/res/values/attrs_manifest.xml
core/res/res/values/public.xml
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java [new file with mode: 0644]
packages/SystemUI/res/drawable/ic_account_circle.xml
packages/SystemUI/res/layout/keyguard_user_switcher.xml [new file with mode: 0644]
packages/SystemUI/res/layout/keyguard_user_switcher_item.xml [new file with mode: 0644]
packages/SystemUI/res/layout/status_bar_expanded.xml
packages/SystemUI/res/layout/status_bar_expanded_header.xml
packages/SystemUI/res/values-sw600dp/config.xml
packages/SystemUI/res/values/attrs.xml
packages/SystemUI/res/values/colors.xml
packages/SystemUI/res/values/config.xml
packages/SystemUI/res/values/dimens.xml
packages/SystemUI/res/values/styles.xml
packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java [new file with mode: 0644]
packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java [new file with mode: 0644]
services/core/java/com/android/server/am/ActiveServices.java
services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityRecord.java
services/core/java/com/android/server/am/ActivityStack.java
services/core/java/com/android/server/am/ActivityStackSupervisor.java
services/core/java/com/android/server/am/BroadcastQueue.java
services/core/java/com/android/server/am/ProcessRecord.java
services/core/java/com/android/server/am/ProcessStatsService.java
services/core/java/com/android/server/am/TaskRecord.java
services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
services/core/java/com/android/server/hdmi/HdmiControlService.java
services/core/java/com/android/server/pm/PackageManagerService.java
services/core/java/com/android/server/tv/TvInputManagerService.java
wifi/java/android/net/wifi/WifiConfiguration.java

index e0e8ce9..03b3013 100644 (file)
@@ -957,6 +957,7 @@ package android {
     field public static final int ratingBarStyleIndicator = 16843280; // 0x1010210
     field public static final int ratingBarStyleSmall = 16842877; // 0x101007d
     field public static final int readPermission = 16842759; // 0x1010007
+    field public static final int relinquishTaskIdentity = 16843893; // 0x1010475
     field public static final int repeatCount = 16843199; // 0x10101bf
     field public static final int repeatMode = 16843200; // 0x10101c0
     field public static final int reqFiveWayNav = 16843314; // 0x1010232
@@ -8085,6 +8086,7 @@ package android.content.pm {
     field public static final int FLAG_IMMERSIVE = 2048; // 0x800
     field public static final int FLAG_MULTIPROCESS = 1; // 0x1
     field public static final int FLAG_NO_HISTORY = 128; // 0x80
+    field public static final int FLAG_RELINQUISH_TASK_IDENTITY = 4096; // 0x1000
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
     field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10
     field public static final int LAUNCH_MULTIPLE = 0; // 0x0
@@ -23461,7 +23463,7 @@ package android.provider {
     field public static final java.lang.String CONTENT_DIRECTORY = "data";
   }
 
-  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
+  public static final class ContactsContract.Contacts.Entity implements android.provider.BaseColumns android.provider.ContactsContract.BaseSyncColumns android.provider.ContactsContract.ContactNameColumns android.provider.ContactsContract.ContactOptionsColumns android.provider.ContactsContract.ContactStatusColumns android.provider.ContactsContract.ContactsColumns android.provider.ContactsContract.DataColumns android.provider.ContactsContract.DataUsageStatColumns android.provider.ContactsContract.RawContactsColumns android.provider.ContactsContract.StatusColumns android.provider.ContactsContract.SyncColumns {
     field public static final java.lang.String CONTENT_DIRECTORY = "entities";
     field public static final java.lang.String DATA_ID = "data_id";
     field public static final java.lang.String RAW_CONTACT_ID = "raw_contact_id";
index 706ef04..4631323 100644 (file)
@@ -31,12 +31,22 @@ import com.android.internal.backup.IBackupTransport;
  * @hide
  */
 public class BackupTransport {
+    // Zero return always means things are okay.  If returned from
+    // getNextFullRestoreDataChunk(), it means that no data could be delivered at
+    // this time, but the restore is still running and the caller should simply
+    // retry.
     public static final int TRANSPORT_OK = 0;
-    public static final int TRANSPORT_ERROR = 1;
-    public static final int TRANSPORT_NOT_INITIALIZED = 2;
-    public static final int TRANSPORT_PACKAGE_REJECTED = 3;
-    public static final int AGENT_ERROR = 4;
-    public static final int AGENT_UNKNOWN = 5;
+
+    // -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
+    // we've delivered the entire data stream for the current restore target.
+    public static final int NO_MORE_DATA = -1;
+
+    // Result codes that indicate real errors are negative and not -1
+    public static final int TRANSPORT_ERROR = -1000;
+    public static final int TRANSPORT_NOT_INITIALIZED = -1001;
+    public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
+    public static final int AGENT_ERROR = -1003;
+    public static final int AGENT_UNKNOWN = -1004;
 
     IBackupTransport mBinderImpl = new TransportImpl();
     /** @hide */
@@ -370,11 +380,14 @@ public class BackupTransport {
      * @param socket The file descriptor that the transport will use for delivering the
      *    streamed archive.  The transport must close this socket in all cases when returning
      *    from this method.
-     * @return 0 when no more data for the current package is available.  A positive value
-     *    indicates the presence of that many bytes to be delivered to the app.  Any negative
-     *    return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR},
-     *    indicating a fatal error condition that precludes further restore operations
-     *    on the current dataset.
+     * @return {@link #NO_MORE_DATA} when no more data for the current package is available.
+     *    A positive value indicates the presence of that many bytes to be delivered to the app.
+     *    A value of zero indicates that no data was deliverable at this time, but the restore
+     *    is still running and the caller should retry.  {@link #TRANSPORT_PACKAGE_REJECTED}
+     *    means that the current package's restore operation should be aborted, but that
+     *    the transport itself is still in a good state and so a multiple-package restore
+     *    sequence can still be continued.  Any other negative return value is treated as a
+     *    fatal error condition that aborts all further restore operations on the current dataset.
      */
     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
         return 0;
index abc8cde..bcf1e87 100644 (file)
@@ -252,6 +252,13 @@ public class ActivityInfo extends ComponentInfo
      */
     public static final int FLAG_IMMERSIVE = 0x0800;
     /**
+     * Bit in {@link #flags}: If set, a task rooted at this activity will have its
+     * baseIntent replaced by the activity immediately above this. Each activity may further
+     * relinquish its identity to the activity above it using this flag. Set from the
+     * android.R.attr#relinquishTaskIdentity attribute.
+     */
+    public static final int FLAG_RELINQUISH_TASK_IDENTITY = 0x1000;
+    /**
      * Bit in {@link #flags} indicating that tasks started with this activity are to be
      * removed from the recent list of tasks when the last activity in the task is finished.
      * {@link android.R.attr#autoRemoveFromRecents}
index b5ceebe..9d871c5 100644 (file)
@@ -2892,6 +2892,7 @@ public abstract class PackageManager {
             PackageParser.Package pkg = parser.parseMonolithicPackage(apkFile, 0);
             if ((flags & GET_SIGNATURES) != 0) {
                 parser.collectCertificates(pkg, 0);
+                parser.collectManifestDigest(pkg);
             }
             PackageUserState state = new PackageUserState();
             return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0, null, state);
index 546f3a5..b40a441 100644 (file)
@@ -744,6 +744,8 @@ public class PackageParser {
      * {@code AndroidManifest.xml}, {@code true} is returned.
      */
     public void collectManifestDigest(Package pkg) throws PackageParserException {
+        pkg.manifestDigest = null;
+
         // TODO: extend to gather digest for split APKs
         try {
             final StrictJarFile jarFile = new StrictJarFile(pkg.codePath);
@@ -2627,6 +2629,12 @@ public class PackageParser {
                     false)) {
                 a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
             }
+
+            if (sa.getBoolean(
+                    com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
+                    false)) {
+                a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
+            }
         } else {
             a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
             a.info.configChanges = 0;
index 0705e0c..44fc3b6 100644 (file)
@@ -172,6 +172,11 @@ public class Camera {
     private static final int NO_ERROR = 0;
     private static final int EACCESS = -13;
     private static final int ENODEV = -19;
+    private static final int EBUSY = -16;
+    private static final int EINVAL = -22;
+    private static final int ENOSYS = -38;
+    private static final int EUSERS = -87;
+    private static final int EOPNOTSUPP = -95;
 
     /**
      * Broadcast Action:  A new picture is taken by the camera, and the entry of
@@ -190,6 +195,22 @@ public class Camera {
     public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
 
     /**
+     * Camera HAL device API version 1.0
+     * @hide
+     */
+    public static final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+
+    /**
+     * A constant meaning the normal camera connect/open will be used.
+     * @hide
+     */
+    public static final int CAMERA_HAL_API_VERSION_NORMAL_OPEN = -2;
+
+    /**
+     * Used to indicate HAL version un-specified.
+     */
+    private static final int CAMERA_HAL_API_VERSION_UNSPECIFIED = -1;
+    /**
      * Hardware face detection. It does not use much CPU.
      */
     private static final int CAMERA_FACE_DETECTION_HW = 0;
@@ -331,6 +352,111 @@ public class Camera {
         return null;
     }
 
+    /**
+     * Creates a new Camera object to access a particular hardware camera with
+     * given hal API version. If the same camera is opened by other applications
+     * or the hal API version is not supported by this device, this will throw a
+     * RuntimeException.
+     * <p>
+     * You must call {@link #release()} when you are done using the camera,
+     * otherwise it will remain locked and be unavailable to other applications.
+     * <p>
+     * Your application should only have one Camera object active at a time for
+     * a particular hardware camera.
+     * <p>
+     * Callbacks from other methods are delivered to the event loop of the
+     * thread which called open(). If this thread has no event loop, then
+     * callbacks are delivered to the main application event loop. If there is
+     * no main application event loop, callbacks are not delivered.
+     * <p class="caution">
+     * <b>Caution:</b> On some devices, this method may take a long time to
+     * complete. It is best to call this method from a worker thread (possibly
+     * using {@link android.os.AsyncTask}) to avoid blocking the main
+     * application UI thread.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as. When
+     * it is {@value #CAMERA_HAL_API_VERSION_NORMAL_OPEN}, the methods will be equivalent
+     * to {@link #open}, but more detailed error information will be returned to managed code.
+     * @return a new Camera object, connected, locked and ready for use.
+     * @throws RuntimeException if opening the camera fails (for example, if the
+     * camera is in use by another process or device policy manager has disabled
+     * the camera).
+     * @see android.app.admin.DevicePolicyManager#getCameraDisabled(android.content.ComponentName)
+     *
+     * @hide
+     */
+    public static Camera openLegacy(int cameraId, int halVersion) {
+        return new Camera(cameraId, halVersion);
+    }
+
+    /**
+     * Create a legacy camera object.
+     *
+     * @param cameraId The hardware camera to access, between 0 and
+     * {@link #getNumberOfCameras()}-1.
+     * @param halVersion The HAL API version this camera device to be opened as.
+     */
+    private Camera(int cameraId, int halVersion) {
+        int err = cameraInit(cameraId, halVersion);
+        if (checkInitErrors(err)) {
+            switch(err) {
+                case EACCESS:
+                    throw new RuntimeException("Fail to connect to camera service");
+                case ENODEV:
+                    throw new RuntimeException("Camera initialization failed");
+                case ENOSYS:
+                    throw new RuntimeException("Camera initialization failed because some methods"
+                            + " are not implemented");
+                case EOPNOTSUPP:
+                    throw new RuntimeException("Camera initialization failed because the hal"
+                            + " version is not supported by this device");
+                case EINVAL:
+                    throw new RuntimeException("Camera initialization failed because the input"
+                            + " arugments are invalid");
+                case EBUSY:
+                    throw new RuntimeException("Camera initialization failed because the camera"
+                            + " device was already opened");
+                case EUSERS:
+                    throw new RuntimeException("Camera initialization failed because the max"
+                            + " number of camera devices were already opened");
+                default:
+                    // Should never hit this.
+                    throw new RuntimeException("Unknown camera error");
+            }
+        }
+    }
+
+    private int cameraInit(int cameraId, int halVersion) {
+        // This function should be only called by Camera(int cameraId, int halVersion).
+        if (halVersion < CAMERA_HAL_API_VERSION_1_0 &&
+                halVersion != CAMERA_HAL_API_VERSION_NORMAL_OPEN) {
+            throw new IllegalArgumentException("Invalid HAL version " + halVersion);
+        }
+
+        mShutterCallback = null;
+        mRawImageCallback = null;
+        mJpegCallback = null;
+        mPreviewCallback = null;
+        mPostviewCallback = null;
+        mUsingPreviewAllocation = false;
+        mZoomListener = null;
+
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
+        String packageName = ActivityThread.currentPackageName();
+
+        return native_setup(new WeakReference<Camera>(this), cameraId, halVersion, packageName);
+    }
+
     Camera(int cameraId) {
         int err = cameraInit(cameraId);
         if (checkInitErrors(err)) {
@@ -369,7 +495,8 @@ public class Camera {
 
         String packageName = ActivityThread.currentPackageName();
 
-        return native_setup(new WeakReference<Camera>(this), cameraId, packageName);
+        return native_setup(new WeakReference<Camera>(this), cameraId,
+                CAMERA_HAL_API_VERSION_UNSPECIFIED, packageName);
     }
 
     /**
@@ -396,7 +523,7 @@ public class Camera {
         release();
     }
 
-    private native final int native_setup(Object camera_this, int cameraId,
+    private native final int native_setup(Object camera_this, int cameraId, int halVersion,
                                            String packageName);
 
     private native final void native_release();
index 31896f5..2bc3dd4 100644 (file)
@@ -74,4 +74,11 @@ interface ICameraService
     int getLegacyParameters(int cameraId, out String[] parameters);
     // Determines if a particular API version is supported; see ICameraService.h for version defines
     int supportsCameraApi(int cameraId, int apiVersion);
+
+    int connectLegacy(ICameraClient client, int cameraId,
+                    int halVersion,
+                    String clientPackageName,
+                    int clientUid,
+                    // Container for an ICamera object
+                    out BinderHolder device);
 }
index 0593720..8038a38 100644 (file)
@@ -1694,7 +1694,7 @@ public final class ContactsContract {
          */
         public static final class Entity implements BaseColumns, ContactsColumns,
                 ContactNameColumns, RawContactsColumns, BaseSyncColumns, SyncColumns, DataColumns,
-                StatusColumns, ContactOptionsColumns, ContactStatusColumns {
+                StatusColumns, ContactOptionsColumns, ContactStatusColumns, DataUsageStatColumns {
             /**
              * no public constructor since this is a utility class
              */
index 7e11850..4995ea1 100644 (file)
@@ -28,7 +28,6 @@ import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
-import android.webkit.WebViewFactory;
 
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -55,6 +54,11 @@ public final class ProcessStats implements Parcelable {
     // that is done.
     public static long COMMIT_PERIOD = 3*60*60*1000;  // Commit current stats every 3 hours
 
+    // Minimum uptime period before committing.  If the COMMIT_PERIOD has elapsed but
+    // the total uptime has not exceeded this amount, then the commit will be held until
+    // it is reached.
+    public static long COMMIT_UPTIME_PERIOD = 60*60*1000;  // Must have at least 1 hour elapsed
+
     public static final int STATE_NOTHING = -1;
     public static final int STATE_PERSISTENT = 0;
     public static final int STATE_TOP = 1;
@@ -81,6 +85,24 @@ public final class ProcessStats implements Parcelable {
     public static final int PSS_USS_MAXIMUM = 6;
     public static final int PSS_COUNT = PSS_USS_MAXIMUM+1;
 
+    public static final int SYS_MEM_USAGE_SAMPLE_COUNT = 0;
+    public static final int SYS_MEM_USAGE_CACHED_MINIMUM = 1;
+    public static final int SYS_MEM_USAGE_CACHED_AVERAGE = 2;
+    public static final int SYS_MEM_USAGE_CACHED_MAXIMUM = 3;
+    public static final int SYS_MEM_USAGE_FREE_MINIMUM = 4;
+    public static final int SYS_MEM_USAGE_FREE_AVERAGE = 5;
+    public static final int SYS_MEM_USAGE_FREE_MAXIMUM = 6;
+    public static final int SYS_MEM_USAGE_ZRAM_MINIMUM = 7;
+    public static final int SYS_MEM_USAGE_ZRAM_AVERAGE = 8;
+    public static final int SYS_MEM_USAGE_ZRAM_MAXIMUM = 9;
+    public static final int SYS_MEM_USAGE_KERNEL_MINIMUM = 10;
+    public static final int SYS_MEM_USAGE_KERNEL_AVERAGE = 11;
+    public static final int SYS_MEM_USAGE_KERNEL_MAXIMUM = 12;
+    public static final int SYS_MEM_USAGE_NATIVE_MINIMUM = 13;
+    public static final int SYS_MEM_USAGE_NATIVE_AVERAGE = 14;
+    public static final int SYS_MEM_USAGE_NATIVE_MAXIMUM = 15;
+    public static final int SYS_MEM_USAGE_COUNT = SYS_MEM_USAGE_NATIVE_MAXIMUM+1;
+
     public static final int ADJ_NOTHING = -1;
     public static final int ADJ_MEM_FACTOR_NORMAL = 0;
     public static final int ADJ_MEM_FACTOR_MODERATE = 1;
@@ -174,7 +196,7 @@ public final class ProcessStats implements Parcelable {
     static final String CSV_SEP = "\t";
 
     // Current version of the parcel format.
-    private static final int PARCEL_VERSION = 14;
+    private static final int PARCEL_VERSION = 18;
     // In-memory Parcel magic number, used to detect attempts to unmarshall bad data
     private static final int MAGIC = 0x50535453;
 
@@ -200,11 +222,16 @@ public final class ProcessStats implements Parcelable {
     public int mMemFactor = STATE_NOTHING;
     public long mStartTime;
 
+    public int[] mSysMemUsageTable = null;
+    public int mSysMemUsageTableSize = 0;
+    public final long[] mSysMemUsageArgs = new long[SYS_MEM_USAGE_COUNT];
+
     public long mTimePeriodStartClock;
     public long mTimePeriodStartRealtime;
     public long mTimePeriodEndRealtime;
+    public long mTimePeriodStartUptime;
+    public long mTimePeriodEndUptime;
     String mRuntime;
-    String mWebView;
     boolean mRunning;
 
     static final int LONGS_SIZE = 4096;
@@ -304,11 +331,77 @@ public final class ProcessStats implements Parcelable {
             mMemFactorDurations[i] += other.mMemFactorDurations[i];
         }
 
+        for (int i=0; i<other.mSysMemUsageTableSize; i++) {
+            int ent = other.mSysMemUsageTable[i];
+            int state = (ent>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+            long[] longs = other.mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            addSysMemUsage(state, longs, ((ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK));
+        }
+
         if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
             mTimePeriodStartClock = other.mTimePeriodStartClock;
             mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
         }
         mTimePeriodEndRealtime += other.mTimePeriodEndRealtime - other.mTimePeriodStartRealtime;
+        mTimePeriodEndUptime += other.mTimePeriodEndUptime - other.mTimePeriodStartUptime;
+    }
+
+    public void addSysMemUsage(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        if (mMemFactor != STATE_NOTHING) {
+            int state = mMemFactor * STATE_COUNT;
+            mSysMemUsageArgs[SYS_MEM_USAGE_SAMPLE_COUNT] = 1;
+            for (int i=0; i<3; i++) {
+                mSysMemUsageArgs[SYS_MEM_USAGE_CACHED_MINIMUM + i] = cachedMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_FREE_MINIMUM + i] = freeMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_ZRAM_MINIMUM + i] = zramMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_KERNEL_MINIMUM + i] = kernelMem;
+                mSysMemUsageArgs[SYS_MEM_USAGE_NATIVE_MINIMUM + i] = nativeMem;
+            }
+            addSysMemUsage(state, mSysMemUsageArgs, 0);
+        }
+    }
+
+    void addSysMemUsage(int state, long[] data, int dataOff) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        int off;
+        if (idx >= 0) {
+            off = mSysMemUsageTable[idx];
+        } else {
+            mAddLongTable = mSysMemUsageTable;
+            mAddLongTableSize = mSysMemUsageTableSize;
+            off = addLongData(~idx, state, SYS_MEM_USAGE_COUNT);
+            mSysMemUsageTable = mAddLongTable;
+            mSysMemUsageTableSize = mAddLongTableSize;
+        }
+        long[] longs = mLongs.get((off>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+        idx = (off>>OFFSET_INDEX_SHIFT)&OFFSET_INDEX_MASK;
+        addSysMemUsage(longs, idx, data, dataOff);
+    }
+
+    static void addSysMemUsage(long[] dstData, int dstOff, long[] addData, int addOff) {
+        final long dstCount = dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        final long addCount = addData[addOff+SYS_MEM_USAGE_SAMPLE_COUNT];
+        if (dstCount == 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i++) {
+                dstData[dstOff+i] = addData[addOff+i];
+            }
+        } else if (addCount > 0) {
+            dstData[dstOff+SYS_MEM_USAGE_SAMPLE_COUNT] = dstCount + addCount;
+            for (int i=SYS_MEM_USAGE_CACHED_MINIMUM; i<SYS_MEM_USAGE_COUNT; i+=3) {
+                if (dstData[dstOff+i] > addData[addOff+i]) {
+                    dstData[dstOff+i] = addData[addOff+i];
+                }
+                dstData[dstOff+i+1] = (long)(
+                        ((dstData[dstOff+i+1]*(double)dstCount)
+                                + (addData[addOff+i+1]*(double)addCount))
+                                / (dstCount+addCount) );
+                if (dstData[dstOff+i+2] < addData[addOff+i+2]) {
+                    dstData[dstOff+i+2] = addData[addOff+i+2];
+                }
+            }
+        }
     }
 
     public static final Parcelable.Creator<ProcessStats> CREATOR
@@ -564,6 +657,164 @@ public final class ProcessStats implements Parcelable {
         return totalTime;
     }
 
+    static class PssAggr {
+        long pss = 0;
+        long samples = 0;
+
+        void add(long newPss, long newSamples) {
+            pss = (long)( (pss*(double)samples) + (newPss*(double)newSamples) )
+                    / (samples+newSamples);
+            samples += newSamples;
+        }
+    }
+
+    public void computeTotalMemoryUse(TotalMemoryUseCollection data, long now) {
+        data.totalTime = 0;
+        for (int i=0; i<STATE_COUNT; i++) {
+            data.processStateWeight[i] = 0;
+            data.processStatePss[i] = 0;
+            data.processStateTime[i] = 0;
+            data.processStateSamples[i] = 0;
+        }
+        for (int i=0; i<SYS_MEM_USAGE_COUNT; i++) {
+            data.sysMemUsage[i] = 0;
+        }
+        data.sysMemCachedWeight = 0;
+        data.sysMemFreeWeight = 0;
+        data.sysMemZRamWeight = 0;
+        data.sysMemKernelWeight = 0;
+        data.sysMemNativeWeight = 0;
+        data.sysMemSamples = 0;
+        long[] totalMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            int ent = mSysMemUsageTable[i];
+            long[] longs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+            int idx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+            addSysMemUsage(totalMemUsage, 0, longs, idx);
+        }
+        for (int is=0; is<data.screenStates.length; is++) {
+            for (int im=0; im<data.memStates.length; im++) {
+                int memBucket = data.screenStates[is] + data.memStates[im];
+                int stateBucket = memBucket * STATE_COUNT;
+                long memTime = mMemFactorDurations[memBucket];
+                if (mMemFactor == memBucket) {
+                    memTime += now - mStartTime;
+                }
+                data.totalTime += memTime;
+                int sysIdx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, stateBucket);
+                long[] longs = totalMemUsage;
+                int idx = 0;
+                if (sysIdx >= 0) {
+                    int ent = mSysMemUsageTable[sysIdx];
+                    long[] tmpLongs = mLongs.get((ent>>OFFSET_ARRAY_SHIFT)&OFFSET_ARRAY_MASK);
+                    int tmpIdx = (ent >> OFFSET_INDEX_SHIFT) & OFFSET_INDEX_MASK;
+                    if (tmpLongs[tmpIdx+SYS_MEM_USAGE_SAMPLE_COUNT] >= 3) {
+                        addSysMemUsage(data.sysMemUsage, 0, longs, idx);
+                        longs = tmpLongs;
+                        idx = tmpIdx;
+                    }
+                }
+                data.sysMemCachedWeight += longs[idx+SYS_MEM_USAGE_CACHED_AVERAGE]
+                        * (double)memTime;
+                data.sysMemFreeWeight += longs[idx+SYS_MEM_USAGE_FREE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemZRamWeight += longs[idx+SYS_MEM_USAGE_ZRAM_AVERAGE]
+                        * (double)memTime;
+                data.sysMemKernelWeight += longs[idx+SYS_MEM_USAGE_KERNEL_AVERAGE]
+                        * (double)memTime;
+                data.sysMemNativeWeight += longs[idx+SYS_MEM_USAGE_NATIVE_AVERAGE]
+                        * (double)memTime;
+                data.sysMemSamples += longs[idx+SYS_MEM_USAGE_SAMPLE_COUNT];
+             }
+        }
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int iproc=0; iproc<procMap.size(); iproc++) {
+            SparseArray<ProcessState> uids = procMap.valueAt(iproc);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final ProcessState proc = uids.valueAt(iu);
+                final PssAggr fgPss = new PssAggr();
+                final PssAggr bgPss = new PssAggr();
+                final PssAggr cachedPss = new PssAggr();
+                boolean havePss = false;
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    int off = proc.mDurationsTable[i];
+                    int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    int procState = type % STATE_COUNT;
+                    long samples = proc.getPssSampleCount(type);
+                    if (samples > 0) {
+                        long avg = proc.getPssAverage(type);
+                        havePss = true;
+                        if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                            fgPss.add(avg, samples);
+                        } else if (procState <= STATE_RECEIVER) {
+                            bgPss.add(avg, samples);
+                        } else {
+                            cachedPss.add(avg, samples);
+                        }
+                    }
+                }
+                if (!havePss) {
+                    continue;
+                }
+                boolean fgHasBg = false;
+                boolean fgHasCached = false;
+                boolean bgHasCached = false;
+                if (fgPss.samples < 3 && bgPss.samples > 0) {
+                    fgHasBg = true;
+                    fgPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (fgPss.samples < 3 && cachedPss.samples > 0) {
+                    fgHasCached = true;
+                    fgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && cachedPss.samples > 0) {
+                    bgHasCached = true;
+                    bgPss.add(cachedPss.pss, cachedPss.samples);
+                }
+                if (bgPss.samples < 3 && !fgHasBg && fgPss.samples > 0) {
+                    bgPss.add(fgPss.pss, fgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !bgHasCached && bgPss.samples > 0) {
+                    cachedPss.add(bgPss.pss, bgPss.samples);
+                }
+                if (cachedPss.samples < 3 && !fgHasCached && fgPss.samples > 0) {
+                    cachedPss.add(fgPss.pss, fgPss.samples);
+                }
+                for (int i=0; i<proc.mDurationsTableSize; i++) {
+                    final int off = proc.mDurationsTable[i];
+                    final int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                    long time = getLong(off, 0);
+                    if (proc.mCurState == type) {
+                        time += now - proc.mStartTime;
+                    }
+                    final int procState = type % STATE_COUNT;
+                    data.processStateTime[procState] += time;
+                    long samples = proc.getPssSampleCount(type);
+                    long avg;
+                    if (samples > 0) {
+                        avg = proc.getPssAverage(type);
+                    } else if (procState <= STATE_IMPORTANT_FOREGROUND) {
+                        samples = fgPss.samples;
+                        avg = fgPss.pss;
+                    } else if (procState <= STATE_RECEIVER) {
+                        samples = bgPss.samples;
+                        avg = bgPss.pss;
+                    } else {
+                        samples = cachedPss.samples;
+                        avg = cachedPss.pss;
+                    }
+                    double newAvg = ( (data.processStatePss[procState]
+                            * (double)data.processStateSamples[procState])
+                                + (avg*(double)samples)
+                            ) / (data.processStateSamples[procState]+samples);
+                    data.processStatePss[procState] = (long)newAvg;
+                    data.processStateSamples[procState] += samples;
+                    data.processStateWeight[procState] += avg * (double)time;
+                }
+            }
+        }
+    }
+
     static void dumpProcessState(PrintWriter pw, String prefix, ProcessState proc,
             int[] screenStates, int[] memStates, int[] procStates, long now) {
         long totalTime = 0;
@@ -679,6 +930,62 @@ public final class ProcessStats implements Parcelable {
         }
     }
 
+    long getSysMemUsageValue(int state, int index) {
+        int idx = binarySearch(mSysMemUsageTable, mSysMemUsageTableSize, state);
+        return idx >= 0 ? getLong(mSysMemUsageTable[idx], index) : 0;
+    }
+
+    void dumpSysMemUsageCategory(PrintWriter pw, String prefix, String label,
+            int bucket, int index) {
+        pw.print(prefix); pw.print(label);
+        pw.print(": ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+        pw.print(" min, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+        pw.print(" avg, ");
+        printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+        pw.println(" max");
+    }
+
+    void dumpSysMemUsage(PrintWriter pw, String prefix, int[] screenStates,
+            int[] memStates) {
+        int printedScreen = -1;
+        for (int is=0; is<screenStates.length; is++) {
+            int printedMem = -1;
+            for (int im=0; im<memStates.length; im++) {
+                final int iscreen = screenStates[is];
+                final int imem = memStates[im];
+                final int bucket = ((iscreen + imem) * STATE_COUNT);
+                long count = getSysMemUsageValue(bucket, SYS_MEM_USAGE_SAMPLE_COUNT);
+                if (count > 0) {
+                    pw.print(prefix);
+                    if (screenStates.length > 1) {
+                        printScreenLabel(pw, printedScreen != iscreen
+                                ? iscreen : STATE_NOTHING);
+                        printedScreen = iscreen;
+                    }
+                    if (memStates.length > 1) {
+                        printMemLabel(pw, printedMem != imem ? imem : STATE_NOTHING, '\0');
+                        printedMem = imem;
+                    }
+                    pw.print(": ");
+                    pw.print(count);
+                    pw.println(" samples:");
+                    dumpSysMemUsageCategory(pw, prefix, "  Cached", bucket,
+                            SYS_MEM_USAGE_CACHED_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Free", bucket,
+                            SYS_MEM_USAGE_FREE_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  ZRam", bucket,
+                            SYS_MEM_USAGE_ZRAM_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Kernel", bucket,
+                            SYS_MEM_USAGE_KERNEL_MINIMUM);
+                    dumpSysMemUsageCategory(pw, prefix, "  Native", bucket,
+                            SYS_MEM_USAGE_NATIVE_MINIMUM);
+                }
+            }
+        }
+    }
+
     static void dumpStateHeadersCsv(PrintWriter pw, String sep, int[] screenStates,
             int[] memStates, int[] procStates) {
         final int NS = screenStates != null ? screenStates.length : 1;
@@ -1088,10 +1395,13 @@ public final class ProcessStats implements Parcelable {
         mTimePeriodStartClock = System.currentTimeMillis();
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+        mTimePeriodStartUptime = mTimePeriodEndUptime = SystemClock.uptimeMillis();
         mLongs.clear();
         mLongs.add(new long[LONGS_SIZE]);
         mNextLong = 0;
         Arrays.fill(mMemFactorDurations, 0);
+        mSysMemUsageTable = null;
+        mSysMemUsageTableSize = 0;
         mStartTime = 0;
         mReadError = null;
         mFlags = 0;
@@ -1220,12 +1530,17 @@ public final class ProcessStats implements Parcelable {
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        long now = SystemClock.uptimeMillis();
+        writeToParcel(out, SystemClock.uptimeMillis(), flags);
+    }
+
+    /** @hide */
+    public void writeToParcel(Parcel out, long now, int flags) {
         out.writeInt(MAGIC);
         out.writeInt(PARCEL_VERSION);
         out.writeInt(STATE_COUNT);
         out.writeInt(ADJ_COUNT);
         out.writeInt(PSS_COUNT);
+        out.writeInt(SYS_MEM_USAGE_COUNT);
         out.writeInt(LONGS_SIZE);
 
         mCommonStringToIndex = new ArrayMap<String, Integer>(mProcesses.mMap.size());
@@ -1268,8 +1583,9 @@ public final class ProcessStats implements Parcelable {
         out.writeLong(mTimePeriodStartClock);
         out.writeLong(mTimePeriodStartRealtime);
         out.writeLong(mTimePeriodEndRealtime);
+        out.writeLong(mTimePeriodStartUptime);
+        out.writeLong(mTimePeriodEndUptime);
         out.writeString(mRuntime);
-        out.writeString(mWebView);
         out.writeInt(mFlags);
 
         out.writeInt(mLongs.size());
@@ -1287,6 +1603,13 @@ public final class ProcessStats implements Parcelable {
         }
         writeCompactedLongArray(out, mMemFactorDurations, mMemFactorDurations.length);
 
+        out.writeInt(mSysMemUsageTableSize);
+        for (int i=0; i<mSysMemUsageTableSize; i++) {
+            if (DEBUG_PARCEL) Slog.i(TAG, "Writing sys mem usage #" + i + ": "
+                    + printLongOffset(mSysMemUsageTable[i]));
+            out.writeInt(mSysMemUsageTable[i]);
+        }
+
         out.writeInt(NPROC);
         for (int ip=0; ip<NPROC; ip++) {
             writeCommonString(out, procMap.keyAt(ip));
@@ -1417,6 +1740,9 @@ public final class ProcessStats implements Parcelable {
         if (!readCheckedInt(in, PSS_COUNT, "pss count")) {
             return;
         }
+        if (!readCheckedInt(in, SYS_MEM_USAGE_COUNT, "sys mem usage count")) {
+            return;
+        }
         if (!readCheckedInt(in, LONGS_SIZE, "longs size")) {
             return;
         }
@@ -1427,8 +1753,9 @@ public final class ProcessStats implements Parcelable {
         buildTimePeriodStartClockStr();
         mTimePeriodStartRealtime = in.readLong();
         mTimePeriodEndRealtime = in.readLong();
+        mTimePeriodStartUptime = in.readLong();
+        mTimePeriodEndUptime = in.readLong();
         mRuntime = in.readString();
-        mWebView = in.readString();
         mFlags = in.readInt();
 
         final int NLONGS = in.readInt();
@@ -1447,6 +1774,12 @@ public final class ProcessStats implements Parcelable {
 
         readCompactedLongArray(in, version, mMemFactorDurations, mMemFactorDurations.length);
 
+        mSysMemUsageTable = readTableFromParcel(in, TAG, "sys mem usage");
+        if (mSysMemUsageTable == BAD_TABLE) {
+            return;
+        }
+        mSysMemUsageTableSize = mSysMemUsageTable != null ? mSysMemUsageTable.length : 0;
+
         int NPROC = in.readInt();
         if (NPROC < 0) {
             mReadError = "bad process count: " + NPROC;
@@ -1826,6 +2159,10 @@ public final class ProcessStats implements Parcelable {
             boolean dumpAll, boolean activeOnly) {
         long totalTime = dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.println("System memory usage:");
+            dumpSysMemUsage(pw, "  ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
+        }
         ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
         boolean printedHeader = false;
         boolean sepNeeded = false;
@@ -2089,10 +2426,57 @@ public final class ProcessStats implements Parcelable {
         dumpTotalsLocked(pw, now);
     }
 
+    long printMemoryCategory(PrintWriter pw, String prefix, String label, double memWeight,
+            long totalTime, long curTotalMem, int samples) {
+        if (memWeight != 0) {
+            long mem = (long)(memWeight * 1024 / totalTime);
+            pw.print(prefix);
+            pw.print(label);
+            pw.print(": ");
+            printSizeValue(pw, mem);
+            pw.print(" (");
+            pw.print(samples);
+            pw.print(" samples)");
+            pw.println();
+            return curTotalMem + mem;
+        }
+        return curTotalMem;
+    }
+
     void dumpTotalsLocked(PrintWriter pw, long now) {
         pw.println("Run time Stats:");
         dumpSingleTime(pw, "  ", mMemFactorDurations, mMemFactor, mStartTime, now);
         pw.println();
+        pw.println("Memory usage:");
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        long totalPss = 0;
+        totalPss = printMemoryCategory(pw, "  ", "Kernel ", totalMem.sysMemKernelWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Native ", totalMem.sysMemNativeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            // Skip restarting service state -- that is not actually a running process.
+            if (i != STATE_SERVICE_RESTARTING) {
+                totalPss = printMemoryCategory(pw, "  ", STATE_NAMES[i],
+                        totalMem.processStateWeight[i], totalMem.totalTime, totalPss,
+                        totalMem.processStateSamples[i]);
+            }
+        }
+        totalPss = printMemoryCategory(pw, "  ", "Cached ", totalMem.sysMemCachedWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Free   ", totalMem.sysMemFreeWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        totalPss = printMemoryCategory(pw, "  ", "Z-Ram   ", totalMem.sysMemZRamWeight,
+                totalMem.totalTime, totalPss, totalMem.sysMemSamples);
+        pw.print("  TOTAL  : ");
+        printSizeValue(pw, totalPss);
+        pw.println();
+        printMemoryCategory(pw, "  ", STATE_NAMES[STATE_SERVICE_RESTARTING],
+                totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
+                totalMem.processStateSamples[STATE_SERVICE_RESTARTING]);
+        pw.println();
         pw.print("          Start time: ");
         pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
         pw.println();
@@ -2118,8 +2502,6 @@ public final class ProcessStats implements Parcelable {
         }
         pw.print(' ');
         pw.print(mRuntime);
-        pw.print(' ');
-        pw.print(mWebView);
         pw.println();
     }
 
@@ -2208,7 +2590,7 @@ public final class ProcessStats implements Parcelable {
     public void dumpCheckinLocked(PrintWriter pw, String reqPackage) {
         final long now = SystemClock.uptimeMillis();
         final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
-        pw.println("vers,4");
+        pw.println("vers,5");
         pw.print("period,"); pw.print(mTimePeriodStartClockStr);
         pw.print(","); pw.print(mTimePeriodStartRealtime); pw.print(",");
         pw.print(mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
@@ -2229,7 +2611,7 @@ public final class ProcessStats implements Parcelable {
             pw.print(",partial");
         }
         pw.println();
-        pw.print("config,"); pw.print(mRuntime); pw.print(','); pw.println(mWebView);
+        pw.print("config,"); pw.println(mRuntime);
         for (int ip=0; ip<pkgMap.size(); ip++) {
             final String pkgName = pkgMap.keyAt(ip);
             if (reqPackage != null && !reqPackage.equals(pkgName)) {
@@ -2362,6 +2744,53 @@ public final class ProcessStats implements Parcelable {
         pw.print("total");
         dumpAdjTimesCheckin(pw, ",", mMemFactorDurations, mMemFactor,
                 mStartTime, now);
+        if (mSysMemUsageTable != null) {
+            pw.print("sysmemusage");
+            for (int i=0; i<mSysMemUsageTableSize; i++) {
+                int off = mSysMemUsageTable[i];
+                int type = (off>>OFFSET_TYPE_SHIFT)&OFFSET_TYPE_MASK;
+                pw.print(",");
+                printProcStateTag(pw, type);
+                for (int j=SYS_MEM_USAGE_SAMPLE_COUNT; j<SYS_MEM_USAGE_COUNT; j++) {
+                    if (j > SYS_MEM_USAGE_CACHED_MINIMUM) {
+                        pw.print(":");
+                    }
+                    pw.print(getLong(off, j));
+                }
+            }
+        }
+        pw.println();
+        TotalMemoryUseCollection totalMem = new TotalMemoryUseCollection(ALL_SCREEN_ADJ,
+                ALL_MEM_ADJ);
+        computeTotalMemoryUse(totalMem, now);
+        pw.print("weights,");
+        pw.print(totalMem.totalTime);
+        pw.print(",");
+        pw.print(totalMem.sysMemCachedWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemFreeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemZRamWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemKernelWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        pw.print(",");
+        pw.print(totalMem.sysMemNativeWeight);
+        pw.print(":");
+        pw.print(totalMem.sysMemSamples);
+        for (int i=0; i<STATE_COUNT; i++) {
+            pw.print(",");
+            pw.print(totalMem.processStateWeight[i]);
+            pw.print(":");
+            pw.print(totalMem.processStateSamples[i]);
+        }
         pw.println();
     }
 
@@ -2452,6 +2881,15 @@ public final class ProcessStats implements Parcelable {
         }
     }
 
+    final public static class ProcessStateHolder {
+        public final int appVersion;
+        public ProcessStats.ProcessState state;
+
+        public ProcessStateHolder(int _appVersion) {
+            appVersion = _appVersion;
+        }
+    }
+
     public static final class ProcessState extends DurationsTable {
         public ProcessState mCommonProcess;
         public final String mPackage;
@@ -2660,7 +3098,7 @@ public final class ProcessStats implements Parcelable {
          * @param pkgList Processes to update.
          */
         public void setState(int state, int memFactor, long now,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             if (state < 0) {
                 state = mNumStartedServices > 0
                         ? (STATE_SERVICE_RESTARTING+(memFactor*STATE_COUNT)) : STATE_NOTHING;
@@ -2770,7 +3208,7 @@ public final class ProcessStats implements Parcelable {
         }
 
         public void addPss(long pss, long uss, boolean always,
-                ArrayMap<String, ProcessState> pkgList) {
+                ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             if (!always) {
                 if (mLastPssState == mCurState && SystemClock.uptimeMillis()
@@ -2845,7 +3283,7 @@ public final class ProcessStats implements Parcelable {
             }
         }
 
-        public void reportExcessiveWake(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveWake(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveWake++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2857,7 +3295,7 @@ public final class ProcessStats implements Parcelable {
             }
         }
 
-        public void reportExcessiveCpu(ArrayMap<String, ProcessState> pkgList) {
+        public void reportExcessiveCpu(ArrayMap<String, ProcessStateHolder> pkgList) {
             ensureNotDead();
             mCommonProcess.mNumExcessiveCpu++;
             if (!mCommonProcess.mMultiPackage) {
@@ -2888,7 +3326,7 @@ public final class ProcessStats implements Parcelable {
             }
         }
 
-        public void reportCachedKill(ArrayMap<String, ProcessState> pkgList, long pss) {
+        public void reportCachedKill(ArrayMap<String, ProcessStateHolder> pkgList, long pss) {
             ensureNotDead();
             mCommonProcess.addCachedKill(1, pss, pss, pss);
             if (!mCommonProcess.mMultiPackage) {
@@ -2925,8 +3363,10 @@ public final class ProcessStats implements Parcelable {
             return this;
         }
 
-        private ProcessState pullFixedProc(ArrayMap<String, ProcessState> pkgList, int index) {
-            ProcessState proc = pkgList.valueAt(index);
+        private ProcessState pullFixedProc(ArrayMap<String, ProcessStateHolder> pkgList,
+                int index) {
+            ProcessStateHolder holder = pkgList.valueAt(index);
+            ProcessState proc = holder.state;
             if (mDead && proc.mCommonProcess != proc) {
                 // Somehow we are contining to use a process state that is dead, because
                 // it was not being told it was active during the last commit.  We can recover
@@ -2959,7 +3399,7 @@ public final class ProcessStats implements Parcelable {
                     throw new IllegalStateException("Didn't create per-package process "
                             + proc.mName + " in pkg " + pkg.mPackageName + "/" + pkg.mUid);
                 }
-                pkgList.setValueAt(index, proc);
+                holder.state = proc;
             }
             return proc;
         }
@@ -3351,4 +3791,27 @@ public final class ProcessStats implements Parcelable {
             }
         }
     }
+
+    public static class TotalMemoryUseCollection {
+        final int[] screenStates;
+        final int[] memStates;
+
+        public TotalMemoryUseCollection(int[] _screenStates, int[] _memStates) {
+            screenStates = _screenStates;
+            memStates = _memStates;
+        }
+
+        public long totalTime;
+        public long[] processStatePss = new long[STATE_COUNT];
+        public double[] processStateWeight = new double[STATE_COUNT];
+        public long[] processStateTime = new long[STATE_COUNT];
+        public int[] processStateSamples = new int[STATE_COUNT];
+        public long[] sysMemUsage = new long[SYS_MEM_USAGE_COUNT];
+        public double sysMemCachedWeight;
+        public double sysMemFreeWeight;
+        public double sysMemZRamWeight;
+        public double sysMemKernelWeight;
+        public double sysMemNativeWeight;
+        public int sysMemSamples;
+    }
 }
index ff0ee65..b098de8 100644 (file)
@@ -93,6 +93,9 @@ public class LocalTransport extends BackupTransport {
 
     private File mFullRestoreSetDir;
     private HashSet<String> mFullRestorePackages;
+    private FileInputStream mCurFullRestoreStream;
+    private FileOutputStream mFullRestoreSocketStream;
+    private byte[] mFullRestoreBuffer;
 
     public LocalTransport(Context context) {
         mContext = context;
@@ -104,34 +107,41 @@ public class LocalTransport extends BackupTransport {
         }
     }
 
+    @Override
     public String name() {
         return new ComponentName(mContext, this.getClass()).flattenToShortString();
     }
 
+    @Override
     public Intent configurationIntent() {
         // The local transport is not user-configurable
         return null;
     }
 
+    @Override
     public String currentDestinationString() {
         return TRANSPORT_DESTINATION_STRING;
     }
 
+    @Override
     public String transportDirName() {
         return TRANSPORT_DIR_NAME;
     }
 
+    @Override
     public long requestBackupTime() {
         // any time is a good time for local backup
         return 0;
     }
 
+    @Override
     public int initializeDevice() {
         if (DEBUG) Log.v(TAG, "wiping all data");
         deleteContents(mCurrentSetDir);
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
         if (DEBUG) {
             try {
@@ -191,7 +201,7 @@ public class LocalTransport extends BackupTransport {
                         entity.write(buf, 0, dataSize);
                     } catch (IOException e) {
                         Log.e(TAG, "Unable to update key file " + entityFile.getAbsolutePath());
-                        return BackupTransport.TRANSPORT_ERROR;
+                        return TRANSPORT_ERROR;
                     } finally {
                         entity.close();
                     }
@@ -199,11 +209,11 @@ public class LocalTransport extends BackupTransport {
                     entityFile.delete();
                 }
             }
-            return BackupTransport.TRANSPORT_OK;
+            return TRANSPORT_OK;
         } catch (IOException e) {
             // oops, something went wrong.  abort the operation and return error.
             Log.v(TAG, "Exception reading backup input:", e);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
     }
 
@@ -222,6 +232,7 @@ public class LocalTransport extends BackupTransport {
         }
     }
 
+    @Override
     public int clearBackupData(PackageInfo packageInfo) {
         if (DEBUG) Log.v(TAG, "clearBackupData() pkg=" + packageInfo.packageName);
 
@@ -243,9 +254,10 @@ public class LocalTransport extends BackupTransport {
             packageDir.delete();
         }
 
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int finishBackup() {
         if (DEBUG) Log.v(TAG, "finishBackup()");
         if (mSocket != null) {
@@ -259,24 +271,27 @@ public class LocalTransport extends BackupTransport {
                 mFullTargetPackage = null;
                 mSocket.close();
             } catch (IOException e) {
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             } finally {
                 mSocket = null;
             }
         }
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     // ------------------------------------------------------------------------------------
     // Full backup handling
+
+    @Override
     public long requestFullBackupTime() {
         return 0;
     }
 
+    @Override
     public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
         if (mSocket != null) {
             Log.e(TAG, "Attempt to initiate full backup while one is in progress");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         if (DEBUG) {
@@ -291,7 +306,7 @@ public class LocalTransport extends BackupTransport {
             mSocketInputStream = new FileInputStream(mSocket.getFileDescriptor());
         } catch (IOException e) {
             Log.e(TAG, "Unable to process socket for full backup");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         mFullTargetPackage = targetPackage.packageName;
@@ -300,18 +315,19 @@ public class LocalTransport extends BackupTransport {
             File tarball = new File(mCurrentSetFullDir, mFullTargetPackage);
             tarstream = new FileOutputStream(tarball);
         } catch (FileNotFoundException e) {
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
         mFullBackupOutputStream = new BufferedOutputStream(tarstream);
         mFullBackupBuffer = new byte[4096];
 
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
+    @Override
     public int sendBackupData(int numBytes) {
         if (mFullBackupBuffer == null) {
             Log.w(TAG, "Attempted sendBackupData before performFullBackup");
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         if (numBytes > mFullBackupBuffer.length) {
@@ -323,21 +339,23 @@ public class LocalTransport extends BackupTransport {
             if (nRead < 0) {
                 // Something went wrong if we expect data but saw EOD
                 Log.w(TAG, "Unexpected EOD; failing backup");
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             }
             mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
             numBytes -= nRead;
             } catch (IOException e) {
                 Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
-                return BackupTransport.TRANSPORT_ERROR;
+                return TRANSPORT_ERROR;
             }
         }
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     // ------------------------------------------------------------------------------------
     // Restore handling
     static final long[] POSSIBLE_SETS = { 2, 3, 4, 5, 6, 7, 8, 9 }; 
+
+    @Override
     public RestoreSet[] getAvailableRestoreSets() {
         long[] existing = new long[POSSIBLE_SETS.length + 1];
         int num = 0;
@@ -358,11 +376,13 @@ public class LocalTransport extends BackupTransport {
         return available;
     }
 
+    @Override
     public long getCurrentRestoreSet() {
         // The current restore set always has the same token
         return CURRENT_SET_TOKEN;
     }
 
+    @Override
     public int startRestore(long token, PackageInfo[] packages) {
         if (DEBUG) Log.v(TAG, "start restore " + token);
         mRestorePackages = packages;
@@ -371,7 +391,7 @@ public class LocalTransport extends BackupTransport {
         mRestoreSetDir = new File(mDataDir, Long.toString(token));
         mRestoreSetIncrementalDir = new File(mRestoreSetDir, INCREMENTAL_DIR);
         mRestoreSetFullDir = new File(mRestoreSetDir, FULL_DATA_DIR);
-        return BackupTransport.TRANSPORT_OK;
+        return TRANSPORT_OK;
     }
 
     @Override
@@ -397,6 +417,7 @@ public class LocalTransport extends BackupTransport {
                 if (maybeFullData.length() > 0) {
                     if (DEBUG) Log.v(TAG, "  nextRestorePackage(TYPE_FULL_STREAM) = " + name);
                     mRestoreType = RestoreDescription.TYPE_FULL_STREAM;
+                    mCurFullRestoreStream = null;   // ensure starting from the ground state
                     found = true;
                 }
             }
@@ -410,6 +431,7 @@ public class LocalTransport extends BackupTransport {
         return RestoreDescription.NO_MORE_PACKAGES;
     }
 
+    @Override
     public int getRestoreData(ParcelFileDescriptor outFd) {
         if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
         if (mRestorePackage < 0) throw new IllegalStateException("nextRestorePackage not called");
@@ -426,7 +448,7 @@ public class LocalTransport extends BackupTransport {
         ArrayList<DecodedFilename> blobs = contentsByKey(packageDir);
         if (blobs == null) {  // nextRestorePackage() ensures the dir exists, so this is an error
             Log.e(TAG, "No keys for package: " + packageDir);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
 
         // We expect at least some data if the directory exists in the first place
@@ -447,10 +469,10 @@ public class LocalTransport extends BackupTransport {
                     in.close();
                 }
             }
-            return BackupTransport.TRANSPORT_OK;
+            return TRANSPORT_OK;
         } catch (IOException e) {
             Log.e(TAG, "Unable to read backup records", e);
-            return BackupTransport.TRANSPORT_ERROR;
+            return TRANSPORT_ERROR;
         }
     }
 
@@ -487,38 +509,27 @@ public class LocalTransport extends BackupTransport {
         return contents;
     }
 
+    @Override
     public void finishRestore() {
         if (DEBUG) Log.v(TAG, "finishRestore()");
+        if (mRestoreType == RestoreDescription.TYPE_FULL_STREAM) {
+            resetFullRestoreState();
+        }
+        mRestoreType = 0;
     }
 
     // ------------------------------------------------------------------------------------
     // Full restore handling
 
-    public int prepareFullRestore(long token, String[] targetPackages) {
-        mRestoreSetDir = new File(mDataDir, Long.toString(token));
-        mFullRestoreSetDir = new File(mRestoreSetDir, FULL_DATA_DIR);
-        mFullRestorePackages = new HashSet<String>();
-        if (mFullRestoreSetDir.exists()) {
-            List<String> pkgs = Arrays.asList(mFullRestoreSetDir.list());
-            HashSet<String> available = new HashSet<String>(pkgs);
-
-            for (int i = 0; i < targetPackages.length; i++) {
-                if (available.contains(targetPackages[i])) {
-                    mFullRestorePackages.add(targetPackages[i]);
-                }
-            }
+    private void resetFullRestoreState() {
+        try {
+        mCurFullRestoreStream.close();
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to close full restore input stream");
         }
-        return BackupTransport.TRANSPORT_OK;
-    }
-
-    /**
-     * Ask the transport what package's full data will be restored next.  When all apps'
-     * data has been delivered, the transport should return {@code null} here.
-     * @return The package name of the next application whose data will be restored, or
-     *    {@code null} if all available package has been delivered.
-     */
-    public String getNextFullRestorePackage() {
-        return null;
+        mCurFullRestoreStream = null;
+        mFullRestoreSocketStream = null;
+        mFullRestoreBuffer = null;
     }
 
     /**
@@ -543,7 +554,79 @@ public class LocalTransport extends BackupTransport {
      *    indicating a fatal error condition that precludes further restore operations
      *    on the current dataset.
      */
+    @Override
     public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
-        return 0;
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("Asked for full restore data for non-stream package");
+        }
+
+        // first chunk?
+        if (mCurFullRestoreStream == null) {
+            final String name = mRestorePackages[mRestorePackage].packageName;
+            if (DEBUG) Log.i(TAG, "Starting full restore of " + name);
+            File dataset = new File(mRestoreSetFullDir, name);
+            try {
+                mCurFullRestoreStream = new FileInputStream(dataset);
+            } catch (IOException e) {
+                // If we can't open the target package's tarball, we return the single-package
+                // error code and let the caller go on to the next package.
+                Log.e(TAG, "Unable to read archive for " + name);
+                return TRANSPORT_PACKAGE_REJECTED;
+            }
+            mFullRestoreSocketStream = new FileOutputStream(socket.getFileDescriptor());
+            mFullRestoreBuffer = new byte[32*1024];
+        }
+
+        int nRead;
+        try {
+            nRead = mCurFullRestoreStream.read(mFullRestoreBuffer);
+            if (nRead < 0) {
+                // EOF: tell the caller we're done
+                nRead = NO_MORE_DATA;
+            } else if (nRead == 0) {
+                // This shouldn't happen when reading a FileInputStream; we should always
+                // get either a positive nonzero byte count or -1.  Log the situation and
+                // treat it as EOF.
+                Log.w(TAG, "read() of archive file returned 0; treating as EOF");
+                nRead = NO_MORE_DATA;
+            } else {
+                if (DEBUG) {
+                    Log.i(TAG, "   delivering restore chunk: " + nRead);
+                }
+                mFullRestoreSocketStream.write(mFullRestoreBuffer, 0, nRead);
+            }
+        } catch (IOException e) {
+            return TRANSPORT_ERROR;  // Hard error accessing the file; shouldn't happen
+        } finally {
+            // Most transports will need to explicitly close 'socket' here, but this transport
+            // is in the same process as the caller so it can leave it up to the backup manager
+            // to manage both socket fds.
+        }
+
+        return nRead;
     }
+
+    /**
+     * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
+     * data for restore, it will invoke this method to tell the transport that it should
+     * abandon the data download for the current package.  The OS will then either call
+     * {@link #nextRestorePackage()} again to move on to restoring the next package in the
+     * set being iterated over, or will call {@link #finishRestore()} to shut down the restore
+     * operation.
+     *
+     * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the
+     *    current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious
+     *    transport-level failure.  If the transport reports an error here, the entire restore
+     *    operation will immediately be finished with no further attempts to restore app data.
+     */
+    @Override
+    public int abortFullRestore() {
+        if (mRestoreType != RestoreDescription.TYPE_FULL_STREAM) {
+            throw new IllegalStateException("abortFullRestore() but not currently restoring");
+        }
+        resetFullRestoreState();
+        mRestoreType = 0;
+        return TRANSPORT_OK;
+    }
+
 }
index 240d520..1ccaa0f 100644 (file)
@@ -291,6 +291,7 @@ public final class BatteryStatsImpl extends BatteryStats {
     final StopwatchTimer[] mBluetoothStateTimer = new StopwatchTimer[NUM_BLUETOOTH_STATES];
 
     int mMobileRadioPowerState = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
+    long mMobileRadioActiveStartTime;
     StopwatchTimer mMobileRadioActiveTimer;
     StopwatchTimer mMobileRadioActivePerAppTimer;
     LongSamplingCounter mMobileRadioActiveAdjustedTime;
@@ -1425,10 +1426,6 @@ public final class BatteryStatsImpl extends BatteryStats {
             return 0;
         }
 
-        long getLastUpdateTimeMs() {
-            return mUpdateTime;
-        }
-
         void stopRunningLocked(long elapsedRealtimeMs) {
             // Ignore attempt to stop a timer that isn't running
             if (mNesting == 0) {
@@ -2790,11 +2787,11 @@ public final class BatteryStatsImpl extends BatteryStats {
                     powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_MEDIUM
                             || powerState == DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH;
             if (active) {
-                realElapsedRealtimeMs = elapsedRealtime;
+                mMobileRadioActiveStartTime = realElapsedRealtimeMs = elapsedRealtime;
                 mHistoryCur.states |= HistoryItem.STATE_MOBILE_RADIO_ACTIVE_FLAG;
             } else {
                 realElapsedRealtimeMs = timestampNs / (1000*1000);
-                long lastUpdateTimeMs = mMobileRadioActiveTimer.getLastUpdateTimeMs();
+                long lastUpdateTimeMs = mMobileRadioActiveStartTime;
                 if (realElapsedRealtimeMs < lastUpdateTimeMs) {
                     Slog.wtf(TAG, "Data connection inactive timestamp " + realElapsedRealtimeMs
                             + " is before start time " + lastUpdateTimeMs);
index 3a53331..4c9feca 100644 (file)
@@ -466,7 +466,7 @@ static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz,
 
 // connect to camera service
 static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
-    jobject weak_this, jint cameraId, jstring clientPackageName)
+    jobject weak_this, jint cameraId, jint halVersion, jstring clientPackageName)
 {
     // Convert jstring to String16
     const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
@@ -474,8 +474,18 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
     String16 clientName(rawClientName, rawClientNameLen);
     env->ReleaseStringChars(clientPackageName, rawClientName);
 
-    sp<Camera> camera = Camera::connect(cameraId, clientName,
-            Camera::USE_CALLING_UID);
+    sp<Camera> camera;
+    if (halVersion == ICameraService::CAMERA_HAL_API_VERSION_UNSPECIFIED) {
+        // Default path: hal version is unspecified, do normal camera open.
+        camera = Camera::connect(cameraId, clientName,
+                Camera::USE_CALLING_UID);
+    } else {
+        jint status = Camera::connectLegacy(cameraId, halVersion, clientName,
+                Camera::USE_CALLING_UID, camera);
+        if (status != NO_ERROR) {
+            return status;
+        }
+    }
 
     if (camera == NULL) {
         return -EACCES;
@@ -510,7 +520,6 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
 // finalizer is invoked later.
 static void android_hardware_Camera_release(JNIEnv *env, jobject thiz)
 {
-    // TODO: Change to ALOGV
     ALOGV("release camera");
     JNICameraContext* context = NULL;
     sp<Camera> camera;
@@ -891,7 +900,7 @@ static JNINativeMethod camMethods[] = {
     "(ILandroid/hardware/Camera$CameraInfo;)V",
     (void*)android_hardware_Camera_getCameraInfo },
   { "native_setup",
-    "(Ljava/lang/Object;ILjava/lang/String;)I",
+    "(Ljava/lang/Object;IILjava/lang/String;)I",
     (void*)android_hardware_Camera_native_setup },
   { "native_release",
     "()V",
index 8b5dff0..fa1a563 100644 (file)
                 android:finishOnCloseSystemDialogs="true"
                 android:excludeFromRecents="true"
                 android:multiprocess="true"
-                android:documentLaunchMode="never">
+                android:documentLaunchMode="never"
+                android:relinquishTaskIdentity="true">
             <intent-filter>
                 <action android:name="android.intent.action.CHOOSER" />
                 <category android:name="android.intent.category.DEFAULT" />
index 4e06d9a..fc1d0df 100644 (file)
          Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS} -->
     <attr name="autoRemoveFromRecents" format="boolean" />
 
+    <!-- Tasks whose root has this attribute set to true will replace baseIntent with that of the
+         next activity in the task. If the next activity also has this attribute set to true then
+         it will yield the baseIntent to any activity that it launches in the same task. This
+         continues until an activity is encountered which has this attribute set to false. False
+         is the default. This attribute set to true also permits activity's use of the
+         TaskDescription to change labels, colors and icons in the recent task list. -->
+    <attr name="relinquishTaskIdentity" format="boolean" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
         <attr name="documentLaunchMode" />
         <attr name="maxRecents" />
         <attr name="autoRemoveFromRecents" />
+        <attr name="relinquishTaskIdentity" />
     </declare-styleable>
     
     <!-- The <code>activity-alias</code> tag declares a new
index f73c958..07a1e51 100644 (file)
   <public type="attr" name="fullBackupOnly" />
   <public type="attr" name="propertyXName" />
   <public type="attr" name="propertyYName" />
+  <public type="attr" name="relinquishTaskIdentity" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
index 7dba21d..b6bb578 100644 (file)
@@ -205,6 +205,37 @@ public class CameraBinderTest extends AndroidTestCase {
         }
     }
 
+    @SmallTest
+    public void testConnectLegacy() throws Exception {
+        final int CAMERA_HAL_API_VERSION_1_0 = 0x100;
+        for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
+            ICamera cameraUser = null;
+            ICameraClient dummyCallbacks = new DummyCameraClient();
+
+            String clientPackageName = getContext().getPackageName();
+
+            BinderHolder holder = new BinderHolder();
+
+            try {
+                CameraBinderDecorator.newInstance(mUtils.getCameraService())
+                        .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
+                        clientPackageName,
+                        CameraBinderTestUtils.USE_CALLING_UID, holder);
+                cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+                assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
+
+                Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
+            } catch (RuntimeException e) {
+                // Not all camera device support openLegacy.
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (cameraUser != null) {
+                    cameraUser.disconnect();
+                }
+            }
+        }
+    }
+
     static class DummyCameraDeviceCallbacks extends ICameraDeviceCallbacks.Stub {
 
         /*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraOpenTest.java
new file mode 100644 (file)
index 0000000..14bbe44
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * 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.mediaframeworktest.unit;
+
+import android.hardware.Camera;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+/**
+ * <pre>
+ * adb shell am instrument \
+ *      -e class 'com.android.mediaframeworktest.unit.CameraOpenTest' \
+ *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
+ * </pre>
+ */
+public class CameraOpenTest extends junit.framework.TestCase {
+    private static String TAG = "CameraOpenTest";
+
+    private Camera mCamera;
+
+    /**
+     * Test @hide android.hardware.Camera#openLegacy API that cannot be tested in CTS.
+     */
+    @SmallTest
+    public void testOpenLegacy() {
+        int nCameras = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCameras; id++) {
+            try {
+                mCamera.openLegacy(id, Camera.CAMERA_HAL_API_VERSION_1_0);
+            } catch (RuntimeException e) {
+                Log.i(TAG, "Unable to open camera as HAL1 legacy camera device " + e);
+            } finally {
+                if (mCamera != null) {
+                    mCamera.release();
+                }
+            }
+        }
+    }
+}
index a7e8514..4a4c1c1 100644 (file)
@@ -22,7 +22,13 @@ Copyright (C) 2014 The Android Open Source Project
         android:viewportWidth="24.0"
         android:viewportHeight="24.0"/>
 
+    <group
+        android:scaleX="1.2"
+        android:scaleY="1.2"
+        android:pivotX="12.0"
+        android:pivotY="12.0">
     <path
         android:fill="#FFFFFFFF"
         android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM12.0,5.0c1.7,0.0 3.0,1.3 3.0,3.0c0.0,1.7 -1.3,3.0 -3.0,3.0c-1.7,0.0 -3.0,-1.3 -3.0,-3.0C9.0,6.3 10.3,5.0 12.0,5.0zM12.0,19.2c-2.5,0.0 -4.7,-1.3 -6.0,-3.2c0.0,-2.0 4.0,-3.1 6.0,-3.1c2.0,0.0 6.0,1.1 6.0,3.1C16.7,17.9 14.5,19.2 12.0,19.2z"/>
+    </group>
 </vector>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher.xml b/packages/SystemUI/res/layout/keyguard_user_switcher.xml
new file mode 100644 (file)
index 0000000..5648065
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/keyguard_user_switcher"
+        android:orientation="vertical"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:gravity="end"
+        android:visibility="gone"
+        android:paddingTop="4dp">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml b/packages/SystemUI/res/layout/keyguard_user_switcher_item.xml
new file mode 100644 (file)
index 0000000..691a80e
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2014 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:sysui="http://schemas.android.com/apk/res-auto"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        android:layout_marginEnd="8dp"
+        android:gravity="center_vertical"
+        android:clickable="true"
+        android:background="@drawable/ripple_drawable">
+    <TextView android:id="@+id/name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="16dp"
+            android:textAppearance="@style/TextAppearance.StatusBar.Expanded.UserSwitcher.UserName"
+            />
+    <com.android.systemui.statusbar.phone.UserAvatarView android:id="@+id/picture"
+            android:layout_width="48dp"
+            android:layout_height="48dp"
+            android:contentDescription="@null"
+            sysui:frameWidth="@dimen/keyguard_user_switcher_border_thickness"
+            sysui:activeFrameColor="@color/current_user_border_color" />
+</LinearLayout>
\ No newline at end of file
index cde83bf..b54ba1a 100644 (file)
@@ -24,6 +24,7 @@
     android:id="@+id/notification_panel"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:background="@android:color/transparent"
     >
 
     <include
 
     <include layout="@layout/status_bar_expanded_header" />
 
+    <ViewStub
+        android:id="@+id/keyguard_user_switcher"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        android:layout_marginTop="@dimen/status_bar_header_height_keyguard"
+        android:layout_gravity="end"
+        android:layout="@layout/keyguard_user_switcher" />
+
     <include
         layout="@layout/keyguard_bottom_area"
         android:visibility="gone" />
index 353368b..2e4c0ef 100644 (file)
@@ -73,9 +73,9 @@
         android:layout_width="wrap_content"
         android:layout_height="@dimen/status_bar_header_height"
         android:layout_toStartOf="@id/multi_user_switch"
-        android:layout_marginEnd="2dp"
+        android:layout_alignWithParentIfMissing="true"
         android:layout_marginStart="16dp"
-        />
+        android:paddingEnd="2dp" />
 
     <TextView
         android:id="@+id/header_charging_info"
index 47581a9..9f4c364 100644 (file)
@@ -32,4 +32,7 @@
     <!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
          card. -->
     <integer name="keyguard_max_notification_count">5</integer>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">true</bool>
 </resources>
index c453618..8473d96 100644 (file)
         <enum name="horizontal" value="0" />
         <enum name="vertical" value="1" />
     </attr>
+    <declare-styleable name="UserAvatarView">
+        <attr name="frameWidth" format="dimension" />
+        <attr name="activeFrameColor" format="color" />
+        <attr name="frameColor" />
+    </declare-styleable>
 </resources>
 
index 3bd8689..4e38da6 100644 (file)
@@ -60,6 +60,9 @@
 
     <color name="keyguard_affordance">#ffffffff</color>
 
+    <!-- The color of the circle around the primary user in the user switcher -->
+    <color name="current_user_border_color">@color/primary_color</color>
+
     <!-- Our material color palette (deep teal) -->
     <color name="primary_color">#ff7fcac3</color>
     <color name="background_color_1">#ff384248</color>
index c64a182..48b327d 100644 (file)
          Notification.tickerText across the status bar for what seems like an
          eternity. -->
     <bool name="enable_ticker">false</bool>
+
+    <!-- Set to true to enable the user switcher on the keyguard. -->
+    <bool name="config_keyguardUserSwitcher">false</bool>
 </resources>
 
index bbcc9c1..fe63211 100644 (file)
 
     <!-- end margin for multi user switch in expanded quick settings -->
     <dimen name="multi_user_switch_expanded_margin">8dp</dimen>
+
+    <!-- end margin for system icons if multi user switch is hidden -->
+    <dimen name="system_icons_switcher_hidden_expanded_margin">16dp</dimen>
+
+    <!-- The thickness of the colored border around the current user. -->
+    <dimen name="keyguard_user_switcher_border_thickness">2dp</dimen>
+
 </resources>
index c117eba..e5d5b03 100644 (file)
     <style name="TextAppearance.StatusBar.Expanded.Network.EmergencyOnly">
     </style>
 
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher">
+        <item name="android:textSize">16sp</item>
+        <item name="android:textStyle">normal</item>
+        <item name="android:textColor">#ffffff</item>
+    </style>
+    <style name="TextAppearance.StatusBar.Expanded.UserSwitcher.UserName" />
+
     <style name="TextAppearance" />
     <style name="TextAppearance.QuickSettings" />
 
index 1da7dab..154d554 100644 (file)
@@ -123,6 +123,7 @@ import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
 import com.android.systemui.statusbar.policy.CastControllerImpl;
 import com.android.systemui.statusbar.policy.DateView;
 import com.android.systemui.statusbar.policy.HeadsUpNotificationView;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
 import com.android.systemui.statusbar.policy.UserInfoController;
 import com.android.systemui.statusbar.policy.LocationControllerImpl;
 import com.android.systemui.statusbar.policy.NetworkControllerImpl;
@@ -211,6 +212,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
     ZenModeController mZenModeController;
     CastControllerImpl mCastController;
     VolumeComponent mVolumeComponent;
+    KeyguardUserSwitcher mKeyguardUserSwitcher;
 
     int mNaturalBarHeight = -1;
     int mIconSize = -1;
@@ -713,6 +715,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
         final SignalClusterView signalCluster =
                 (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster);
 
+        mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
+                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mHeader);
 
         mNetworkController.addSignalCluster(signalCluster);
         signalCluster.setNetworkController(mNetworkController);
@@ -2913,9 +2917,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
             mKeyguardStatusView.setVisibility(View.VISIBLE);
             mKeyguardIndicationController.setVisible(true);
             mNotificationPanel.resetViews();
+            mKeyguardUserSwitcher.setKeyguard(true);
         } else {
             mKeyguardStatusView.setVisibility(View.GONE);
             mKeyguardIndicationController.setVisible(false);
+            mKeyguardUserSwitcher.setKeyguard(false);
         }
         if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
index 1712124..fc10a08 100644 (file)
@@ -63,6 +63,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
 
     private boolean mShowEmergencyCallsOnly;
     private boolean mShowChargingInfo;
+    private boolean mKeyguardUserSwitcherShowing;
 
     private int mCollapsedHeight;
     private int mExpandedHeight;
@@ -72,6 +73,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
     private int mNormalWidth;
     private int mPadding;
     private int mMultiUserExpandedMargin;
+    private int mSystemIconsSwitcherHiddenExpandedMargin;
 
     private ActivityStarter mActivityStarter;
     private BrightnessController mBrightnessController;
@@ -125,7 +127,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
         mPadding = getResources().getDimensionPixelSize(R.dimen.notification_side_padding);
         mMultiUserExpandedMargin =
                 getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin);
-
+        mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize(
+                R.dimen.system_icons_switcher_hidden_expanded_margin);
     }
 
     public void setActivityStarter(ActivityStarter activityStarter) {
@@ -216,12 +219,15 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
                 ? VISIBLE : GONE);
         mChargingInfo.setVisibility(mExpanded && !mOverscrolled && mShowChargingInfo
                 && !mShowEmergencyCallsOnly ? VISIBLE : GONE);
+        mMultiUserSwitch.setVisibility(mExpanded || !mKeyguardUserSwitcherShowing
+                ? VISIBLE : GONE);
     }
 
     private void updateSystemIconsLayoutParams() {
         RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsContainer.getLayoutParams();
         boolean systemIconsAboveClock = mExpanded && !mOverscrolled
                 && mShowChargingInfo && !mShowEmergencyCallsOnly;
+        lp.setMarginEnd(0);
         if (systemIconsAboveClock) {
             lp.addRule(ALIGN_PARENT_START);
             lp.removeRule(START_OF);
@@ -230,7 +236,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
                     ? mSettingsButton.getId()
                     : mMultiUserSwitch.getId());
             lp.removeRule(ALIGN_PARENT_START);
+            if (mMultiUserSwitch.getVisibility() == GONE) {
+                lp.setMarginEnd(mSystemIconsSwitcherHiddenExpandedMargin);
+            }
         }
+        mSystemIconsContainer.setLayoutParams(lp);
 
         RelativeLayout.LayoutParams clockLp = (LayoutParams) mDateTime.getLayoutParams();
         if (systemIconsAboveClock) {
@@ -238,6 +248,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
         } else {
             clockLp.addRule(BELOW, mEmergencyCallsOnly.getId());
         }
+        mDateTime.setLayoutParams(clockLp);
     }
 
     private void updateBrightnessControllerState() {
@@ -385,4 +396,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
     public void setChargingInfo(CharSequence chargingInfo) {
         mChargingInfo.setText(chargingInfo);
     }
+
+    public void setKeyguardUserSwitcherShowing(boolean showing) {
+        // STOPSHIP: NOT CALLED PROPERLY WHEN GOING TO FULL SHADE AND RETURNING!?!
+        mKeyguardUserSwitcherShowing = showing;
+        updateVisibilities();
+        updateSystemIconsLayoutParams();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UserAvatarView.java
new file mode 100644 (file)
index 0000000..6c9dc85
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Shader;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * A view that displays a user image cropped to a circle with a frame.
+ */
+public class UserAvatarView extends View {
+
+    private int mActiveFrameColor;
+    private int mFrameColor;
+    private float mFrameWidth;
+    private Bitmap mBitmap;
+    private Drawable mDrawable;
+
+    private final Paint mFramePaint = new Paint();
+    private final Paint mBitmapPaint = new Paint();
+    private final Matrix mDrawMatrix = new Matrix();
+
+    private float mScale = 1;
+
+    public UserAvatarView(Context context, AttributeSet attrs,
+            int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        final TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.UserAvatarView, defStyleAttr, defStyleRes);
+        final int N = a.getIndexCount();
+        for (int i = 0; i < N; i++) {
+            int attr = a.getIndex(i);
+            switch (attr) {
+                case R.styleable.UserAvatarView_frameWidth:
+                    setFrameWidth(a.getDimension(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_activeFrameColor:
+                    setActiveFrameColor(a.getColor(attr, 0));
+                    break;
+                case R.styleable.UserAvatarView_frameColor:
+                    setFrameColor(a.getColor(attr, 0));
+                    break;
+            }
+        }
+        a.recycle();
+
+        mFramePaint.setAntiAlias(true);
+        mFramePaint.setStyle(Paint.Style.STROKE);
+        mBitmapPaint.setAntiAlias(true);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public UserAvatarView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public UserAvatarView(Context context) {
+        this(context, null);
+    }
+
+    public void setBitmap(Bitmap bitmap) {
+        setDrawable(null);
+        mBitmap = bitmap;
+        mBitmapPaint.setShader(new BitmapShader(
+                bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP));
+        configureBounds();
+        invalidate();
+    }
+
+    public void setFrameColor(int frameColor) {
+        mFrameColor = frameColor;
+        invalidate();
+    }
+
+    public void setActiveFrameColor(int activeFrameColor) {
+        mActiveFrameColor = activeFrameColor;
+        invalidate();
+    }
+
+    public void setFrameWidth(float frameWidth) {
+        mFrameWidth = frameWidth;
+        invalidate();
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        configureBounds();
+    }
+
+    public void configureBounds() {
+        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
+        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
+
+        int dwidth;
+        int dheight;
+        if (mBitmap != null) {
+            dwidth = mBitmap.getWidth();
+            dheight = mBitmap.getHeight();
+        } else if (mDrawable != null) {
+            dwidth = mDrawable.getIntrinsicWidth();
+            dheight = mDrawable.getIntrinsicHeight();
+            mDrawable.setBounds(0, 0, dwidth, dheight);
+            vwidth -= 2 * (mFrameWidth - 1);
+            vheight -= 2 * (mFrameWidth - 1);
+        } else {
+            return;
+        }
+
+        float scale;
+        float dx;
+        float dy;
+
+        scale = Math.min((float) vwidth / (float) dwidth,
+                (float) vheight / (float) dheight);
+
+        dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
+        dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
+
+        mDrawMatrix.setScale(scale, scale);
+        mDrawMatrix.postTranslate(dx, dy);
+        mScale = scale;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        int frameColor = isActivated() ? mActiveFrameColor : mFrameColor;
+        float halfW = getWidth() / 2f;
+        float halfH = getHeight() / 2f;
+        float halfSW = Math.min(halfH, halfW);
+        if (mBitmap != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.concat(mDrawMatrix);
+            float halfBW = mBitmap.getWidth() / 2f;
+            float halfBH = mBitmap.getHeight() / 2f;
+            float halfBSW = Math.min(halfBH, halfBW);
+            canvas.drawCircle(halfBW, halfBH, halfBSW - mFrameWidth / mScale + 1, mBitmapPaint);
+            canvas.restoreToCount(saveCount);
+        } else if (mDrawable != null && mScale > 0) {
+            int saveCount = canvas.getSaveCount();
+            canvas.save();
+            canvas.translate(mPaddingLeft, mPaddingTop);
+            canvas.translate(mFrameWidth - 1, mFrameWidth - 1);
+            canvas.concat(mDrawMatrix);
+            mDrawable.draw(canvas);
+            canvas.restoreToCount(saveCount);
+        }
+        if (frameColor != 0) {
+            mFramePaint.setColor(frameColor);
+            mFramePaint.setStrokeWidth(mFrameWidth);
+            canvas.drawCircle(halfW, halfH, halfSW - mFrameWidth / 2f, mFramePaint);
+        }
+    }
+
+    public void setDrawable(Drawable d) {
+        if (mDrawable != null) {
+            mDrawable.setCallback(null);
+            unscheduleDrawable(mDrawable);
+        }
+        mDrawable = d;
+        if (d != null) {
+            d.setCallback(this);
+            if (d.isStateful()) {
+                d.setState(getDrawableState());
+            }
+            d.setLayoutDirection(getLayoutDirection());
+            configureBounds();
+        }
+        if (d != null) {
+            mBitmap = null;
+        }
+        configureBounds();
+        invalidate();
+    }
+
+    @Override
+    public void invalidateDrawable(Drawable dr) {
+        if (dr == mDrawable) {
+            invalidate();
+        } else {
+            super.invalidateDrawable(dr);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
new file mode 100644 (file)
index 0000000..c90750c
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarHeaderView;
+import com.android.systemui.statusbar.phone.UserAvatarView;
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.WindowManagerGlobal;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the user switcher on the Keyguard.
+ */
+public class KeyguardUserSwitcher implements View.OnClickListener {
+
+    private static final String TAG = "KeyguardUserSwitcher";
+
+    private final Context mContext;
+    private final ViewGroup mUserSwitcher;
+    private final UserManager mUserManager;
+    private final StatusBarHeaderView mHeader;
+
+    public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
+            StatusBarHeaderView header) {
+        mContext = context;
+        if (context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher)) {
+            mUserSwitcher = (ViewGroup) userSwitcher.inflate();
+            mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+            mHeader = header;
+            refresh();
+        } else {
+            mUserSwitcher = null;
+            mUserManager = null;
+            mHeader = null;
+        }
+    }
+
+    public void setKeyguard(boolean keyguard) {
+        if (mUserSwitcher != null) {
+            // TODO: Cache showUserSwitcherOnKeyguard().
+            if (keyguard && showUserSwitcherOnKeyguard()) {
+                show();
+                refresh();
+            } else {
+                hide();
+            }
+        }
+    }
+
+    /**
+     * @return true if the user switcher should be shown on the lock screen.
+     * @see android.os.UserManager#isUserSwitcherEnabled()
+     */
+    private boolean showUserSwitcherOnKeyguard() {
+        // TODO: Set isEdu. The edu provisioning process can add settings to Settings.Global.
+        boolean isEdu = false;
+        if (isEdu) {
+            return true;
+        }
+        List<UserInfo> users = mUserManager.getUsers(true /* excludeDying */);
+        int N = users.size();
+        int switchableUsers = 0;
+        for (int i = 0; i < N; i++) {
+            if (users.get(i).supportsSwitchTo()) {
+                switchableUsers++;
+            }
+        }
+        return switchableUsers > 1;
+    }
+
+    public void show() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.VISIBLE);
+            mHeader.setKeyguardUserSwitcherShowing(true);
+        }
+    }
+
+    private void hide() {
+        if (mUserSwitcher != null) {
+            // TODO: animate
+            mUserSwitcher.setVisibility(View.GONE);
+            mHeader.setKeyguardUserSwitcherShowing(false);
+        }
+    }
+
+    private void refresh() {
+        if (mUserSwitcher != null) {
+            new AsyncTask<Void, Void, ArrayList<UserData>>() {
+                @Override
+                protected ArrayList<UserData> doInBackground(Void... params) {
+                    return loadUsers();
+                }
+
+                @Override
+                protected void onPostExecute(ArrayList<UserData> userInfos) {
+                    bind(userInfos);
+                }
+            }.execute((Void[]) null);
+        }
+    }
+
+    private void bind(ArrayList<UserData> userList) {
+        mUserSwitcher.removeAllViews();
+        int N = userList.size();
+        for (int i = 0; i < N; i++) {
+            mUserSwitcher.addView(inflateUser(userList.get(i)));
+        }
+        // TODO: add Guest
+        // TODO: add (+) button
+    }
+
+    private View inflateUser(UserData user) {
+        View v = LayoutInflater.from(mUserSwitcher.getContext()).inflate(
+                R.layout.keyguard_user_switcher_item, mUserSwitcher, false);
+        TextView name = (TextView) v.findViewById(R.id.name);
+        UserAvatarView picture = (UserAvatarView) v.findViewById(R.id.picture);
+        name.setText(user.userInfo.name);
+        picture.setActivated(user.isCurrent);
+        if (user.userInfo.isGuest()) {
+            picture.setDrawable(mContext.getResources().getDrawable(R.drawable.ic_account_circle));
+        } else {
+            picture.setBitmap(user.userIcon);
+        }
+        v.setOnClickListener(this);
+        v.setTag(user.userInfo);
+        // TODO: mark which user is current for accessibility.
+        return v;
+    }
+
+    @Override
+    public void onClick(View v) {
+        switchUser(((UserInfo)v.getTag()).id);
+    }
+
+    // TODO: Factor out logic below and share with QS implementation.
+
+    private ArrayList<UserData> loadUsers() {
+        ArrayList<UserInfo> users = (ArrayList<UserInfo>) mUserManager
+                .getUsers(true /* excludeDying */);
+        int N = users.size();
+        ArrayList<UserData> result = new ArrayList<>(N);
+        int currentUser = -1;
+        try {
+            currentUser = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couln't get current user.", e);
+        }
+        for (int i = 0; i < N; i++) {
+            UserInfo user = users.get(i);
+            if (user.supportsSwitchTo()) {
+                boolean isCurrent = user.id == currentUser;
+                result.add(new UserData(user, mUserManager.getUserIcon(user.id), isCurrent));
+            }
+        }
+        return result;
+    }
+
+    private void switchUser(int userId) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().lockNow(null);
+            ActivityManagerNative.getDefault().switchUser(userId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Couldn't switch user.", e);
+        }
+    }
+
+    private static class UserData {
+        final UserInfo userInfo;
+        final Bitmap userIcon;
+        final boolean isCurrent;
+
+        UserData(UserInfo userInfo, Bitmap userIcon, boolean isCurrent) {
+            this.userInfo = userInfo;
+            this.userIcon = userIcon;
+            this.isCurrent = isCurrent;
+        }
+    }
+}
index 816e022..69262c8 100755 (executable)
@@ -1328,7 +1328,7 @@ public final class ActiveServices {
                         + " app=" + app);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(r.appInfo.packageName, mAm.mProcessStats);
+                    app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                     realStartServiceLocked(r, app, execInFg);
                     return null;
                 } catch (RemoteException e) {
@@ -1883,7 +1883,8 @@ public final class ActiveServices {
 
                     mPendingServices.remove(i);
                     i--;
-                    proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats);
+                    proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
+                            mAm.mProcessStats);
                     realStartServiceLocked(sr, proc, sr.createdFromFg);
                     didSomething = true;
                 }
index 34c1ecd..904baba 100644 (file)
@@ -570,6 +570,12 @@ public final class ActivityManagerService extends ActivityManagerNative
     long mLastFullPssTime = SystemClock.uptimeMillis();
 
     /**
+     * If set, the next time we collect PSS data we should do a full collection
+     * with data from native processes and the kernel.
+     */
+    boolean mFullPssPending = false;
+
+    /**
      * This is the process holding what we currently consider to be
      * the "home" activity.
      */
@@ -1801,8 +1807,49 @@ public final class ActivityManagerService extends ActivityManagerNative
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case COLLECT_PSS_BG_MSG: {
-                int i=0, num=0;
                 long start = SystemClock.uptimeMillis();
+                MemInfoReader memInfo = null;
+                synchronized (ActivityManagerService.this) {
+                    if (mFullPssPending) {
+                        mFullPssPending = false;
+                        memInfo = new MemInfoReader();
+                    }
+                }
+                if (memInfo != null) {
+                    updateCpuStatsNow();
+                    long nativeTotalPss = 0;
+                    synchronized (mProcessCpuThread) {
+                        final int N = mProcessCpuTracker.countStats();
+                        for (int j=0; j<N; j++) {
+                            ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(j);
+                            if (st.vsize <= 0 || st.uid >= Process.FIRST_APPLICATION_UID
+                                    || st.uid == Process.SYSTEM_UID) {
+                                // This is definitely an application process; skip it.
+                                continue;
+                            }
+                            synchronized (mPidsSelfLocked) {
+                                if (mPidsSelfLocked.indexOfKey(st.pid) >= 0) {
+                                    // This is one of our own processes; skip it.
+                                    continue;
+                                }
+                            }
+                            nativeTotalPss += Debug.getPss(st.pid, null);
+                        }
+                    }
+                    memInfo.readMemInfo();
+                    synchronized (this) {
+                        if (DEBUG_PSS) Slog.d(TAG, "Collected native and kernel memory in "
+                                + (SystemClock.uptimeMillis()-start) + "ms");
+                        mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                                memInfo.getFreeSizeKb(),
+                                memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+                                memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()
+                                        +memInfo.getSlabSizeKb(),
+                                nativeTotalPss);
+                    }
+                }
+
+                int i=0, num=0;
                 long[] tmp = new long[1];
                 do {
                     ProcessRecord proc;
@@ -2769,7 +2816,7 @@ public final class ActivityManagerService extends ActivityManagerNative
                 // 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);
+                app.addPackage(info.packageName, info.versionCode, mProcessStats);
                 return app;
             }
 
@@ -2824,7 +2871,7 @@ public final class ActivityManagerService extends ActivityManagerNative
             }
         } else {
             // If this is a new package in the process, add the package to the list
-            app.addPackage(info.packageName, mProcessStats);
+            app.addPackage(info.packageName, info.versionCode, mProcessStats);
         }
 
         // If the system is not ready yet, then hold off on starting this
@@ -7780,7 +7827,8 @@ public final class ActivityManagerService extends ActivityManagerNative
                     // 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);
+                    app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.versionCode,
+                            mProcessStats);
                 }
                 ensurePackageDexOpt(cpi.applicationInfo.packageName);
             }
@@ -12588,6 +12636,8 @@ public final class ActivityManagerService extends ActivityManagerNative
             }
         }
 
+        long nativeProcTotalPss = 0;
+
         if (!isCheckinRequest && procs.size() > 1) {
             // If we are showing aggregations, also look for native processes to
             // include so that our aggregations are more accurate.
@@ -12609,6 +12659,7 @@ public final class ActivityManagerService extends ActivityManagerNative
 
                         final long myTotalPss = mi.getTotalPss();
                         totalPss += myTotalPss;
+                        nativeProcTotalPss += myTotalPss;
 
                         MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                                 st.name, myTotalPss, st.pid, false);
@@ -12676,6 +12727,15 @@ public final class ActivityManagerService extends ActivityManagerNative
             }
             MemInfoReader memInfo = new MemInfoReader();
             memInfo.readMemInfo();
+            if (nativeProcTotalPss > 0) {
+                synchronized (this) {
+                    mProcessStats.addSysMemUsageLocked(memInfo.getCachedSizeKb(),
+                            memInfo.getFreeSizeKb(),
+                            memInfo.getSwapTotalSizeKb()-memInfo.getSwapFreeSizeKb(),
+                            memInfo.getBuffersSizeKb()+memInfo.getShmemSizeKb()+memInfo.getSlabSizeKb(),
+                            nativeProcTotalPss);
+                }
+            }
             if (!brief) {
                 if (!isCompact) {
                     pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb());
@@ -15456,6 +15516,7 @@ public final class ActivityManagerService extends ActivityManagerNative
         }
         if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs!  memLowered=" + memLowered);
         mLastFullPssTime = now;
+        mFullPssPending = true;
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
         for (int i=mLruProcesses.size()-1; i>=0; i--) {
index dd9cae9..0825f2e 100755 (executable)
@@ -582,11 +582,6 @@ final class ActivityRecord {
         }
     }
 
-    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);
@@ -1035,11 +1030,11 @@ final class ActivityRecord {
             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;
+        final int activityNdx = task.mActivities.indexOf(r);
+        if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
+            return -1;
         }
+        return task.taskId;
     }
 
     static ActivityRecord isInStackLocked(IBinder token) {
index 03ce530..fe2a473 100755 (executable)
@@ -1066,40 +1066,6 @@ final class ActivityStack {
         }
     }
 
-    /**
-     * 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);
-    }
-
     private void setVisibile(ActivityRecord r, boolean visible) {
         r.visible = visible;
         mWindowManager.setAppVisibility(r.appToken, visible);
@@ -1954,8 +1920,7 @@ final class ActivityStack {
                 // 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) {
+                if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                     resetTaskIfNeededLocked(r, r);
                     doShow = topRunningNonDelayedActivityLocked(null) == r;
                 }
@@ -2048,7 +2013,8 @@ final class ActivityStack {
         // 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 ) {
+        final int rootActivityNdx = task.findEffectiveRootIndex();
+        for (int i = numActivities - 1; i > rootActivityNdx; --i ) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
@@ -2207,8 +2173,10 @@ final class ActivityStack {
 
         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) {
+        final int rootActivityNdx = affinityTask.findEffectiveRootIndex();
+
+        // Do not operate on or below the effective root Activity.
+        for (int i = numActivities - 1; i > rootActivityNdx; --i) {
             ActivityRecord target = activities.get(i);
 
             final int flags = target.info.flags;
index 9264186..9763523 100644 (file)
@@ -1178,7 +1178,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
                     // 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);
+                    app.addPackage(r.info.packageName, r.info.applicationInfo.versionCode,
+                            mService.mProcessStats);
                 }
                 realStartActivityLocked(r, app, andResume, checkConfig);
                 return;
index 7b2b04d..cdcc74b 100644 (file)
@@ -887,7 +887,8 @@ public final class BroadcastQueue {
                     info.activityInfo.applicationInfo.uid, false);
             if (app != null && app.thread != null) {
                 try {
-                    app.addPackage(info.activityInfo.packageName, mService.mProcessStats);
+                    app.addPackage(info.activityInfo.packageName,
+                            info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                     processCurBroadcastLocked(r, app);
                     return;
                 } catch (RemoteException e) {
index 8d7d300..a5b80bc 100644 (file)
@@ -53,8 +53,8 @@ final class ProcessRecord {
     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>();
+    final ArrayMap<String, ProcessStats.ProcessStateHolder> pkgList
+            = new ArrayMap<String, ProcessStats.ProcessStateHolder>();
     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)
@@ -371,7 +371,7 @@ final class ProcessRecord {
         uid = _uid;
         userId = UserHandle.getUserId(_uid);
         processName = _processName;
-        pkgList.put(_info.packageName, null);
+        pkgList.put(_info.packageName, new ProcessStats.ProcessStateHolder(_info.versionCode));
         maxAdj = ProcessList.UNKNOWN_ADJ;
         curRawAdj = setRawAdj = -100;
         curAdj = setAdj = -100;
@@ -398,16 +398,15 @@ final class ProcessRecord {
                     info.versionCode, processName);
             baseProcessTracker.makeActive();
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
+                holder.state = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid,
                         info.versionCode, processName);
-                if (ps != baseProcessTracker) {
-                    ps.makeActive();
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
-                pkgList.setValueAt(i, ps);
             }
         }
         thread = _thread;
@@ -424,11 +423,11 @@ final class ProcessRecord {
             }
             baseProcessTracker = null;
             for (int i=0; i<pkgList.size(); i++) {
-                ProcessStats.ProcessState ps = pkgList.valueAt(i);
-                if (ps != null && ps != origBase) {
-                    ps.makeInactive();
+                ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                if (holder.state != null && holder.state != origBase) {
+                    holder.state.makeInactive();
                 }
-                pkgList.setValueAt(i, null);
+                holder.state = null;
             }
         }
     }
@@ -572,14 +571,16 @@ final class ProcessRecord {
     /*
      *  Return true if package has been added false if not
      */
-    public boolean addPackage(String pkg, ProcessStatsService tracker) {
+    public boolean addPackage(String pkg, int versionCode, ProcessStatsService tracker) {
         if (!pkgList.containsKey(pkg)) {
             if (baseProcessTracker != null) {
-                ProcessStats.ProcessState state = tracker.getProcessStateLocked(
-                        pkg, info.uid, info.versionCode, processName);
-                pkgList.put(pkg, state);
-                if (state != baseProcessTracker) {
-                    state.makeActive();
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        versionCode);
+                holder.state = tracker.getProcessStateLocked(
+                        pkg, info.uid, versionCode, processName);
+                pkgList.put(pkg, holder);
+                if (holder.state != baseProcessTracker) {
+                    holder.state.makeActive();
                 }
             } else {
                 pkgList.put(pkg, null);
@@ -615,23 +616,26 @@ final class ProcessRecord {
                     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();
+                    ProcessStats.ProcessStateHolder holder = pkgList.valueAt(i);
+                    if (holder.state != null && holder.state != baseProcessTracker) {
+                        holder.state.makeInactive();
                     }
 
                 }
                 pkgList.clear();
                 ProcessStats.ProcessState ps = tracker.getProcessStateLocked(
                         info.packageName, info.uid, info.versionCode, processName);
-                pkgList.put(info.packageName, ps);
+                ProcessStats.ProcessStateHolder holder = new ProcessStats.ProcessStateHolder(
+                        info.versionCode);
+                holder.state = ps;
+                pkgList.put(info.packageName, holder);
                 if (ps != baseProcessTracker) {
                     ps.makeActive();
                 }
             }
         } else if (N != 1) {
             pkgList.clear();
-            pkgList.put(info.packageName, null);
+            pkgList.put(info.packageName, new ProcessStats.ProcessStateHolder(info.versionCode));
         }
     }
     
index 14f3ef9..7ec14c2 100644 (file)
@@ -171,10 +171,17 @@ public final class ProcessStatsService extends IProcessStats.Stub {
         return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0;
     }
 
+    public void addSysMemUsageLocked(long cachedMem, long freeMem, long zramMem, long kernelMem,
+            long nativeMem) {
+        mProcessStats.addSysMemUsage(cachedMem, freeMem, zramMem, kernelMem, nativeMem);
+    }
+
     public boolean shouldWriteNowLocked(long now) {
         if (now > (mLastWriteTime+WRITE_PERIOD)) {
             if (SystemClock.elapsedRealtime()
-                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) {
+                    > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD) &&
+                    SystemClock.uptimeMillis()
+                    > (mProcessStats.mTimePeriodStartUptime+ProcessStats.COMMIT_UPTIME_PERIOD)) {
                 mCommitPending = true;
             }
             return true;
@@ -212,6 +219,7 @@ public final class ProcessStatsService extends IProcessStats.Stub {
             if (mPendingWrite == null || !mPendingWriteCommitted) {
                 mPendingWrite = Parcel.obtain();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
+                mProcessStats.mTimePeriodEndUptime = now;
                 if (commit) {
                     mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE;
                 }
@@ -439,8 +447,10 @@ public final class ProcessStatsService extends IProcessStats.Stub {
         mWriteLock.lock();
         try {
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
             }
             if (historic != null) {
                 ArrayList<String> files = getCommittedFiles(0, false, true);
@@ -470,8 +480,10 @@ public final class ProcessStatsService extends IProcessStats.Stub {
             Parcel current = Parcel.obtain();
             long curTime;
             synchronized (mAm) {
+                long now = SystemClock.uptimeMillis();
                 mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
-                mProcessStats.writeToParcel(current, 0);
+                mProcessStats.mTimePeriodEndUptime = now;
+                mProcessStats.writeToParcel(current, now, 0);
                 curTime = mProcessStats.mTimePeriodEndRealtime
                         - mProcessStats.mTimePeriodStartRealtime;
             }
@@ -568,8 +580,8 @@ public final class ProcessStatsService extends IProcessStats.Stub {
     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("    [--details] [--full-details] [--current] [--hours N] [--last N]");
+        pw.println("    [--active] [--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.");
@@ -581,6 +593,7 @@ public final class ProcessStatsService extends IProcessStats.Stub {
         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("  --last: only show the last committed stats at index N (starting at 1).");
         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.");
@@ -621,6 +634,7 @@ public final class ProcessStatsService extends IProcessStats.Stub {
         boolean dumpFullDetails = false;
         boolean dumpAll = false;
         int aggregateHours = 0;
+        int lastIndex = 0;
         boolean activeOnly = false;
         String reqPackage = null;
         boolean csvSepScreenStats = false;
@@ -705,6 +719,20 @@ public final class ProcessStatsService extends IProcessStats.Stub {
                         dumpHelp(pw);
                         return;
                     }
+                } else if ("--last".equals(arg)) {
+                    i++;
+                    if (i >= args.length) {
+                        pw.println("Error: argument required for --last");
+                        dumpHelp(pw);
+                        return;
+                    }
+                    try {
+                        lastIndex = Integer.parseInt(args[i]);
+                    } catch (NumberFormatException e) {
+                        pw.println("Error: --last argument not an int -- " + args[i]);
+                        dumpHelp(pw);
+                        return;
+                    }
                 } else if ("--active".equals(arg)) {
                     activeOnly = true;
                     currentOnly = true;
@@ -818,6 +846,43 @@ public final class ProcessStatsService extends IProcessStats.Stub {
             dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
                     dumpDetails, dumpFullDetails, dumpAll, activeOnly);
             return;
+        } else if (lastIndex > 0) {
+            pw.print("LAST STATS AT INDEX "); pw.print(lastIndex); pw.println(":");
+            ArrayList<String> files = getCommittedFiles(0, false, true);
+            if (lastIndex >= files.size()) {
+                pw.print("Only have "); pw.print(files.size()); pw.println(" data sets");
+                return;
+            }
+            AtomicFile file = new AtomicFile(new File(files.get(lastIndex)));
+            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(lastIndex));
+                pw.print("; "); pw.println(processStats.mReadError);
+                return;
+            }
+            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 {
+                pw.print("COMMITTED STATS FROM ");
+                pw.print(processStats.mTimePeriodStartClockStr);
+                if (checkedIn) pw.print(" (checked in)");
+                pw.println(":");
+                if (dumpDetails || dumpFullDetails) {
+                    processStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
+                            activeOnly);
+                    if (dumpAll) {
+                        pw.print("  mFile="); pw.println(mFile.getBaseFile());
+                    }
+                } else {
+                    processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
+                }
+            }
+            return;
         }
 
         boolean sepNeeded = false;
@@ -859,7 +924,7 @@ public final class ProcessStatsService extends IProcessStats.Stub {
                                 // Always dump summary here, dumping all details is just too
                                 // much crud.
                                 if (dumpFullDetails) {
-                                    mProcessStats.dumpLocked(pw, reqPackage, now, false, false,
+                                    processStats.dumpLocked(pw, reqPackage, now, false, false,
                                             activeOnly);
                                 } else {
                                     processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly);
index 1cde41f..9fff329 100644 (file)
@@ -27,7 +27,6 @@ import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.Slog;
@@ -57,11 +56,12 @@ final class TaskRecord extends ThumbnailHolder {
     private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
     private static final String ATTR_LASTDESCRIPTION = "last_description";
     private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+    private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
 
     private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
 
     final int taskId;       // Unique identifier for this task.
-    final String affinity;  // The affinity name for this task, or null.
+    String affinity;        // The affinity name for this task, or null.
     final IVoiceInteractionSession voiceSession;    // Voice interaction session driving task
     final IVoiceInteractor voiceInteractor;         // Associated interactor to provide to app
     Intent intent;          // The original intent that started the task.
@@ -111,13 +111,15 @@ final class TaskRecord extends ThumbnailHolder {
      * Display.DEFAULT_DISPLAY. */
     boolean mOnTopOfHome = false;
 
+    /** If original intent did not allow relinquishing task identity, save that information */
+    boolean mNeverRelinquishIdentity = true;
+
     final ActivityManagerService mService;
 
     TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
             IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
         mService = service;
         taskId = _taskId;
-        affinity = info.taskAffinity;
         voiceSession = _voiceSession;
         voiceInteractor = _voiceInteractor;
         setIntent(_intent, info);
@@ -128,7 +130,7 @@ final class TaskRecord extends ThumbnailHolder {
             String _affinity, ComponentName _realActivity, ComponentName _origActivity,
             boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
             int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
-            long lastTimeMoved) {
+            long lastTimeMoved, boolean neverRelinquishIdentity) {
         mService = service;
         taskId = _taskId;
         intent = _intent;
@@ -146,6 +148,7 @@ final class TaskRecord extends ThumbnailHolder {
         lastDescription = _lastDescription;
         mActivities = activities;
         mLastTimeMoved = lastTimeMoved;
+        mNeverRelinquishIdentity = neverRelinquishIdentity;
     }
 
     void touchActiveTime() {
@@ -157,6 +160,14 @@ final class TaskRecord extends ThumbnailHolder {
     }
 
     void setIntent(Intent _intent, ActivityInfo info) {
+        if (intent == null) {
+            mNeverRelinquishIdentity =
+                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
+        } else if (mNeverRelinquishIdentity) {
+            return;
+        }
+
+        affinity = info.taskAffinity;
         stringName = null;
 
         if (info.targetActivity == null) {
@@ -282,6 +293,7 @@ final class TaskRecord extends ThumbnailHolder {
 
         mActivities.remove(newTop);
         mActivities.add(newTop);
+        updateEffectiveIntent();
 
         setFrontOfTask();
     }
@@ -311,6 +323,7 @@ final class TaskRecord extends ThumbnailHolder {
             r.mActivityType = taskType;
         }
         mActivities.add(index, r);
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -322,6 +335,7 @@ final class TaskRecord extends ThumbnailHolder {
             // Was previously in list.
             numFullscreen--;
         }
+        updateEffectiveIntent();
         if (r.isPersistable()) {
             mService.notifyTaskPersisterLocked(this, false);
         }
@@ -579,12 +593,19 @@ final class TaskRecord extends ThumbnailHolder {
         // utility activities.
         int activityNdx;
         final int numActivities = mActivities.size();
+        final boolean relinquish = numActivities == 0 ? false :
+                (mActivities.get(0).info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) != 0;
         for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
                 ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
+            if (relinquish && (r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                // This will be the top activity for determining taskDescription. Pre-inc to
+                // overcome initial decrement below.
+                ++activityNdx;
+                break;
+            }
             if (r.intent != null &&
-                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                            != 0) {
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 break;
             }
         }
@@ -615,6 +636,27 @@ final class TaskRecord extends ThumbnailHolder {
         }
     }
 
+    int findEffectiveRootIndex() {
+        int activityNdx;
+        final int topActivityNdx = mActivities.size() - 1;
+        for (activityNdx = 0; activityNdx < topActivityNdx; ++activityNdx) {
+            final ActivityRecord r = mActivities.get(activityNdx);
+            if (r.finishing) {
+                continue;
+            }
+            if ((r.info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
+                break;
+            }
+        }
+        return activityNdx;
+    }
+
+    void updateEffectiveIntent() {
+        final int effectiveRootIndex = findEffectiveRootIndex();
+        final ActivityRecord r = mActivities.get(effectiveRootIndex);
+        setIntent(r.intent, r.info);
+    }
+
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
         Slog.i(TAG, "Saving task=" + this);
 
@@ -634,6 +676,7 @@ final class TaskRecord extends ThumbnailHolder {
         out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
         out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
         out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+        out.attribute(null, ATTR_NEVERRELINQUISH, String.valueOf(mNeverRelinquishIdentity));
         if (lastDescription != null) {
             out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
         }
@@ -684,6 +727,7 @@ final class TaskRecord extends ThumbnailHolder {
         int userId = 0;
         String lastDescription = null;
         long lastTimeOnTop = 0;
+        boolean neverRelinquishIdentity = true;
         int taskId = -1;
         final int outerDepth = in.getDepth();
 
@@ -714,6 +758,8 @@ final class TaskRecord extends ThumbnailHolder {
                 lastDescription = attrValue;
             } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
                 lastTimeOnTop = Long.valueOf(attrValue);
+            } else if (ATTR_NEVERRELINQUISH.equals(attrName)) {
+                neverRelinquishIdentity = Boolean.valueOf(attrValue);
             } else {
                 Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
             }
@@ -748,7 +794,7 @@ final class TaskRecord extends ThumbnailHolder {
         final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
                 affinityIntent, affinity, realActivity, origActivity, rootHasReset,
                 askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
-                lastTimeOnTop);
+                lastTimeOnTop, neverRelinquishIdentity);
 
         for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
index 353f603..e61bad9 100644 (file)
@@ -142,6 +142,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     }
 
     private static void invokeCallback(IHdmiControlCallback callback, int result) {
+        if (callback == null) {
+            return;
+        }
         try {
             callback.onComplete(result);
         } catch (RemoteException e) {
@@ -455,7 +458,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
     final void addCecDevice(HdmiCecDeviceInfo info) {
         assertRunOnServiceThread();
         addDeviceInfo(info);
-
+        mService.invokeDeviceEventListeners(info, true);
         // TODO: announce new device detection.
     }
 
@@ -466,10 +469,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
      */
     final void removeCecDevice(int address) {
         assertRunOnServiceThread();
-        removeDeviceInfo(address);
+        HdmiCecDeviceInfo info = removeDeviceInfo(address);
         mCecMessageCache.flushMessagesFrom(address);
-
-        // TODO: announce a device removal.
+        mService.invokeDeviceEventListeners(info, false);
     }
 
     /**
index fddb833..050ba46 100644 (file)
@@ -37,6 +37,7 @@ import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.server.SystemService;
 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
 
@@ -94,21 +95,25 @@ public final class HdmiControlService extends SystemService {
 
     // List of listeners registered by callers that want to get notified of
     // hotplug events.
+    @GuardedBy("mLock")
     private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
 
     // List of records for hotplug event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
             new ArrayList<>();
 
     // List of listeners registered by callers that want to get notified of
     // device status events.
+    @GuardedBy("mLock")
     private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
 
     // List of records for device event listener to handle the the caller killed in action.
+    @GuardedBy("mLock")
     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
             new ArrayList<>();
 
-    // Handler running on service thread. It's used to run a task in service thread.
+    // Handler used to run a task in service thread.
     private final Handler mHandler = new Handler();
 
     @Nullable
@@ -171,8 +176,7 @@ public final class HdmiControlService extends SystemService {
                     }
                     finished.append(deviceType, logicalAddress);
 
-                    // Once finish address allocation for all devices, notify
-                    // it to each device.
+                    // Address allocation completed for all devices. Notify each device.
                     if (deviceTypes.size() == finished.size()) {
                         notifyAddressAllocated(devices);
                     }
@@ -663,15 +667,28 @@ public final class HdmiControlService extends SystemService {
     }
 
     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
+        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
+        try {
+            listener.asBinder().linkToDeath(record, 0);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Listener already died");
+            return;
+        }
         synchronized (mLock) {
-            for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
-                if (record.mListener.asBinder() == listener.asBinder()) {
-                    listener.asBinder().unlinkToDeath(record, 0);
-                    mDeviceEventListenerRecords.remove(record);
-                    break;
+            mDeviceEventListeners.add(listener);
+            mDeviceEventListenerRecords.add(record);
+        }
+    }
+
+    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
+        synchronized (mLock) {
+            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
+                try {
+                    listener.onStatusChanged(device, activated);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to report device event:" + e);
                 }
             }
-            mHotplugEventListeners.remove(listener);
         }
     }
 
@@ -683,16 +700,16 @@ public final class HdmiControlService extends SystemService {
         }
     }
 
-    private void announceHotplugEvent(int portNo, boolean connected) {
-        HdmiHotplugEvent event = new HdmiHotplugEvent(portNo, connected);
+    private void announceHotplugEvent(int portId, boolean connected) {
+        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
         synchronized (mLock) {
             for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
-                invokeHotplugEventListener(listener, event);
+                invokeHotplugEventListenerLocked(listener, event);
             }
         }
     }
 
-    private void invokeHotplugEventListener(IHdmiHotplugEventListener listener,
+    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
             HdmiHotplugEvent event) {
         try {
             listener.onReceived(event);
index a7fc7eb..90e263a 100755 (executable)
@@ -4229,6 +4229,7 @@ public class PackageManagerService extends IPackageManager.Stub {
 
         try {
             pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
             mLastScanError = e.error;
             return false;
@@ -10272,6 +10273,7 @@ public class PackageManagerService extends IPackageManager.Stub {
 
         try {
             pp.collectCertificates(pkg, parseFlags);
+            pp.collectManifestDigest(pkg);
         } catch (PackageParserException e) {
             res.returnCode = e.error;
             return;
index cbbcc58..99b5b03 100644 (file)
@@ -631,7 +631,9 @@ public final class TvInputManagerService extends SystemService {
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
-            return false;
+            // STOPSHIP: Redesign the API around the availability change. For now, the service
+            // will be always available.
+            return true;
         }
 
         @Override
index 777471d..59b48e4 100644 (file)
@@ -373,19 +373,19 @@ public class WifiConfiguration implements Parcelable {
     public static int GOOD_RSSI_24 = -65;
 
     /** @hide **/
-    public static int LOW_RSSI_24 = -75;
+    public static int LOW_RSSI_24 = -77;
 
     /** @hide **/
     public static int BAD_RSSI_24 = -87;
 
     /** @hide **/
-    public static int GOOD_RSSI_5 = -55;
+    public static int GOOD_RSSI_5 = -60;
 
     /** @hide **/
-    public static int LOW_RSSI_5 = -65;
+    public static int LOW_RSSI_5 = -72;
 
     /** @hide **/
-    public static int BAD_RSSI_5 = -75;
+    public static int BAD_RSSI_5 = -82;
 
     /** @hide **/
     public static int UNWANTED_BLACKLIST_SOFT_BUMP = 4;
@@ -419,6 +419,15 @@ public class WifiConfiguration implements Parcelable {
      * 5GHz band is penalized if the 5GHz RSSI is lower than this threshold **/
     public static int G_BAND_PREFERENCE_RSSI_THRESHOLD = -75;
 
+    /** @hide
+     * Boost given to RSSI on a home network for the purpose of calculating the score
+     * This adds stickiness to home networks, as defined by:
+     * - less than 4 known BSSIDs
+     * - PSK only
+     * - TODO: add a test to verify that all BSSIDs are behind same gateway
+     ***/
+    public static int HOME_NETWORK_RSSI_BOOST = 5;
+
     /**
      * @hide
      * A summary of the RSSI and Band status for that configuration