Merge "Explicitly do not support SEARCH_SERVICE on UI_MODE_TYPE_WATCH"
diff --git a/api/current.txt b/api/current.txt
index e2d33d0..ee87fac 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -38261,17 +38261,6 @@
     method public byte[] transmit(byte[]) throws java.io.IOException;
   }
 
-  public abstract interface ISecureElementListener implements android.os.IInterface {
-    method public abstract void serviceConnected() throws android.os.RemoteException;
-  }
-
-  public static abstract class ISecureElementListener.Stub extends android.os.Binder implements android.se.omapi.ISecureElementListener {
-    ctor public ISecureElementListener.Stub();
-    method public android.os.IBinder asBinder();
-    method public static android.se.omapi.ISecureElementListener asInterface(android.os.IBinder);
-    method public boolean onTransact(int, android.os.Parcel, android.os.Parcel, int) throws android.os.RemoteException;
-  }
-
   public class Reader {
     method public void closeSessions();
     method public java.lang.String getName();
@@ -38281,13 +38270,19 @@
   }
 
   public class SEService {
-    ctor public SEService(android.content.Context, android.se.omapi.ISecureElementListener);
+    ctor public SEService(android.content.Context, android.se.omapi.SEService.SecureElementListener);
     method public android.se.omapi.Reader[] getReaders();
     method public java.lang.String getVersion();
     method public boolean isConnected();
     method public void shutdown();
   }
 
+  public static abstract class SEService.SecureElementListener extends android.os.Binder {
+    ctor public SEService.SecureElementListener();
+    method public android.os.IBinder asBinder();
+    method public void serviceConnected();
+  }
+
   public class Session {
     method public void close();
     method public void closeChannels();
diff --git a/api/test-current.txt b/api/test-current.txt
index 55e7926..d5b4311 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -732,6 +732,10 @@
     field public static final java.lang.String MBMS_STREAMING_SERVICE_OVERRIDE_METADATA = "mbms-streaming-service-override";
   }
 
+  public class ServiceState implements android.os.Parcelable {
+    method public void setSystemAndNetworkId(int, int);
+  }
+
 }
 
 package android.telephony.mbms {
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 6b573e9..521ab4e 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -109,7 +109,8 @@
         public static final int NOTIFICATION_SEEN = 10;
 
         /**
-         * An event type denoting a change in App Standby Bucket.
+         * An event type denoting a change in App Standby Bucket. Additional bucket information
+         * is contained in mBucketAndReason.
          * @hide
          */
         @SystemApi
@@ -180,11 +181,12 @@
         public String[] mContentAnnotations;
 
         /**
-         * The app standby bucket assigned.
+         * The app standby bucket assigned and reason. Bucket is the high order 16 bits, reason
+         * is the low order 16 bits.
          * Only present for {@link #STANDBY_BUCKET_CHANGED} event types
          * {@hide}
          */
-        public int mBucket;
+        public int mBucketAndReason;
 
         /** @hide */
         @EventFlags
@@ -205,7 +207,7 @@
             mContentType = orig.mContentType;
             mContentAnnotations = orig.mContentAnnotations;
             mFlags = orig.mFlags;
-            mBucket = orig.mBucket;
+            mBucketAndReason = orig.mBucketAndReason;
         }
 
         /**
@@ -268,7 +270,19 @@
          */
         @SystemApi
         public int getStandbyBucket() {
-            return mBucket;
+            return (mBucketAndReason & 0xFFFF0000) >>> 16;
+        }
+
+        /**
+         * Returns the reason for the bucketing, if the event is of type
+         * {@link #STANDBY_BUCKET_CHANGED}, otherwise returns 0. Reason values include
+         * the main reason which is one of REASON_MAIN_*, OR'ed with REASON_SUB_*, if there
+         * are sub-reasons for the main reason, such as REASON_SUB_USAGE_* when the main reason
+         * is REASON_MAIN_USAGE.
+         * @hide
+         */
+        public int getStandbyReason() {
+            return mBucketAndReason & 0x0000FFFF;
         }
 
         /** @hide */
@@ -428,7 +442,7 @@
                 p.writeStringArray(event.mContentAnnotations);
                 break;
             case Event.STANDBY_BUCKET_CHANGED:
-                p.writeInt(event.mBucket);
+                p.writeInt(event.mBucketAndReason);
                 break;
         }
     }
@@ -474,7 +488,7 @@
                 eventOut.mContentAnnotations = p.createStringArray();
                 break;
             case Event.STANDBY_BUCKET_CHANGED:
-                eventOut.mBucket = p.readInt();
+                eventOut.mBucketAndReason = p.readInt();
                 break;
         }
     }
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 9a0bb1d..5a57b06 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -132,24 +132,37 @@
     @SystemApi
     public static final int STANDBY_BUCKET_NEVER = 50;
 
-    /** {@hide} Reason for bucketing -- default initial state */
-    public static final String REASON_DEFAULT = "default";
+    /** @hide */
+    public static final int REASON_MAIN_MASK = 0xFF00;
+    /** @hide */
+    public static final int REASON_MAIN_DEFAULT =   0x0100;
+    /** @hide */
+    public static final int REASON_MAIN_TIMEOUT =   0x0200;
+    /** @hide */
+    public static final int REASON_MAIN_USAGE =     0x0300;
+    /** @hide */
+    public static final int REASON_MAIN_FORCED =    0x0400;
+    /** @hide */
+    public static final int REASON_MAIN_PREDICTED = 0x0500;
 
-    /** {@hide} Reason for bucketing -- timeout */
-    public static final String REASON_TIMEOUT = "timeout";
-
-    /** {@hide} Reason for bucketing -- usage */
-    public static final String REASON_USAGE = "usage";
-
-    /** {@hide} Reason for bucketing -- forced by user / shell command */
-    public static final String REASON_FORCED = "forced";
-
-    /**
-     * {@hide}
-     * Reason for bucketing -- predicted. This is a prefix and the UID of the bucketeer will
-     * be appended.
-     */
-    public static final String REASON_PREDICTED = "predicted";
+    /** @hide */
+    public static final int REASON_SUB_MASK = 0x00FF;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_SYSTEM_INTERACTION = 0x0001;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_NOTIFICATION_SEEN  = 0x0002;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_USER_INTERACTION   = 0x0003;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_MOVE_TO_FOREGROUND = 0x0004;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_MOVE_TO_BACKGROUND = 0x0005;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_SYSTEM_UPDATE      = 0x0006;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_ACTIVE_TIMEOUT     = 0x0007;
+    /** @hide */
+    public static final int REASON_SUB_USAGE_SYNC_ADAPTER       = 0x0008;
 
     /** @hide */
     @IntDef(flag = false, prefix = { "STANDBY_BUCKET_" }, value = {
@@ -427,6 +440,55 @@
         }
     }
 
+    /** @hide */
+    public static String reasonToString(int standbyReason) {
+        StringBuilder sb = new StringBuilder();
+        switch (standbyReason & REASON_MAIN_MASK) {
+            case REASON_MAIN_DEFAULT:
+                sb.append("d");
+                break;
+            case REASON_MAIN_FORCED:
+                sb.append("f");
+                break;
+            case REASON_MAIN_PREDICTED:
+                sb.append("p");
+                break;
+            case REASON_MAIN_TIMEOUT:
+                sb.append("t");
+                break;
+            case REASON_MAIN_USAGE:
+                sb.append("u-");
+                switch (standbyReason & REASON_SUB_MASK) {
+                    case REASON_SUB_USAGE_SYSTEM_INTERACTION:
+                        sb.append("si");
+                        break;
+                    case REASON_SUB_USAGE_NOTIFICATION_SEEN:
+                        sb.append("ns");
+                        break;
+                    case REASON_SUB_USAGE_USER_INTERACTION:
+                        sb.append("ui");
+                        break;
+                    case REASON_SUB_USAGE_MOVE_TO_FOREGROUND:
+                        sb.append("mf");
+                        break;
+                    case REASON_SUB_USAGE_MOVE_TO_BACKGROUND:
+                        sb.append("mb");
+                        break;
+                    case REASON_SUB_USAGE_SYSTEM_UPDATE:
+                        sb.append("su");
+                        break;
+                    case REASON_SUB_USAGE_ACTIVE_TIMEOUT:
+                        sb.append("at");
+                        break;
+                    case REASON_SUB_USAGE_SYNC_ADAPTER:
+                        sb.append("sa");
+                        break;
+                }
+                break;
+        }
+        return sb.toString();
+    }
+
     /**
      * {@hide}
      * Temporarily whitelist the specified app for a short duration. This is to allow an app
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 5d6a989..b62b1ee 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -139,7 +139,7 @@
 
         /** Callback to inform listeners that the idle state has changed to a new bucket. */
         public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
-                boolean idle, int bucket);
+                boolean idle, int bucket, int reason);
 
         /**
          * Callback to inform listeners that the parole state has changed. This means apps are
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index eb30979..93690bf 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -18,13 +18,27 @@
 
 import static android.content.ConfigurationProto.DENSITY_DPI;
 import static android.content.ConfigurationProto.FONT_SCALE;
+import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN;
+import static android.content.ConfigurationProto.HDR_COLOR_MODE;
+import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
+import static android.content.ConfigurationProto.LOCALES;
+import static android.content.ConfigurationProto.MCC;
+import static android.content.ConfigurationProto.MNC;
+import static android.content.ConfigurationProto.NAVIGATION;
+import static android.content.ConfigurationProto.NAVIGATION_HIDDEN;
 import static android.content.ConfigurationProto.ORIENTATION;
 import static android.content.ConfigurationProto.SCREEN_HEIGHT_DP;
 import static android.content.ConfigurationProto.SCREEN_LAYOUT;
 import static android.content.ConfigurationProto.SCREEN_WIDTH_DP;
 import static android.content.ConfigurationProto.SMALLEST_SCREEN_WIDTH_DP;
+import static android.content.ConfigurationProto.TOUCHSCREEN;
 import static android.content.ConfigurationProto.UI_MODE;
+import static android.content.ConfigurationProto.WIDE_COLOR_GAMUT;
 import static android.content.ConfigurationProto.WINDOW_CONFIGURATION;
+import static android.content.ResourcesConfigurationProto.CONFIGURATION;
+import static android.content.ResourcesConfigurationProto.SCREEN_HEIGHT_PX;
+import static android.content.ResourcesConfigurationProto.SCREEN_WIDTH_PX;
+import static android.content.ResourcesConfigurationProto.SDK_VERSION;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -38,6 +52,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.util.proto.ProtoOutputStream;
 import android.view.View;
 
@@ -1076,7 +1091,19 @@
     public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
         final long token = protoOutputStream.start(fieldId);
         protoOutputStream.write(FONT_SCALE, fontScale);
+        protoOutputStream.write(MCC, mcc);
+        protoOutputStream.write(MNC, mnc);
+        mLocaleList.writeToProto(protoOutputStream, LOCALES);
         protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
+        protoOutputStream.write(HDR_COLOR_MODE,
+                (colorMode & Configuration.COLOR_MODE_HDR_MASK) >> COLOR_MODE_HDR_SHIFT);
+        protoOutputStream.write(WIDE_COLOR_GAMUT,
+                colorMode & Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_MASK);
+        protoOutputStream.write(TOUCHSCREEN, touchscreen);
+        protoOutputStream.write(KEYBOARD_HIDDEN, keyboardHidden);
+        protoOutputStream.write(HARD_KEYBOARD_HIDDEN, hardKeyboardHidden);
+        protoOutputStream.write(NAVIGATION, navigation);
+        protoOutputStream.write(NAVIGATION_HIDDEN, navigationHidden);
         protoOutputStream.write(ORIENTATION, orientation);
         protoOutputStream.write(UI_MODE, uiMode);
         protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
@@ -1088,6 +1115,36 @@
     }
 
     /**
+     * Write full {@link android.content.ResourcesConfigurationProto} to protocol buffer output
+     * stream.
+     *
+     * @param protoOutputStream Stream to write the Configuration object to.
+     * @param fieldId           Field Id of the Configuration as defined in the parent message
+     * @param metrics           Current display information
+     * @hide
+     */
+    public void writeResConfigToProto(ProtoOutputStream protoOutputStream, long fieldId,
+            DisplayMetrics metrics) {
+        final int width, height;
+        if (metrics.widthPixels >= metrics.heightPixels) {
+            width = metrics.widthPixels;
+            height = metrics.heightPixels;
+        } else {
+            //noinspection SuspiciousNameCombination
+            width = metrics.heightPixels;
+            //noinspection SuspiciousNameCombination
+            height = metrics.widthPixels;
+        }
+
+        final long token = protoOutputStream.start(fieldId);
+        writeToProto(protoOutputStream, CONFIGURATION);
+        protoOutputStream.write(SDK_VERSION, Build.VERSION.RESOURCES_SDK_INT);
+        protoOutputStream.write(SCREEN_WIDTH_PX, width);
+        protoOutputStream.write(SCREEN_HEIGHT_PX, height);
+        protoOutputStream.end(token);
+    }
+
+    /**
      * Convert the UI mode to a human readable format.
      * @hide
      */
@@ -1925,11 +1982,21 @@
 
     /**
      * Returns a string representation of the configuration that can be parsed
-     * by build tools (like AAPT).
+     * by build tools (like AAPT), without display metrics included
      *
      * @hide
      */
     public static String resourceQualifierString(Configuration config) {
+        return resourceQualifierString(config, null);
+    }
+
+    /**
+     * Returns a string representation of the configuration that can be parsed
+     * by build tools (like AAPT).
+     *
+     * @hide
+     */
+    public static String resourceQualifierString(Configuration config, DisplayMetrics metrics) {
         ArrayList<String> parts = new ArrayList<String>();
 
         if (config.mcc != 0) {
@@ -2177,6 +2244,20 @@
                 break;
         }
 
+        if (metrics != null) {
+            final int width, height;
+            if (metrics.widthPixels >= metrics.heightPixels) {
+                width = metrics.widthPixels;
+                height = metrics.heightPixels;
+            } else {
+                //noinspection SuspiciousNameCombination
+                width = metrics.heightPixels;
+                //noinspection SuspiciousNameCombination
+                height = metrics.widthPixels;
+            }
+            parts.add(width + "x" + height);
+        }
+
         parts.add("v" + Build.VERSION.RESOURCES_SDK_INT);
         return TextUtils.join("-", parts);
     }
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index ca9cbec..87e1b7d 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -20,7 +20,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.content.LocaleProto;
 import android.icu.util.ULocale;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -140,6 +142,25 @@
     }
 
     /**
+     * Helper to write LocaleList to a protocol buffer output stream.  Assumes the parent
+     * protobuf has declared the locale as repeated.
+     *
+     * @param protoOutputStream Stream to write the locale to.
+     * @param fieldId Field Id of the Locale as defined in the parent message.
+     * @hide
+     */
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        for (int i = 0; i < mList.length; i++) {
+            final Locale locale = mList[i];
+            final long token = protoOutputStream.start(fieldId);
+            protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage());
+            protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry());
+            protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant());
+            protoOutputStream.end(token);
+        }
+    }
+
+    /**
      * Retrieves a String representation of the language tags in this list.
      */
     @NonNull
diff --git a/core/java/android/se/omapi/ISecureElementListener.aidl b/core/java/android/se/omapi/ISecureElementListener.aidl
index 3a99d63..e0c6e04 100644
--- a/core/java/android/se/omapi/ISecureElementListener.aidl
+++ b/core/java/android/se/omapi/ISecureElementListener.aidl
@@ -21,6 +21,7 @@
 
 /**
  * Interface to receive call-backs when the service is connected.
+ * @hide
  */
 interface ISecureElementListener {
   /**
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index b8937e6..d59e86a 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -59,6 +59,21 @@
      */
     public static final int NO_SUCH_ELEMENT_ERROR = 2;
 
+    /**
+     * Interface to send call-backs to the application when the service is connected.
+     */
+    public abstract static class SecureElementListener extends ISecureElementListener.Stub {
+        @Override
+        public IBinder asBinder() {
+            return this;
+        }
+
+        /**
+         * Called by the framework when the service is connected.
+         */
+        public void serviceConnected() {};
+    }
+
     private static final String TAG = "OMAPI.SEService";
 
     private final Object mLock = new Object();
@@ -98,9 +113,9 @@
      *            the context of the calling application. Cannot be
      *            <code>null</code>.
      * @param listener
-     *            a ISecureElementListener object. Can be <code>null</code>.
+     *            a SecureElementListener object. Can be <code>null</code>.
      */
-    public SEService(Context context, ISecureElementListener listener) {
+    public SEService(Context context, SecureElementListener listener) {
 
         if (context == null) {
             throw new NullPointerException("context must not be null");
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 9b4ea33..d837518 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -137,7 +137,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 175 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 176 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -14602,6 +14602,7 @@
             u.mJobsFreshnessTimeMs.writeSummaryFromParcelLocked(out);
             for (int i = 0; i < JOB_FRESHNESS_BUCKETS.length; i++) {
                 if (u.mJobsFreshnessBuckets[i] != null) {
+                    out.writeInt(1);
                     u.mJobsFreshnessBuckets[i].writeSummaryFromParcelLocked(out);
                 } else {
                     out.writeInt(0);
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index 7178ec7..46667d1 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -16,6 +16,7 @@
 package com.android.internal.os;
 
 import android.os.Process;
+import android.os.StrictMode;
 import android.os.SystemClock;
 import android.util.Slog;
 
@@ -69,6 +70,7 @@
         boolean wakeup_sources;
         final long startTime = SystemClock.uptimeMillis();
 
+        final int oldMask = StrictMode.allowThreadDiskReadsMask();
         try {
             FileInputStream is;
             try {
@@ -90,6 +92,8 @@
         } catch (java.io.IOException e) {
             Slog.wtf(TAG, "failed to read kernel wakelocks", e);
             return null;
+        } finally {
+            StrictMode.setThreadPolicyMask(oldMask);
         }
 
         final long readTime = SystemClock.uptimeMillis() - startTime;
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index a62d56c..834ecde 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -25,7 +25,7 @@
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 /**
- * An android resource configuration.
+ * An android Configuration object.
  */
 message ConfigurationProto {
   option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -35,17 +35,65 @@
   optional uint32 mnc = 3;
   repeated LocaleProto locales = 4;
   optional uint32 screen_layout = 5;
-  optional uint32 touchscreen = 6;
-  optional uint32 keyboard_hidden = 7;
-  optional uint32 hard_keyboard_hidden = 8;
-  optional uint32 navigation = 9;
-  optional uint32 navigation_hidden = 10;
-  optional uint32 orientation = 11;
-  optional uint32 ui_mode = 12;
-  optional uint32 screen_width_dp = 13;
-  optional uint32 screen_height_dp = 14;
-  optional uint32 smallest_screen_width_dp = 15;
-  optional uint32 density_dpi = 16;
-  optional .android.app.WindowConfigurationProto window_configuration = 17;
+  optional uint32 hdr_color_mode = 6;
+  optional uint32 wide_color_gamut = 7;
+  optional uint32 touchscreen = 8;
+  optional uint32 keyboard_hidden = 9;
+  optional uint32 hard_keyboard_hidden = 10;
+  optional uint32 navigation = 11;
+  optional uint32 navigation_hidden = 12;
+  optional uint32 orientation = 13;
+  optional uint32 ui_mode = 14;
+  optional uint32 screen_width_dp = 15;
+  optional uint32 screen_height_dp = 16;
+  optional uint32 smallest_screen_width_dp = 17;
+  optional uint32 density_dpi = 18;
+  optional .android.app.WindowConfigurationProto window_configuration = 19;
 }
 
+/**
+ * All current configuration data used to select resources.
+ */
+message ResourcesConfigurationProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  required ConfigurationProto configuration = 1;
+
+  optional uint32 sdk_version = 2;
+  optional uint32 screen_width_px = 3;
+  optional uint32 screen_height_px = 4;
+}
+
+/**
+ * Overall device configuration data.
+ */
+message DeviceConfigurationProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional uint32 stable_screen_width_px = 1;
+  optional uint32 stable_screen_height_px = 2;
+  optional uint32 stable_density_dpi = 3;
+
+  optional uint64 total_ram = 4;
+  optional bool low_ram = 5;
+  optional uint32 max_cores = 6;
+  optional bool has_secure_screen_lock = 7;
+
+  optional uint32 opengl_version = 8;
+  repeated string opengl_extensions = 9;
+
+  repeated string shared_libraries = 10;
+  repeated string features = 11;
+  repeated string cpu_architectures = 12;
+}
+
+/**
+ * All current configuration data device is running with, everything used
+ * to filter and target apps.
+ */
+message GlobalConfigurationProto {
+  option (.android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional ResourcesConfigurationProto resources = 1;
+  optional DeviceConfigurationProto device = 2;
+}
diff --git a/core/res/res/drawable-watch/sym_def_app_icon.xml b/core/res/res/drawable-watch/sym_def_app_icon.xml
index 2720bfa..6945256 100644
--- a/core/res/res/drawable-watch/sym_def_app_icon.xml
+++ b/core/res/res/drawable-watch/sym_def_app_icon.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@color/accent_device_default_dark" />
+    <background android:drawable="@drawable/sym_def_app_icon_background" />
     <foreground android:drawable="@mipmap/sym_def_app_icon_foreground" />
 </adaptive-icon>
diff --git a/core/res/res/drawable-watch/sym_def_app_icon_background.xml b/core/res/res/drawable-watch/sym_def_app_icon_background.xml
new file mode 100644
index 0000000..6d6352f
--- /dev/null
+++ b/core/res/res/drawable-watch/sym_def_app_icon_background.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="#2374CE"/>
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 525d493..346aad6c 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -43,6 +43,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="top"
+            android:layout_weight="1"
             android:paddingStart="@dimen/notification_content_margin_start"
             android:paddingEnd="@dimen/notification_content_margin_end"
             android:clipToPadding="false"
@@ -75,19 +76,19 @@
                 android:maxLines="3"
             />
         </LinearLayout>
+        <FrameLayout android:id="@+id/actions_container"
+                     android:layout_width="match_parent"
+                     android:layout_height="wrap_content"
+                     android:layout_gravity="bottom">
+            <com.android.internal.widget.NotificationActionListLayout
+                android:id="@+id/actions"
+                android:layout_width="match_parent"
+                android:layout_height="@dimen/notification_action_list_height"
+                android:paddingEnd="4dp"
+                android:orientation="horizontal"
+                android:gravity="center"
+                android:visibility="gone"
+            />
+        </FrameLayout>
     </LinearLayout>
-    <FrameLayout android:id="@+id/actions_container"
-                 android:layout_width="match_parent"
-                 android:layout_height="wrap_content"
-                 android:layout_gravity="bottom">
-        <com.android.internal.widget.NotificationActionListLayout
-            android:id="@+id/actions"
-            android:layout_width="match_parent"
-            android:layout_height="@dimen/notification_action_list_height"
-            android:paddingEnd="4dp"
-            android:orientation="horizontal"
-            android:gravity="center"
-            android:visibility="gone"
-        />
-    </FrameLayout>
 </FrameLayout>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
index 3cda9c9..784c714 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java
@@ -444,6 +444,7 @@
 
     private void dispatchActiveDeviceChanged(CachedBluetoothDevice activeDevice,
                                              int bluetoothProfile) {
+        mDeviceManager.onActiveDeviceChanged(activeDevice, bluetoothProfile);
         synchronized (mCallbacks) {
             for (BluetoothCallback callback : mCallbacks) {
                 callback.onActiveDeviceChanged(activeDevice, bluetoothProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index e1ebbc4..f6ec6a8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -27,6 +27,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.bluetooth.BluetoothAdapter;
+import android.support.annotation.VisibleForTesting;
 
 import com.android.settingslib.R;
 
@@ -461,12 +462,12 @@
     }
 
     /**
-     * Set the device status as active or non-active per Bluetooth profile.
+     * Update the device status as active or non-active per Bluetooth profile.
      *
      * @param isActive true if the device is active
      * @param bluetoothProfile the Bluetooth profile
      */
-    public void setActiveDevice(boolean isActive, int bluetoothProfile) {
+    public void onActiveDeviceChanged(boolean isActive, int bluetoothProfile) {
         boolean changed = false;
         switch (bluetoothProfile) {
         case BluetoothProfile.A2DP:
@@ -478,7 +479,7 @@
             mIsActiveDeviceHeadset = isActive;
             break;
         default:
-            Log.w(TAG, "setActiveDevice: unknown profile " + bluetoothProfile +
+            Log.w(TAG, "onActiveDeviceChanged: unknown profile " + bluetoothProfile +
                     " isActive " + isActive);
             break;
         }
@@ -487,6 +488,26 @@
         }
     }
 
+    /**
+     * Get the device status as active or non-active per Bluetooth profile.
+     *
+     * @param bluetoothProfile the Bluetooth profile
+     * @return true if the device is active
+     */
+    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+    public boolean isActiveDevice(int bluetoothProfile) {
+        switch (bluetoothProfile) {
+            case BluetoothProfile.A2DP:
+                return mIsActiveDeviceA2dp;
+            case BluetoothProfile.HEADSET:
+                return mIsActiveDeviceHeadset;
+            default:
+                Log.w(TAG, "getActiveDevice: unknown profile " + bluetoothProfile);
+                break;
+        }
+        return false;
+    }
+
     void setRssi(short rssi) {
         if (mRssi != rssi) {
             mRssi = rssi;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index c3ff617..a8e0039 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -24,6 +24,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * CachedBluetoothDeviceManager manages the set of remote Bluetooth devices.
@@ -167,6 +168,15 @@
             }
         }
     }
+
+    public synchronized void onActiveDeviceChanged(CachedBluetoothDevice activeDevice,
+                                                   int bluetoothProfile) {
+        for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
+            boolean isActive = Objects.equals(cachedDevice, activeDevice);
+            cachedDevice.onActiveDeviceChanged(isActive, bluetoothProfile);
+        }
+    }
+
     private void log(String msg) {
         if (DEBUG) {
             Log.d(TAG, msg);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
new file mode 100644
index 0000000..d6b2006
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import com.android.settingslib.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Collection;
+
+@RunWith(RobolectricTestRunner.class)
+public class CachedBluetoothDeviceManagerTest {
+    private final static String DEVICE_NAME_1 = "TestName_1";
+    private final static String DEVICE_NAME_2 = "TestName_2";
+    private final static String DEVICE_ALIAS_1 = "TestAlias_1";
+    private final static String DEVICE_ALIAS_2 = "TestAlias_2";
+    private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11";
+    private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
+    private final BluetoothClass DEVICE_CLASS_1 =
+        new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+    private final BluetoothClass DEVICE_CLASS_2 =
+        new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+    @Mock
+    private LocalBluetoothAdapter mLocalAdapter;
+    @Mock
+    private LocalBluetoothProfileManager mLocalProfileManager;
+    @Mock
+    private LocalBluetoothManager mLocalBluetoothManager;
+    @Mock
+    private BluetoothEventManager mBluetoothEventManager;
+    @Mock
+    private HeadsetProfile mHfpProfile;
+    @Mock
+    private A2dpProfile mA2dpProfile;
+    @Mock
+    private PanProfile mPanProfile;
+    @Mock
+    private BluetoothDevice mDevice1;
+    @Mock
+    private BluetoothDevice mDevice2;
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private Context mContext;
+    private String[] mActiveDeviceStringsArray;
+    private String mActiveDeviceStringNone;
+    private String mActiveDeviceStringAll;
+    private String mActiveDeviceStringMedia;
+    private String mActiveDeviceStringPhone;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
+        when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
+        when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
+        when(mDevice2.getName()).thenReturn(DEVICE_NAME_2);
+        when(mDevice1.getAliasName()).thenReturn(DEVICE_ALIAS_1);
+        when(mDevice2.getAliasName()).thenReturn(DEVICE_ALIAS_2);
+        when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1);
+        when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2);
+
+        when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
+        when(mLocalAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON);
+        when(mHfpProfile.isProfileReady()).thenReturn(true);
+        when(mA2dpProfile.isProfileReady()).thenReturn(true);
+        when(mPanProfile.isProfileReady()).thenReturn(true);
+        mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
+    }
+
+    /**
+     * Test to verify addDevice().
+     */
+    @Test
+    public void testAddDevice_validCachedDevices_devicesAdded() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).contains(cachedDevice2);
+
+        assertThat(mCachedDeviceManager.findDevice(mDevice1)).isEqualTo(cachedDevice1);
+        assertThat(mCachedDeviceManager.findDevice(mDevice2)).isEqualTo(cachedDevice2);
+    }
+
+    /**
+     * Test to verify getName().
+     */
+    @Test
+    public void testGetName_validCachedDevice_nameFound() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        assertThat(mCachedDeviceManager.getName(mDevice1)).isEqualTo(DEVICE_ALIAS_1);
+    }
+
+    /**
+     * Test to verify onDeviceNameUpdated().
+     */
+    @Test
+    public void testOnDeviceNameUpdated_validName_nameUpdated() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        assertThat(cachedDevice1.getName()).isEqualTo(DEVICE_ALIAS_1);
+
+        final String newAliasName = "NewAliasName";
+        when(mDevice1.getAliasName()).thenReturn(newAliasName);
+        mCachedDeviceManager.onDeviceNameUpdated(mDevice1);
+        assertThat(cachedDevice1.getName()).isEqualTo(newAliasName);
+    }
+
+    /**
+     * Test to verify clearNonBondedDevices().
+     */
+    @Test
+    public void testClearNonBondedDevices_bondedAndNonBondedDevices_nonBondedDevicesCleared() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+        mCachedDeviceManager.clearNonBondedDevices();
+        Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).contains(cachedDevice2);
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        mCachedDeviceManager.clearNonBondedDevices();
+        devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).contains(cachedDevice1);
+        assertThat(devices).doesNotContain(cachedDevice2);
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        mCachedDeviceManager.clearNonBondedDevices();
+        devices = mCachedDeviceManager.getCachedDevicesCopy();
+        assertThat(devices).doesNotContain(cachedDevice1);
+        assertThat(devices).doesNotContain(cachedDevice2);
+    }
+
+    /**
+     * Test to verify onBtClassChanged().
+     */
+    @Test
+    public void testOnBtClassChanged_validBtClass_classChanged() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        assertThat(cachedDevice1.getBtClass()).isEqualTo(DEVICE_CLASS_1);
+
+        final BluetoothClass newBluetoothClass = DEVICE_CLASS_2;
+        when(mDevice1.getBluetoothClass()).thenReturn(newBluetoothClass);
+        mCachedDeviceManager.onBtClassChanged(mDevice1);
+        assertThat(cachedDevice1.getBtClass()).isEqualTo(newBluetoothClass);
+    }
+
+    /**
+     * Test to verify onDeviceDisappeared().
+     */
+    @Test
+    public void testOnDeviceDisappeared_deviceBondedUnbonded_unbondedDeviceDisappeared() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isFalse();
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
+        assertThat(mCachedDeviceManager.onDeviceDisappeared(cachedDevice1)).isTrue();
+    }
+
+    /**
+     * Test to verify onActiveDeviceChanged().
+     */
+    @Test
+    public void testOnActiveDeviceChanged_connectedDevices_activeDeviceChanged() {
+        CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice1);
+        assertThat(cachedDevice1).isNotNull();
+        CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter,
+                mLocalProfileManager, mDevice2);
+        assertThat(cachedDevice2).isNotNull();
+
+        when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+        // Connect both devices for A2DP and HFP
+        cachedDevice1.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice2.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice1.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+        cachedDevice2.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
+
+        // Verify that both devices are connected and none is Active
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+
+        // The first device is active for A2DP, the second device is active for HFP
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1, BluetoothProfile.A2DP);
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice2, BluetoothProfile.HEADSET);
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
+
+        // The first device is active for A2DP and HFP
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice1, BluetoothProfile.HEADSET);
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+
+        // The second device is active for A2DP and HFP
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice2, BluetoothProfile.A2DP);
+        mCachedDeviceManager.onActiveDeviceChanged(cachedDevice2, BluetoothProfile.HEADSET);
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isTrue();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
+
+        // No active device for A2DP
+        mCachedDeviceManager.onActiveDeviceChanged(null, BluetoothProfile.A2DP);
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isTrue();
+
+        // No active device for HFP
+        mCachedDeviceManager.onActiveDeviceChanged(null, BluetoothProfile.HEADSET);
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice1.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.A2DP)).isFalse();
+        assertThat(cachedDevice2.isActiveDevice(BluetoothProfile.HEADSET)).isFalse();
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 92c68e6..0775727 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -147,7 +147,7 @@
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
 
         // Set device as Active for A2DP and test connection state summary
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(media)");
 
         // Test with battery level
@@ -163,7 +163,7 @@
         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         // Set A2DP profile to be connected, Active and test connection state summary
         mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(media)");
 
         // Set A2DP profile to be disconnected and test connection state summary
@@ -179,7 +179,7 @@
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
 
         // Set device as Active for HFP and test connection state summary
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(phone)");
 
         // Test with battery level
@@ -195,7 +195,7 @@
         mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
         // Set HFP profile to be connected, Active and test connection state summary
         mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active(phone)");
 
         // Set HFP profile to be disconnected and test connection state summary
@@ -212,8 +212,8 @@
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected");
 
         // Set device as Active for A2DP and HFP and test connection state summary
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active");
 
         // Test with battery level
@@ -222,16 +222,16 @@
                 "Connected, battery 10%, active");
 
         // Disconnect A2DP only and test connection state summary
-        mCachedDevice.setActiveDevice(false, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.A2DP);
         mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_DISCONNECTED);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
                 "Connected (no media), battery 10%, active(phone)");
 
         // Disconnect HFP only and test connection state summary
-        mCachedDevice.setActiveDevice(false, BluetoothProfile.HEADSET);
+        mCachedDevice.onActiveDeviceChanged(false, BluetoothProfile.HEADSET);
         mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED);
         mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(
                 "Connected (no phone), battery 10%, active(media)");
 
@@ -240,8 +240,8 @@
         // Set A2DP and HFP profiles to be connected, Active and test connection state summary
         mCachedDevice.onProfileStateChanged(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
         mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_CONNECTED);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.A2DP);
-        mCachedDevice.setActiveDevice(true, BluetoothProfile.HEADSET);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.HEADSET);
         assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Connected, active");
 
         // Set A2DP and HFP profiles to be disconnected and test connection state summary
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 4ffe5fe..933c952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -4985,7 +4985,7 @@
                                     notificationKey)) {
                                 // Show work challenge, do not run PendingIntent and
                                 // remove notification
-                                collapsePanel();
+                                collapseOnMainThread();
                                 return;
                             }
                         }
@@ -5026,11 +5026,7 @@
                     }
                 }
                 if (shouldCollapse()) {
-                    if (Looper.getMainLooper().isCurrentThread()) {
-                        collapsePanel();
-                    } else {
-                        mStackScroller.post(this::collapsePanel);
-                    }
+                    collapseOnMainThread();
                 }
 
                 try {
@@ -5058,6 +5054,14 @@
         }, afterKeyguardGone);
     }
 
+    private void collapseOnMainThread() {
+        if (Looper.getMainLooper().isCurrentThread()) {
+            collapsePanel();
+        } else {
+            mStackScroller.post(this::collapsePanel);
+        }
+    }
+
     private boolean shouldCollapse() {
         return mState != StatusBarState.SHADE || !mActivityLaunchAnimator.isAnimationPending();
     }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 9cd3621..c93f405 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -3860,7 +3860,7 @@
     final class AppStandbyTracker extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
-                boolean idle, int bucket) {
+                boolean idle, int bucket, int reason) {
             if (DEBUG_STANDBY) {
                 Slog.d(TAG, "Package " + packageName + " for user " + userId + " now in bucket " +
                         bucket);
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java
index fc4d463..a6b71b7 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -678,7 +678,7 @@
     final class StandbyTracker extends AppIdleStateChangeListener {
         @Override
         public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
-                int bucket) {
+                int bucket, int reason) {
             if (DEBUG) {
                 Slog.d(TAG,"onAppIdleStateChanged: " + packageName + " u" + userId
                         + (idle ? " idle" : " active") + " " + bucket);
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 0d6d2bd..6550d06 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -85,7 +85,7 @@
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_3 = 10;
     private static final int DEFAULT_POWER_CHECK_MAX_CPU_4 = 2;
     private static final long DEFAULT_SERVICE_USAGE_INTERACTION_TIME = 30*60*1000;
-    private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 24*60*60*1000L;
+    private static final long DEFAULT_USAGE_STATS_INTERACTION_INTERVAL = 2*60*60*1000L;
     private static final long DEFAULT_SERVICE_RESTART_DURATION = 1*1000;
     private static final long DEFAULT_SERVICE_RESET_RUN_DURATION = 60*1000;
     private static final int DEFAULT_SERVICE_RESTART_DURATION_FACTOR = 4;
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 07e27bc..3b2a22d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -23,6 +23,7 @@
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.IUidObserver;
+import android.app.KeyguardManager;
 import android.app.ProfilerInfo;
 import android.app.WaitResult;
 import android.app.usage.AppStandbyInfo;
@@ -32,16 +33,24 @@
 import android.content.ComponentCallbacks2;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.DeviceConfigurationProto;
+import android.content.GlobalConfigurationProto;
 import android.content.IIntentReceiver;
 import android.content.Intent;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.content.pm.SharedLibraryInfo;
 import android.content.pm.UserInfo;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
@@ -57,8 +66,11 @@
 import android.util.ArrayMap;
 import android.util.DebugUtils;
 import android.util.DisplayMetrics;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 
 import com.android.internal.util.HexDump;
+import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.Preconditions;
 
 import java.io.BufferedReader;
@@ -75,6 +87,11 @@
 import java.util.List;
 import java.util.Map;
 
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.opengles.GL;
+import javax.microedition.khronos.opengles.GL10;
+
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
@@ -1835,17 +1852,112 @@
         }
     }
 
-    int runGetConfig(PrintWriter pw) throws RemoteException {
-        int days = 14;
-        String option = getNextOption();
-        if (option != null) {
-            if (!option.equals("--days")) {
-                throw new IllegalArgumentException("unrecognized option " + option);
-            }
+    private void writeDeviceConfig(ProtoOutputStream protoOutputStream, long fieldId,
+            PrintWriter pw, Configuration config, DisplayManager dm) {
+        Point stableSize = dm.getStableDisplaySize();
+        long token = -1;
+        if (protoOutputStream != null) {
+            token = protoOutputStream.start(fieldId);
+            protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_WIDTH_PX, stableSize.x);
+            protoOutputStream.write(DeviceConfigurationProto.STABLE_SCREEN_HEIGHT_PX, stableSize.y);
+            protoOutputStream.write(DeviceConfigurationProto.STABLE_DENSITY_DPI,
+                    DisplayMetrics.DENSITY_DEVICE_STABLE);
+        }
+        if (pw != null) {
+            pw.print("stable-width-px: "); pw.println(stableSize.x);
+            pw.print("stable-height-px: "); pw.println(stableSize.y);
+            pw.print("stable-density-dpi: "); pw.println(DisplayMetrics.DENSITY_DEVICE_STABLE);
+        }
 
-            days = Integer.parseInt(getNextArgRequired());
-            if (days <= 0) {
-                throw new IllegalArgumentException("--days must be a positive integer");
+        MemInfoReader memreader = new MemInfoReader();
+        memreader.readMemInfo();
+        KeyguardManager kgm = mInternal.mContext.getSystemService(KeyguardManager.class);
+        if (protoOutputStream != null) {
+            protoOutputStream.write(DeviceConfigurationProto.TOTAL_RAM, memreader.getTotalSize());
+            protoOutputStream.write(DeviceConfigurationProto.LOW_RAM,
+                    ActivityManager.isLowRamDeviceStatic());
+            protoOutputStream.write(DeviceConfigurationProto.MAX_CORES,
+                    Runtime.getRuntime().availableProcessors());
+            protoOutputStream.write(DeviceConfigurationProto.HAS_SECURE_SCREEN_LOCK,
+                    kgm.isDeviceSecure());
+        }
+        if (pw != null) {
+            pw.print("total-ram: "); pw.println(memreader.getTotalSize());
+            pw.print("low-ram: "); pw.println(ActivityManager.isLowRamDeviceStatic());
+            pw.print("max-cores: "); pw.println(Runtime.getRuntime().availableProcessors());
+            pw.print("has-secure-screen-lock: "); pw.println(kgm.isDeviceSecure());
+        }
+
+        ConfigurationInfo configInfo = mInternal.getDeviceConfigurationInfo();
+        if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
+            if (protoOutputStream != null) {
+                protoOutputStream.write(DeviceConfigurationProto.OPENGL_VERSION,
+                        configInfo.reqGlEsVersion);
+            }
+            if (pw != null) {
+                pw.print("opengl-version: 0x");
+                pw.println(Integer.toHexString(configInfo.reqGlEsVersion));
+            }
+        }
+
+        /*
+        GL10 gl = ((GL10)((EGL10)EGLContext.getEGL()).eglGetCurrentContext().getGL());
+        protoOutputStream.write(DeviceConfigurationProto.OPENGL_VERSION,
+                gl.glGetString(GL10.GL_VERSION));
+        String glExtensions = gl.glGetString(GL10.GL_EXTENSIONS);
+        for (String ext : glExtensions.split(" ")) {
+            protoOutputStream.write(DeviceConfigurationProto.OPENGL_EXTENSIONS, ext);
+        }
+        */
+
+        PackageManager pm = mInternal.mContext.getPackageManager();
+        List<SharedLibraryInfo> slibs = pm.getSharedLibraries(0);
+        for (int i = 0; i < slibs.size(); i++) {
+            if (protoOutputStream != null) {
+                protoOutputStream.write(DeviceConfigurationProto.SHARED_LIBRARIES,
+                        slibs.get(i).getName());
+            }
+            if (pw != null) {
+                pw.print("shared-libraries: "); pw.println(slibs.get(i).getName());
+            }
+        }
+
+        FeatureInfo[] features = pm.getSystemAvailableFeatures();
+        for (int i = 0; i < features.length; i++) {
+            if (features[i].name != null) {
+                if (protoOutputStream != null) {
+                    protoOutputStream.write(DeviceConfigurationProto.FEATURES, features[i].name);
+                }
+                if (pw != null) {
+                    pw.print("features: "); pw.println(features[i].name);
+                }
+            }
+        }
+
+        if (protoOutputStream != null) {
+            protoOutputStream.end(token);
+        }
+    }
+
+    int runGetConfig(PrintWriter pw) throws RemoteException {
+        int days = -1;
+        boolean asProto = false;
+        boolean inclDevice = false;
+
+        String opt;
+        while ((opt=getNextOption()) != null) {
+            if (opt.equals("--days")) {
+                days = Integer.parseInt(getNextArgRequired());
+                if (days <= 0) {
+                    throw new IllegalArgumentException("--days must be a positive integer");
+                }
+            } else if (opt.equals("--proto")) {
+                asProto = true;
+            } else if (opt.equals("--device")) {
+                inclDevice = true;
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
             }
         }
 
@@ -1855,18 +1967,38 @@
             return -1;
         }
 
-        pw.println("config: " + Configuration.resourceQualifierString(config));
-        pw.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
+        DisplayManager dm = mInternal.mContext.getSystemService(DisplayManager.class);
+        Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
+        DisplayMetrics metrics = new DisplayMetrics();
+        display.getMetrics(metrics);
 
-        final List<Configuration> recentConfigs = getRecentConfigurations(days);
-        final int recentConfigSize = recentConfigs.size();
-        if (recentConfigSize > 0) {
-            pw.println("recentConfigs:");
-        }
+        if (asProto) {
+            final ProtoOutputStream proto = new ProtoOutputStream(getOutFileDescriptor());
+            config.writeResConfigToProto(proto, GlobalConfigurationProto.RESOURCES, metrics);
+            if (inclDevice) {
+                writeDeviceConfig(proto, GlobalConfigurationProto.DEVICE, null, config, dm);
+            }
+            proto.flush();
 
-        for (int i = 0; i < recentConfigSize; i++) {
-            pw.println("  config: " + Configuration.resourceQualifierString(
-                    recentConfigs.get(i)));
+        } else {
+            pw.println("config: " + Configuration.resourceQualifierString(config, metrics));
+            pw.println("abi: " + TextUtils.join(",", Build.SUPPORTED_ABIS));
+            if (inclDevice) {
+                writeDeviceConfig(null, -1, pw, config, dm);
+            }
+
+            if (days >= 0) {
+                final List<Configuration> recentConfigs = getRecentConfigurations(days);
+                final int recentConfigSize = recentConfigs.size();
+                if (recentConfigSize > 0) {
+                    pw.println("recentConfigs:");
+                    for (int i = 0; i < recentConfigSize; i++) {
+                        pw.println("  config: " + Configuration.resourceQualifierString(
+                                recentConfigs.get(i)));
+                    }
+                }
+            }
+
         }
         return 0;
     }
@@ -2729,8 +2861,11 @@
             pw.println("      Gets the process state of an app given its <UID>.");
             pw.println("  attach-agent <PROCESS> <FILE>");
             pw.println("    Attach an agent to the specified <PROCESS>, which may be either a process name or a PID.");
-            pw.println("  get-config");
-            pw.println("      Rtrieve the configuration and any recent configurations of the device.");
+            pw.println("  get-config [--days N] [--device] [--proto]");
+            pw.println("      Retrieve the configuration and any recent configurations of the device.");
+            pw.println("      --days: also return last N days of configurations that have been seen.");
+            pw.println("      --device: also output global device configuration info.");
+            pw.println("      --proto: return result as a proto; does not include --days info.");
             pw.println("  supports-multiwindow");
             pw.println("      Returns true if the device supports multiwindow.");
             pw.println("  supports-split-screen-multi-window");
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 740866c..017fada 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -2240,7 +2240,7 @@
 
         @Override
         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
-                boolean idle, int bucket) {
+                boolean idle, int bucket, int reason) {
             final int uid = mLocalPM.getPackageUid(packageName,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
             if (uid < 0) {
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index bd8fe28..ed29a4c 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -186,7 +186,8 @@
     private final class AppIdleStateChangeListener
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket,
+                int reason) {
             boolean changed = false;
             synchronized (mLock) {
                 if (mAppIdleParoleOn) {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index f29e0bb..ab55553 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -3947,7 +3947,8 @@
             extends UsageStatsManagerInternal.AppIdleStateChangeListener {
 
         @Override
-        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket) {
+        public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket,
+                int reason) {
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
diff --git a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
index 90db2a3..796d364 100644
--- a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.server;
 
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+
 import static com.android.server.AppStateTracker.TARGET_OP;
 
 import static org.junit.Assert.assertEquals;
@@ -612,7 +615,7 @@
 
         // Exempt package 2 on user-10.
         mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false,
-                UsageStatsManager.STANDBY_BUCKET_EXEMPTED);
+                UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
 
         areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
@@ -624,7 +627,7 @@
 
         // Exempt package 1 on user-0.
         mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false,
-                UsageStatsManager.STANDBY_BUCKET_EXEMPTED);
+                UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
 
         areRestricted(instance, UID_1, PACKAGE_1, NONE);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
@@ -632,7 +635,7 @@
 
         // Unexempt package 2 on user-10.
         mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false,
-                UsageStatsManager.STANDBY_BUCKET_ACTIVE);
+                UsageStatsManager.STANDBY_BUCKET_ACTIVE, REASON_MAIN_USAGE);
 
         areRestricted(instance, UID_1, PACKAGE_1, NONE);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
@@ -644,9 +647,9 @@
         mPowerSaveObserver.accept(getPowerSaveState());
 
         mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false,
-                UsageStatsManager.STANDBY_BUCKET_EXEMPTED);
+                UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
         mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 0, false,
-                UsageStatsManager.STANDBY_BUCKET_EXEMPTED);
+                UsageStatsManager.STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
 
         setAppOps(UID_1, PACKAGE_1, true);
 
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
index 7b06648..36504ac 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java
@@ -16,10 +16,13 @@
 
 package com.android.server.usage;
 
-import static android.app.usage.UsageStatsManager.REASON_TIMEOUT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -92,18 +95,18 @@
         AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
 
         aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 1000, STANDBY_BUCKET_ACTIVE,
-                UsageStatsManager.REASON_USAGE);
+                REASON_MAIN_USAGE);
         // ACTIVE means not idle
         assertFalse(aih.isIdle(PACKAGE_1, USER_ID, 2000));
 
         aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
-                UsageStatsManager.REASON_USAGE);
+                REASON_MAIN_USAGE);
         aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
-                REASON_TIMEOUT);
+                REASON_MAIN_TIMEOUT);
 
         assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
-        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_TIMEOUT);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT);
 
         // RARE is considered idle
         assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
@@ -115,7 +118,7 @@
         aih = new AppIdleHistory(mStorageDir, 4000);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
         assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
-        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_TIMEOUT);
+        assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT);
 
         assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
         assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
@@ -133,4 +136,18 @@
         assertEquals(1000, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 7000));
         assertEquals(5000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 7000));
     }
+
+    public void testReason() throws Exception {
+        AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);
+        aih.reportUsage(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
+                REASON_SUB_USAGE_MOVE_TO_FOREGROUND, 2000, 0);
+        assertEquals(REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND,
+                aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000));
+        aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 4000, STANDBY_BUCKET_WORKING_SET,
+                REASON_MAIN_TIMEOUT);
+        aih.writeAppIdleTimes(USER_ID);
+
+        aih = new AppIdleHistory(mStorageDir, 5000);
+        assertEquals(REASON_MAIN_TIMEOUT, aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000));
+    }
 }
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index cbbdca6..edf1f74 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -18,11 +18,11 @@
 
 import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
 import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
-import static android.app.usage.UsageStatsManager.REASON_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_FORCED;
-import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
-import static android.app.usage.UsageStatsManager.REASON_TIMEOUT;
-import static android.app.usage.UsageStatsManager.REASON_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
@@ -410,11 +410,11 @@
         setChargingState(mController, false);
         // Set it to timeout or usage, so that prediction can override it
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_TIMEOUT, 1 * HOUR_MS);
+                REASON_MAIN_TIMEOUT, 1 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController));
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_PREDICTED + ":CTS", 1 * HOUR_MS);
+                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
 
         // Fast forward 12 hours
@@ -440,28 +440,28 @@
         setChargingState(mController, false);
         // Can force to NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED, 1 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController));
 
         // Prediction can't override FORCED reason
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_FORCED, 1 * HOUR_MS);
+                REASON_MAIN_FORCED, 1 * HOUR_MS);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_PREDICTED, 1 * HOUR_MS);
+                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController));
 
         // Prediction can't override NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_DEFAULT, 2 * HOUR_MS);
+                REASON_MAIN_DEFAULT, 2 * HOUR_MS);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController));
 
         // Prediction can't set to NEVER
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_USAGE, 2 * HOUR_MS);
+                REASON_MAIN_USAGE, 2 * HOUR_MS);
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
-                REASON_PREDICTED, 2 * HOUR_MS);
+                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
         assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
     }
 
@@ -474,7 +474,7 @@
 
         mInjector.mElapsedRealtime = 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // bucketing works after timeout
@@ -483,7 +483,7 @@
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_FREQUENT);
     }
 
@@ -498,15 +498,15 @@
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
-                REASON_PREDICTED, 1000);
+                REASON_MAIN_PREDICTED, 1000);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
+                REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
+                REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
         assertBucket(STANDBY_BUCKET_FREQUENT);
     }
 
@@ -527,18 +527,18 @@
         // Still in ACTIVE after first USER_INTERACTION times out
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // Both timed out, so NOTIFICATION_SEEN timeout should be effective
         mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_WORKING_SET);
 
         mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_RARE);
     }
 
@@ -561,7 +561,7 @@
         // Predict to ACTIVE
         mInjector.mElapsedRealtime += 1000;
         mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
-                REASON_PREDICTED, mInjector.mElapsedRealtime);
+                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
         assertBucket(STANDBY_BUCKET_ACTIVE);
 
         // CheckIdleStates should not change the prediction
diff --git a/services/usage/java/com/android/server/usage/AppIdleHistory.java b/services/usage/java/com/android/server/usage/AppIdleHistory.java
index 8e5a418..fd28b65 100644
--- a/services/usage/java/com/android/server/usage/AppIdleHistory.java
+++ b/services/usage/java/com/android/server/usage/AppIdleHistory.java
@@ -16,10 +16,12 @@
 
 package com.android.server.usage;
 
-import static android.app.usage.UsageStatsManager.REASON_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_FORCED;
-import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
-import static android.app.usage.UsageStatsManager.REASON_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
@@ -112,8 +114,10 @@
         // Standby bucket
         @UsageStatsManager.StandbyBuckets
         int currentBucket;
-        // Reason for setting the standby bucket. TODO: Switch to int.
-        String bucketingReason;
+        // Reason for setting the standby bucket. The value here is a combination of
+        // one of UsageStatsManager.REASON_MAIN_* and one (or none) of
+        // UsageStatsManager.REASON_SUB_*. Also see REASON_MAIN_MASK and REASON_SUB_MASK.
+        int bucketingReason;
         // In-memory only, last bucket for which the listeners were informed
         int lastInformedBucket;
         // The last time a job was run for this app, using elapsed timebase
@@ -212,13 +216,14 @@
      * @param appUsageHistory the usage record for the app being updated
      * @param packageName name of the app being updated, for logging purposes
      * @param newBucket the bucket to set the app to
+     * @param usageReason the sub-reason for usage, one of REASON_SUB_USAGE_*
      * @param elapsedRealtime mark as used time if non-zero
      * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
      *                with bucket values of ACTIVE and WORKING_SET.
      * @return
      */
     public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
-            int newBucket, long elapsedRealtime, long timeout) {
+            int newBucket, int usageReason, long elapsedRealtime, long timeout) {
         // Set the timeout if applicable
         if (timeout > elapsedRealtime) {
             // Convert to elapsed timebase
@@ -246,10 +251,10 @@
             if (DEBUG) {
                 Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
                         .currentBucket
-                        + ", reason=" + appUsageHistory.bucketingReason);
+                        + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
             }
         }
-        appUsageHistory.bucketingReason = REASON_USAGE;
+        appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason;
 
         return appUsageHistory;
     }
@@ -262,16 +267,17 @@
      * @param packageName
      * @param userId
      * @param newBucket the bucket to set the app to
-     * @param elapsedRealtime mark as used time if non-zero
+     * @param usageReason sub reason for usage
+     * @param nowElapsed mark as used time if non-zero
      * @param timeout set the timeout of the specified bucket, if non-zero. Can only be used
      *                with bucket values of ACTIVE and WORKING_SET.
      * @return
      */
     public AppUsageHistory reportUsage(String packageName, int userId, int newBucket,
-            long nowElapsed, long timeout) {
+            int usageReason, long nowElapsed, long timeout) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory history = getPackageHistory(userHistory, packageName, nowElapsed, true);
-        return reportUsage(history, packageName, newBucket, nowElapsed, timeout);
+        return reportUsage(history, packageName, newBucket, usageReason, nowElapsed, timeout);
     }
 
     private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
@@ -293,7 +299,7 @@
             appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
             appUsageHistory.lastPredictedTime = getElapsedTime(0);
             appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
-            appUsageHistory.bucketingReason = REASON_DEFAULT;
+            appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
             appUsageHistory.lastInformedBucket = -1;
             appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
             userHistory.put(packageName, appUsageHistory);
@@ -328,18 +334,18 @@
     }
 
     public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
-            int bucket, String reason) {
+            int bucket, int reason) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory =
                 getPackageHistory(userHistory, packageName, elapsedRealtime, true);
         appUsageHistory.currentBucket = bucket;
         appUsageHistory.bucketingReason = reason;
-        if (reason.startsWith(REASON_PREDICTED)) {
+        if ((reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED) {
             appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime);
         }
         if (DEBUG) {
             Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
-                    + ", reason=" + appUsageHistory.bucketingReason);
+                    + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
         }
     }
 
@@ -393,11 +399,11 @@
         return buckets;
     }
 
-    public String getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
+    public int getAppStandbyReason(String packageName, int userId, long elapsedRealtime) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory =
                 getPackageHistory(userHistory, packageName, elapsedRealtime, false);
-        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
+        return appUsageHistory != null ? appUsageHistory.bucketingReason : 0;
     }
 
     public long getElapsedTime(long elapsedRealtime) {
@@ -411,11 +417,11 @@
                 elapsedRealtime, true);
         if (idle) {
             appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
-            appUsageHistory.bucketingReason = REASON_FORCED;
+            appUsageHistory.bucketingReason = REASON_MAIN_FORCED;
         } else {
             appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
             // This is to pretend that the app was just used, don't freeze the state anymore.
-            appUsageHistory.bucketingReason = REASON_USAGE;
+            appUsageHistory.bucketingReason = REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION;
         }
         return appUsageHistory.currentBucket;
     }
@@ -516,7 +522,7 @@
                         appUsageHistory.currentBucket = currentBucketString == null
                                 ? STANDBY_BUCKET_ACTIVE
                                 : Integer.parseInt(currentBucketString);
-                        appUsageHistory.bucketingReason =
+                        String bucketingReason =
                                 parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
                         appUsageHistory.lastJobRunTime = getLongValue(parser,
                                 ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
@@ -524,8 +530,13 @@
                                 ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, 0L);
                         appUsageHistory.bucketWorkingSetTimeoutTime = getLongValue(parser,
                                 ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME, 0L);
-                        if (appUsageHistory.bucketingReason == null) {
-                            appUsageHistory.bucketingReason = REASON_DEFAULT;
+                        appUsageHistory.bucketingReason = REASON_MAIN_DEFAULT;
+                        if (bucketingReason != null) {
+                            try {
+                                appUsageHistory.bucketingReason =
+                                        Integer.parseInt(bucketingReason, 16);
+                            } catch (NumberFormatException nfe) {
+                            }
                         }
                         appUsageHistory.lastInformedBucket = -1;
                         userHistory.put(packageName, appUsageHistory);
@@ -574,7 +585,8 @@
                         Long.toString(history.lastPredictedTime));
                 xml.attribute(null, ATTR_CURRENT_BUCKET,
                         Integer.toString(history.currentBucket));
-                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
+                xml.attribute(null, ATTR_BUCKETING_REASON,
+                        Integer.toHexString(history.bucketingReason));
                 if (history.bucketActiveTimeoutTime > 0) {
                     xml.attribute(null, ATTR_BUCKET_ACTIVE_TIMEOUT_TIME, Long.toString(history
                             .bucketActiveTimeoutTime));
@@ -600,7 +612,7 @@
     }
 
     public void dump(IndentingPrintWriter idpw, int userId, String pkg) {
-        idpw.println("Package idle stats:");
+        idpw.println("App Standby States:");
         idpw.increaseIndent();
         ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -615,24 +627,25 @@
                 continue;
             }
             idpw.print("package=" + packageName);
-            idpw.print(" userId=" + userId);
-            idpw.print(" lastUsedElapsed=");
+            idpw.print(" u=" + userId);
+            idpw.print(" bucket=" + appUsageHistory.currentBucket
+                    + " reason="
+                    + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason));
+            idpw.print(" used=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
-            idpw.print(" lastUsedScreenOn=");
+            idpw.print(" usedScr=");
             TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
-            idpw.print(" lastPredictedTime=");
+            idpw.print(" lastPred=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
-            idpw.print(" bucketActiveTimeoutTime=");
-            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketActiveTimeoutTime,
+            idpw.print(" activeLeft=");
+            TimeUtils.formatDuration(appUsageHistory.bucketActiveTimeoutTime - totalElapsedTime,
                     idpw);
-            idpw.print(" bucketWorkingSetTimeoutTime=");
-            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketWorkingSetTimeoutTime,
+            idpw.print(" wsLeft=");
+            TimeUtils.formatDuration(appUsageHistory.bucketWorkingSetTimeoutTime - totalElapsedTime,
                     idpw);
-            idpw.print(" lastJobRunTime=");
+            idpw.print(" lastJob=");
             TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
             idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
-            idpw.print(" bucket=" + appUsageHistory.currentBucket
-                    + " reason=" + appUsageHistory.bucketingReason);
             idpw.println();
         }
         idpw.println();
diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java
index f40aa5b..e836677 100644
--- a/services/usage/java/com/android/server/usage/AppStandbyController.java
+++ b/services/usage/java/com/android/server/usage/AppStandbyController.java
@@ -16,11 +16,20 @@
 
 package com.android.server.usage;
 
-import static android.app.usage.UsageStatsManager.REASON_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_FORCED;
-import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
-import static android.app.usage.UsageStatsManager.REASON_TIMEOUT;
-import static android.app.usage.UsageStatsManager.REASON_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_NOTIFICATION_SEEN;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_UPDATE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
 import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
@@ -129,8 +138,8 @@
             STANDBY_BUCKET_RARE
     };
 
-    // Expiration time for predicted bucket
-    private static final long PREDICTION_TIMEOUT = 12 * ONE_HOUR;
+    /** Default expiration time for bucket prediction. After this, use thresholds to downgrade. */
+    private static final long DEFAULT_PREDICTION_TIMEOUT = 12 * ONE_HOUR;
 
     /**
      * Indicates the maximum wait time for admin data to be available;
@@ -187,6 +196,8 @@
     long mNotificationSeenTimeoutMillis;
     /** Minimum time a system update event should keep the buckets elevated. */
     long mSystemUpdateUsageTimeoutMillis;
+    /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */
+    long mPredictionTimeoutMillis;
 
     volatile boolean mAppIdleEnabled;
     boolean mAppIdleTempParoled;
@@ -223,24 +234,30 @@
         // Whether the bucket change is because the user has started interacting with the app
         boolean isUserInteraction;
 
-        StandbyUpdateRecord(String pkgName, int userId, int bucket, boolean isInteraction) {
+        // Reason for bucket change
+        int reason;
+
+        StandbyUpdateRecord(String pkgName, int userId, int bucket, int reason,
+                boolean isInteraction) {
             this.packageName = pkgName;
             this.userId = userId;
             this.bucket = bucket;
+            this.reason = reason;
             this.isUserInteraction = isInteraction;
         }
 
         public static StandbyUpdateRecord obtain(String pkgName, int userId,
-                int bucket, boolean isInteraction) {
+                int bucket, int reason, boolean isInteraction) {
             synchronized (sStandbyUpdatePool) {
                 final int size = sStandbyUpdatePool.size();
                 if (size < 1) {
-                    return new StandbyUpdateRecord(pkgName, userId, bucket, isInteraction);
+                    return new StandbyUpdateRecord(pkgName, userId, bucket, reason, isInteraction);
                 }
                 StandbyUpdateRecord r = sStandbyUpdatePool.remove(size - 1);
                 r.packageName = pkgName;
                 r.userId = userId;
                 r.bucket = bucket;
+                r.reason = reason;
                 r.isUserInteraction = isInteraction;
                 return r;
             }
@@ -339,10 +356,11 @@
                 if (!packageName.equals(providerPkgName)) {
                     synchronized (mAppIdleLock) {
                         AppUsageHistory appUsage = mAppIdleHistory.reportUsage(packageName, userId,
-                                STANDBY_BUCKET_ACTIVE, elapsedRealtime,
+                                STANDBY_BUCKET_ACTIVE, REASON_SUB_USAGE_SYNC_ADAPTER,
+                                elapsedRealtime,
                                 elapsedRealtime + mStrongUsageTimeoutMillis);
                         maybeInformListeners(packageName, userId, elapsedRealtime,
-                                appUsage.currentBucket, false);
+                                appUsage.currentBucket, appUsage.bucketingReason, false);
                     }
                 }
             } catch (PackageManager.NameNotFoundException e) {
@@ -497,50 +515,54 @@
         if (isSpecial) {
             synchronized (mAppIdleLock) {
                 mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
-                        STANDBY_BUCKET_EXEMPTED, REASON_DEFAULT);
+                        STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
             }
             maybeInformListeners(packageName, userId, elapsedRealtime,
-                    STANDBY_BUCKET_EXEMPTED, false);
+                    STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT, false);
         } else {
             synchronized (mAppIdleLock) {
                 final AppIdleHistory.AppUsageHistory app =
                         mAppIdleHistory.getAppUsageHistory(packageName,
                         userId, elapsedRealtime);
-                String reason = app.bucketingReason;
+                int reason = app.bucketingReason;
+                final int oldMainReason = reason & REASON_MAIN_MASK;
 
                 // If the bucket was forced by the user/developer, leave it alone.
                 // A usage event will be the only way to bring it out of this forced state
-                if (REASON_FORCED.equals(app.bucketingReason)) {
+                if (oldMainReason == REASON_MAIN_FORCED) {
                     return;
                 }
                 final int oldBucket = app.currentBucket;
                 int newBucket = Math.max(oldBucket, STANDBY_BUCKET_ACTIVE); // Undo EXEMPTED
                 boolean predictionLate = false;
                 // Compute age-based bucket
-                if (REASON_DEFAULT.equals(app.bucketingReason)
-                        || REASON_USAGE.equals(app.bucketingReason)
-                        || REASON_TIMEOUT.equals(app.bucketingReason)
+                if (oldMainReason == REASON_MAIN_DEFAULT
+                        || oldMainReason == REASON_MAIN_USAGE
+                        || oldMainReason == REASON_MAIN_TIMEOUT
                         || (predictionLate = predictionTimedOut(app, elapsedRealtime))) {
                     newBucket = getBucketForLocked(packageName, userId,
                             elapsedRealtime);
                     if (DEBUG) {
                         Slog.d(TAG, "Evaluated AOSP newBucket = " + newBucket);
                     }
-                    reason = REASON_TIMEOUT;
+                    reason = REASON_MAIN_TIMEOUT;
                 }
                 // Check if the app is within one of the timeouts for forced bucket elevation
                 final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime);
                 if (newBucket >= STANDBY_BUCKET_ACTIVE
                         && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) {
                     newBucket = STANDBY_BUCKET_ACTIVE;
-                    reason = REASON_USAGE;
+                    reason = app.bucketingReason;
                     if (DEBUG) {
                         Slog.d(TAG, "    Keeping at ACTIVE due to min timeout");
                     }
                 } else if (newBucket >= STANDBY_BUCKET_WORKING_SET
                         && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) {
                     newBucket = STANDBY_BUCKET_WORKING_SET;
-                    reason = REASON_USAGE;
+                    // If it was already there, keep the reason, else assume timeout to WS
+                    reason = (newBucket == oldBucket)
+                            ? app.bucketingReason
+                            : REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
                     if (DEBUG) {
                         Slog.d(TAG, "    Keeping at WORKING_SET due to min timeout");
                     }
@@ -553,43 +575,41 @@
                     mAppIdleHistory.setAppStandbyBucket(packageName, userId,
                             elapsedRealtime, newBucket, reason);
                     maybeInformListeners(packageName, userId, elapsedRealtime,
-                            newBucket, false);
+                            newBucket, reason, false);
                 }
             }
         }
     }
 
+    /** Returns true if there hasn't been a prediction for the app in a while. */
     private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) {
-        return app.bucketingReason != null
-                && app.bucketingReason.startsWith(REASON_PREDICTED)
+        return (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED
                 && app.lastPredictedTime > 0
                 && mAppIdleHistory.getElapsedTime(elapsedRealtime)
-                    - app.lastPredictedTime > PREDICTION_TIMEOUT;
+                    - app.lastPredictedTime > mPredictionTimeoutMillis;
     }
 
-    private boolean hasBucketTimeoutPassed(AppIdleHistory.AppUsageHistory app,
-            long elapsedRealtime) {
-        final long elapsedTimeAdjusted = mAppIdleHistory.getElapsedTime(elapsedRealtime);
-        return app.bucketActiveTimeoutTime < elapsedTimeAdjusted
-                && app.bucketWorkingSetTimeoutTime < elapsedTimeAdjusted;
-    }
-
+    /** Inform listeners if the bucket has changed since it was last reported to listeners */
     private void maybeInformListeners(String packageName, int userId,
-            long elapsedRealtime, int bucket, boolean userStartedInteracting) {
+            long elapsedRealtime, int bucket, int reason, boolean userStartedInteracting) {
         synchronized (mAppIdleLock) {
-            // TODO: fold these into one call + lookup for efficiency if needed
             if (mAppIdleHistory.shouldInformListeners(packageName, userId,
                     elapsedRealtime, bucket)) {
-                StandbyUpdateRecord r = StandbyUpdateRecord.obtain(packageName, userId,
-                        bucket, userStartedInteracting);
+                final StandbyUpdateRecord r = StandbyUpdateRecord.obtain(packageName, userId,
+                        bucket, reason, userStartedInteracting);
                 if (DEBUG) Slog.d(TAG, "Standby bucket for " + packageName + "=" + bucket);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        StandbyUpdateRecord.obtain(packageName, userId,
-                                bucket, userStartedInteracting)));
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, r));
             }
         }
     }
 
+    /**
+     * Evaluates next bucket based on time since last used and the bucketing thresholds.
+     * @param packageName the app
+     * @param userId the user
+     * @param elapsedRealtime as the name suggests, current elapsed time
+     * @return the bucket for the app, based on time since last used
+     */
     @GuardedBy("mAppIdleLock")
     @StandbyBuckets int getBucketForLocked(String packageName, int userId,
             long elapsedRealtime) {
@@ -675,17 +695,20 @@
                 final AppUsageHistory appHistory = mAppIdleHistory.getAppUsageHistory(
                         event.mPackage, userId, elapsedRealtime);
                 final int prevBucket = appHistory.currentBucket;
-                final String prevBucketReason = appHistory.bucketingReason;
+                final int prevBucketReason = appHistory.bucketingReason;
                 final long nextCheckTime;
+                final int subReason = usageEventToSubReason(event.mEventType);
+                final int reason = REASON_MAIN_USAGE | subReason;
                 if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
                     // Mild usage elevates to WORKING_SET but doesn't change usage time.
                     mAppIdleHistory.reportUsage(appHistory, event.mPackage,
-                            STANDBY_BUCKET_WORKING_SET,
+                            STANDBY_BUCKET_WORKING_SET, subReason,
                             0, elapsedRealtime + mNotificationSeenTimeoutMillis);
                     nextCheckTime = mNotificationSeenTimeoutMillis;
+
                 } else {
                     mAppIdleHistory.reportUsage(appHistory, event.mPackage,
-                            STANDBY_BUCKET_ACTIVE,
+                            STANDBY_BUCKET_ACTIVE, subReason,
                             elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis);
                     nextCheckTime = mStrongUsageTimeoutMillis;
                 }
@@ -695,9 +718,9 @@
                 final boolean userStartedInteracting =
                         appHistory.currentBucket == STANDBY_BUCKET_ACTIVE &&
                         prevBucket != appHistory.currentBucket &&
-                        prevBucketReason != REASON_USAGE;
+                        (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
                 maybeInformListeners(event.mPackage, userId, elapsedRealtime,
-                        appHistory.currentBucket, userStartedInteracting);
+                        appHistory.currentBucket, reason, userStartedInteracting);
 
                 if (previouslyIdle) {
                     notifyBatteryStats(event.mPackage, userId, false);
@@ -706,6 +729,17 @@
         }
     }
 
+    private int usageEventToSubReason(int eventType) {
+        switch (eventType) {
+            case UsageEvents.Event.MOVE_TO_FOREGROUND: return REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
+            case UsageEvents.Event.MOVE_TO_BACKGROUND: return REASON_SUB_USAGE_MOVE_TO_BACKGROUND;
+            case UsageEvents.Event.SYSTEM_INTERACTION: return REASON_SUB_USAGE_SYSTEM_INTERACTION;
+            case UsageEvents.Event.USER_INTERACTION: return REASON_SUB_USAGE_USER_INTERACTION;
+            case UsageEvents.Event.NOTIFICATION_SEEN: return REASON_SUB_USAGE_NOTIFICATION_SEEN;
+            default: return 0;
+        }
+    }
+
     /**
      * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
      * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
@@ -731,7 +765,8 @@
                 userId, elapsedRealtime);
         // Inform listeners if necessary
         if (previouslyIdle != stillIdle) {
-            maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, false);
+            maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
+                    REASON_MAIN_FORCED, false);
             if (!stillIdle) {
                 notifyBatteryStats(packageName, userId, idle);
             }
@@ -962,11 +997,11 @@
     }
 
     void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
-            String reason, long elapsedRealtime) {
+            int reason, long elapsedRealtime) {
         synchronized (mAppIdleLock) {
             AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName,
                     userId, elapsedRealtime);
-            boolean predicted = reason != null && reason.startsWith(REASON_PREDICTED);
+            boolean predicted = (reason & REASON_MAIN_MASK) == REASON_MAIN_PREDICTED;
 
             // Don't allow changing bucket if higher than ACTIVE
             if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return;
@@ -979,7 +1014,7 @@
             }
 
             // If the bucket was forced, don't allow prediction to override
-            if (app.bucketingReason.equals(REASON_FORCED) && predicted) return;
+            if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return;
 
             // If the bucket is required to stay in a higher state for a specified duration, don't
             // override unless the duration has passed
@@ -989,14 +1024,18 @@
                 if (newBucket > STANDBY_BUCKET_ACTIVE
                         && app.bucketActiveTimeoutTime > elapsedTimeAdjusted) {
                     newBucket = STANDBY_BUCKET_ACTIVE;
-                    reason = REASON_USAGE;
+                    reason = app.bucketingReason;
                     if (DEBUG) {
                         Slog.d(TAG, "    Keeping at ACTIVE due to min timeout");
                     }
                 } else if (newBucket > STANDBY_BUCKET_WORKING_SET
                         && app.bucketWorkingSetTimeoutTime > elapsedTimeAdjusted) {
                     newBucket = STANDBY_BUCKET_WORKING_SET;
-                    reason = REASON_USAGE;
+                    if (app.currentBucket != newBucket) {
+                        reason = REASON_MAIN_USAGE | REASON_SUB_USAGE_ACTIVE_TIMEOUT;
+                    } else {
+                        reason = app.bucketingReason;
+                    }
                     if (DEBUG) {
                         Slog.d(TAG, "    Keeping at WORKING_SET due to min timeout");
                     }
@@ -1006,7 +1045,7 @@
             mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
                     reason);
         }
-        maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, false);
+        maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, reason, false);
     }
 
     @VisibleForTesting
@@ -1106,11 +1145,12 @@
         return packageName != null && packageName.equals(activeScorer);
     }
 
-    void informListeners(String packageName, int userId, int bucket, boolean userInteraction) {
+    void informListeners(String packageName, int userId, int bucket, int reason,
+            boolean userInteraction) {
         final boolean idle = bucket >= STANDBY_BUCKET_RARE;
         synchronized (mPackageAccessListeners) {
             for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-                listener.onAppIdleStateChanged(packageName, userId, idle, bucket);
+                listener.onAppIdleStateChanged(packageName, userId, idle, bucket, reason);
                 if (userInteraction) {
                     listener.onUserInteractionStarted(packageName, userId);
                 }
@@ -1188,7 +1228,8 @@
                 if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
                     // Mark app as used for 2 hours. After that it can timeout to whatever the
                     // past usage pattern was.
-                    mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE, 0,
+                    mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE,
+                            REASON_SUB_USAGE_SYSTEM_UPDATE, 0,
                             elapsedRealtime + mSystemUpdateUsageTimeoutMillis);
                 }
             }
@@ -1369,7 +1410,8 @@
             switch (msg.what) {
                 case MSG_INFORM_LISTENERS:
                     StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj;
-                    informListeners(r.packageName, r.userId, r.bucket, r.isUserInteraction);
+                    informListeners(r.packageName, r.userId, r.bucket, r.reason,
+                            r.isUserInteraction);
                     r.recycle();
                     break;
 
@@ -1477,7 +1519,7 @@
                 "notification_seen_duration";
         private static final String KEY_SYSTEM_UPDATE_HOLD_DURATION =
                 "system_update_usage_duration";
-
+        private static final String KEY_PREDICTION_TIMEOUT = "prediction_timeout";
 
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
@@ -1547,6 +1589,9 @@
                 mSystemUpdateUsageTimeoutMillis = mParser.getDurationMillis
                         (KEY_SYSTEM_UPDATE_HOLD_DURATION,
                                 COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR);
+                mPredictionTimeoutMillis = mParser.getDurationMillis
+                        (KEY_PREDICTION_TIMEOUT,
+                                COMPRESS_TIME ? 10 * ONE_MINUTE : DEFAULT_PREDICTION_TIMEOUT);
             }
         }
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index dedf967..43ac58a 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -120,10 +120,10 @@
             new UsageStatsManagerInternal.AppIdleStateChangeListener() {
                 @Override
                 public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
-                        int bucket) {
+                        int bucket, int reason) {
                     Event event = new Event();
                     event.mEventType = Event.STANDBY_BUCKET_CHANGED;
-                    event.mBucket = bucket;
+                    event.mBucketAndReason = (bucket << 16) | (reason & 0xFFFF);
                     event.mPackage = packageName;
                     // This will later be converted to system time.
                     event.mTimeStamp = SystemClock.elapsedRealtime();
@@ -741,9 +741,9 @@
                 throw re.rethrowFromSystemServer();
             }
             final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final String reason = shellCaller
-                    ? UsageStatsManager.REASON_FORCED
-                    : UsageStatsManager.REASON_PREDICTED + ":" + callingUid;
+            final int reason = shellCaller
+                    ? UsageStatsManager.REASON_MAIN_FORCED
+                    : UsageStatsManager.REASON_MAIN_PREDICTED;
             final long token = Binder.clearCallingIdentity();
             try {
                 // Caller cannot set their own standby state
@@ -798,9 +798,9 @@
                 throw re.rethrowFromSystemServer();
             }
             final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
-            final String reason = shellCaller
-                    ? UsageStatsManager.REASON_FORCED
-                    : UsageStatsManager.REASON_PREDICTED + ":" + callingUid;
+            final int reason = shellCaller
+                    ? UsageStatsManager.REASON_MAIN_FORCED
+                    : UsageStatsManager.REASON_MAIN_PREDICTED;
             final long token = Binder.clearCallingIdentity();
             try {
                 final long elapsedRealtime = SystemClock.elapsedRealtime();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
index d1ed599..5e7e80d 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsXmlV1.java
@@ -26,10 +26,7 @@
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStats;
 import android.content.res.Configuration;
-import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.Log;
-import android.util.LogWriter;
 
 import java.io.IOException;
 import java.net.ProtocolException;
@@ -175,7 +172,7 @@
                 event.mShortcutId = (id != null) ? id.intern() : null;
                 break;
             case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
-                event.mBucket = XmlUtils.readIntAttribute(parser, STANDBY_BUCKET_ATTR, 0);
+                event.mBucketAndReason = XmlUtils.readIntAttribute(parser, STANDBY_BUCKET_ATTR, 0);
                 break;
         }
 
@@ -281,8 +278,8 @@
                 }
                 break;
             case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
-                if (event.mBucket != 0) {
-                    XmlUtils.writeIntAttribute(xml, STANDBY_BUCKET_ATTR, event.mBucket);
+                if (event.mBucketAndReason != 0) {
+                    XmlUtils.writeIntAttribute(xml, STANDBY_BUCKET_ATTR, event.mBucketAndReason);
                 }
         }
 
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index 8afc511..3fbcd81 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -520,7 +520,8 @@
             pw.printPair("shortcutId", event.mShortcutId);
         }
         if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
-            pw.printPair("standbyBucket", event.mBucket);
+            pw.printPair("standbyBucket", event.getStandbyBucket());
+            pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
         }
         pw.printHexPair("flags", event.mFlags);
         pw.println();
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index b6be3c5..82a7450 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -1215,6 +1216,7 @@
     }
 
     /** @hide */
+    @TestApi
     public void setSystemAndNetworkId(int systemId, int networkId) {
         this.mSystemId = systemId;
         this.mNetworkId = networkId;